aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules0
-rw-r--r--all/build.gradle2
-rw-r--r--build.gradle3
-rw-r--r--buildscripts/import-control.xml6
-rw-r--r--checker-framework/stubs/log4j.astub8
-rw-r--r--contrib/log_correlation/log4j2/README.md88
-rw-r--r--contrib/log_correlation/log4j2/build.gradle26
-rw-r--r--contrib/log_correlation/log4j2/src/main/java/io/opencensus/contrib/logcorrelation/log4j2/ContextDataUtils.java212
-rw-r--r--contrib/log_correlation/log4j2/src/main/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusTraceContextDataInjector.java177
-rw-r--r--contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/AbstractOpenCensusLog4jLogCorrelationTest.java97
-rw-r--r--contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusLog4jLogCorrelationAllSpansTest.java167
-rw-r--r--contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusLog4jLogCorrelationNoSpansTest.java86
-rw-r--r--contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusLog4jLogCorrelationSampledSpansTest.java89
-rw-r--r--contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusTraceContextDataInjectorTest.java207
-rw-r--r--contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/TestSpan.java46
-rw-r--r--settings.gradle3
16 files changed, 1217 insertions, 0 deletions
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.gitmodules
diff --git a/all/build.gradle b/all/build.gradle
index d46f2f61..83ffb69e 100644
--- a/all/build.gradle
+++ b/all/build.gradle
@@ -13,6 +13,7 @@ def subprojects = [
project(':opencensus-contrib-grpc-util'),
project(':opencensus-contrib-grpc-metrics'),
project(':opencensus-contrib-http-util'),
+ project(':opencensus-contrib-log-correlation-log4j2'),
project(':opencensus-contrib-log-correlation-stackdriver'),
project(':opencensus-contrib-monitored-resource-util'),
project(':opencensus-contrib-spring'),
@@ -39,6 +40,7 @@ def subprojects_javadoc = [
project(':opencensus-contrib-grpc-util'),
project(':opencensus-contrib-grpc-metrics'),
project(':opencensus-contrib-http-util'),
+ project(':opencensus-contrib-log-correlation-log4j2'),
project(':opencensus-contrib-log-correlation-stackdriver'),
project(':opencensus-contrib-monitored-resource-util'),
project(':opencensus-contrib-spring'),
diff --git a/build.gradle b/build.gradle
index 07751679..dcb006ce 100644
--- a/build.gradle
+++ b/build.gradle
@@ -158,6 +158,7 @@ subprojects {
googleAuthVersion = '0.11.0'
googleCloudBetaVersion = '0.64.0-beta'
googleCloudGaVersion = '1.46.0'
+ log4j2Version = '2.11.1'
signalfxVersion = '0.0.39'
springBootVersion = '1.5.15.RELEASE'
springCloudVersion = '1.3.4.RELEASE'
@@ -182,6 +183,7 @@ subprojects {
google_auth: "com.google.auth:google-auth-library-credentials:${googleAuthVersion}",
google_cloud_logging: "com.google.cloud:google-cloud-logging:${googleCloudGaVersion}",
google_cloud_trace: "com.google.cloud:google-cloud-trace:${googleCloudBetaVersion}",
+ log4j2: "org.apache.logging.log4j:log4j-core:${log4j2Version}",
zipkin_reporter: "io.zipkin.reporter2:zipkin-reporter:${zipkinReporterVersion}",
zipkin_urlconnection: "io.zipkin.reporter2:zipkin-sender-urlconnection:${zipkinReporterVersion}",
jaeger_reporter: "com.uber.jaeger:jaeger-core:${jaegerReporterVersion}",
@@ -423,6 +425,7 @@ subprojects {
'opencensus-contrib-grpc-metrics',
'opencensus-contrib-grpc-util',
'opencensus-contrib-http-util',
+ 'opencensus-contrib-log-correlation-log4j2',
'opencensus-contrib-log-correlation-stackdriver',
'opencensus-contrib-monitored-resource-util',
'opencensus-contrib-spring',
diff --git a/buildscripts/import-control.xml b/buildscripts/import-control.xml
index 6dffe25a..d545878a 100644
--- a/buildscripts/import-control.xml
+++ b/buildscripts/import-control.xml
@@ -94,6 +94,12 @@ General guidelines on imports:
<allow pkg="io.opencensus.tags"/>
<allow pkg="io.opencensus.trace"/>
</subpackage>
+ <subpackage name="logcorrelation.log4j2">
+ <allow pkg="io.opencensus.contrib.logcorrelation.log4j2"/>
+ <allow pkg="io.opencensus.trace"/>
+ <disallow pkg="org.apache.logging.log4j.core.impl"/>
+ <allow pkg="org.apache.logging.log4j"/>
+ </subpackage>
<subpackage name="logcorrelation.stackdriver">
<allow pkg="com.google.cloud"/>
<allow pkg="io.opencensus.trace"/>
diff --git a/checker-framework/stubs/log4j.astub b/checker-framework/stubs/log4j.astub
new file mode 100644
index 00000000..20b3240e
--- /dev/null
+++ b/checker-framework/stubs/log4j.astub
@@ -0,0 +1,8 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+package org.apache.logging.log4j;
+
+class ThreadContext {
+ @Nullable
+ static ReadOnlyThreadContextMap getThreadContextMap();
+}
diff --git a/contrib/log_correlation/log4j2/README.md b/contrib/log_correlation/log4j2/README.md
new file mode 100644
index 00000000..a5bf1449
--- /dev/null
+++ b/contrib/log_correlation/log4j2/README.md
@@ -0,0 +1,88 @@
+# OpenCensus Log4j 2 Log Correlation
+
+This subproject is currently experimental, so it may be redesigned or removed in the future. It
+will remain experimental until we have a specification for a log correlation feature in
+[opencensus-specs](https://github.com/census-instrumentation/opencensus-specs/)
+(issue [#123](https://github.com/census-instrumentation/opencensus-specs/issues/123)).
+
+The `opencensus-contrib-log-correlation-log4j2` artifact provides a
+[Log4j 2](https://logging.apache.org/log4j/2.x/)
+[`ContextDataInjector`](https://logging.apache.org/log4j/2.x/manual/extending.html#Custom_ContextDataInjector)
+that automatically adds tracing data to the context of Log4j
+[`LogEvent`](https://logging.apache.org/log4j/2.x/log4j-core/apidocs/org/apache/logging/log4j/core/LogEvent.html)s.
+The class name is
+`OpenCensusTraceContextDataInjector`. `OpenCensusTraceContextDataInjector` adds the current trace
+ID, span ID, and sampling decision to each `LogEvent`, so that they can be accessed with
+[`LogEvent.getContextData()`](https://logging.apache.org/log4j/2.x/log4j-core/apidocs/org/apache/logging/log4j/core/LogEvent.html#getContextData())
+or included in a layout.
+
+See
+https://github.com/census-ecosystem/opencensus-experiments/tree/master/java/log_correlation/log4j2
+for a demo that uses this library to correlate logs and traces in Stackdriver.
+
+## Instructions
+
+### Add the dependencies to your project
+
+For Maven add to your `pom.xml`:
+```xml
+<dependencies>
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-contrib-log-correlation-log4j2</artifactId>
+ <version>0.16.1</version>
+ <scope>runtime</scope>
+ </dependency>
+</dependencies>
+```
+
+For Gradle add to your dependencies:
+```groovy
+runtime 'io.opencensus:opencensus-contrib-log-correlation-log4j2:0.16.1'
+```
+
+### Configure the `OpenCensusTraceContextDataInjector`
+
+#### Specify the `ContextDataInjector` override
+
+Override Log4j's default `ContextDataInjector` by setting the system property
+`log4j2.contextDataInjector` to the full name of the class,
+`io.opencensus.contrib.logcorrelation.log4j2.OpenCensusTraceContextDataInjector`.
+
+#### Choose when to add tracing data to log events
+
+The following system property controls the decision to add tracing data from the current span to a
+log event:
+
+`io.opencensus.contrib.logcorrelation.log4j2.OpenCensusTraceContextDataInjector.spanSelection`
+
+The allowed values are:
+
+* `ALL_SPANS`: adds tracing data to all log events (default)
+
+* `NO_SPANS`: disables the log correlation feature
+
+* `SAMPLED_SPANS`: adds tracing data to log events when the current span is sampled
+
+### Add the tracing data to log entries
+
+`opencensus-contrib-log-correlation-log4j2` adds the following key-value pairs to the `LogEvent`
+context:
+
+* `opencensusTraceId` - the lowercase base16 encoding of the current trace ID
+* `opencensusSpanId` - the lowercase base16 encoding of the current span ID
+* `opencensusTraceSampled` - the sampling decision of the current span ("true" or "false")
+
+These values can be accessed from layouts with
+[Context Map Lookup](http://logging.apache.org/log4j/2.x/manual/lookups.html#ContextMapLookup). For
+example, the trace ID can be accessed with `$${ctx:opencensusTraceId}`. The values can also be
+accessed with the `X` conversion character in
+[`PatternLayout`](http://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout), for
+example, `%X{opencensusTraceId}`.
+
+See an example Log4j configuration file in the demo:
+https://github.com/census-ecosystem/opencensus-experiments/tree/master/java/log_correlation/log4j2/src/main/resources/log4j2.xml
+
+### Java Versions
+
+Java 6 or above is required for using this artifact.
diff --git a/contrib/log_correlation/log4j2/build.gradle b/contrib/log_correlation/log4j2/build.gradle
new file mode 100644
index 00000000..4a4a6ebc
--- /dev/null
+++ b/contrib/log_correlation/log4j2/build.gradle
@@ -0,0 +1,26 @@
+description = 'OpenCensus Log4j 2 Log Correlation'
+
+apply plugin: 'java'
+
+dependencies {
+ compile project(':opencensus-api'),
+ libraries.log4j2
+
+ testCompile libraries.guava
+
+ signature "org.codehaus.mojo.signature:java16:+@signature"
+}
+
+compileTestJava {
+ sourceCompatibility = "1.7"
+ targetCompatibility = "1.7"
+}
+
+test {
+ systemProperties['log4j2.contextDataInjector'] =
+ 'io.opencensus.contrib.logcorrelation.log4j2.OpenCensusTraceContextDataInjector'
+
+ // Each test class should run in a separate JVM. See the comment in
+ // AbstractOpenCensusLog4jLogCorrelationTest.
+ forkEvery = 1
+}
diff --git a/contrib/log_correlation/log4j2/src/main/java/io/opencensus/contrib/logcorrelation/log4j2/ContextDataUtils.java b/contrib/log_correlation/log4j2/src/main/java/io/opencensus/contrib/logcorrelation/log4j2/ContextDataUtils.java
new file mode 100644
index 00000000..dd32e448
--- /dev/null
+++ b/contrib/log_correlation/log4j2/src/main/java/io/opencensus/contrib/logcorrelation/log4j2/ContextDataUtils.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.contrib.logcorrelation.log4j2;
+
+import io.opencensus.contrib.logcorrelation.log4j2.OpenCensusTraceContextDataInjector.SpanSelection;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.unsafe.ContextUtils;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.ReadOnlyStringMap;
+import org.apache.logging.log4j.util.SortedArrayStringMap;
+import org.apache.logging.log4j.util.StringMap;
+import org.apache.logging.log4j.util.TriConsumer;
+
+// Implementation of the methods inherited from ContextDataInjector.
+//
+// This class uses "shareable" to mean that a method's return value can be passed to another
+// thread.
+final class ContextDataUtils {
+ private ContextDataUtils() {}
+
+ // The implementation of this method is based on the example in the Javadocs for
+ // ContextDataInjector.injectContextData.
+ static StringMap injectContextData(
+ SpanSelection spanSelection, @Nullable List<Property> properties, StringMap reusable) {
+ if (properties == null || properties.isEmpty()) {
+ return shareableRawContextData(spanSelection);
+ }
+ // Context data has precedence over configuration properties.
+ putProperties(properties, reusable);
+ // TODO(sebright): The following line can be optimized. See
+ // https://github.com/census-instrumentation/opencensus-java/pull/1422/files#r216425494.
+ reusable.putAll(nonShareableRawContextData(spanSelection));
+ return reusable;
+ }
+
+ private static void putProperties(Collection<Property> properties, StringMap stringMap) {
+ for (Property property : properties) {
+ stringMap.putValue(property.getName(), property.getValue());
+ }
+ }
+
+ private static StringMap shareableRawContextData(SpanSelection spanSelection) {
+ SpanContext spanContext = shouldAddTracingDataToLogEvent(spanSelection);
+ return spanContext == null
+ ? getShareableContextData()
+ : getShareableContextAndTracingData(spanContext);
+ }
+
+ static ReadOnlyStringMap nonShareableRawContextData(SpanSelection spanSelection) {
+ SpanContext spanContext = shouldAddTracingDataToLogEvent(spanSelection);
+ return spanContext == null
+ ? getNonShareableContextData()
+ : getShareableContextAndTracingData(spanContext);
+ }
+
+ // This method returns the current span context iff tracing data should be added to the LogEvent.
+ // It avoids getting the current span when the feature is disabled, for efficiency.
+ @Nullable
+ private static SpanContext shouldAddTracingDataToLogEvent(SpanSelection spanSelection) {
+ switch (spanSelection) {
+ case NO_SPANS:
+ return null;
+ case SAMPLED_SPANS:
+ SpanContext spanContext = getCurrentSpanContext();
+ if (spanContext.getTraceOptions().isSampled()) {
+ return spanContext;
+ } else {
+ return null;
+ }
+ case ALL_SPANS:
+ return getCurrentSpanContext();
+ }
+ throw new AssertionError("Unknown spanSelection: " + spanSelection);
+ }
+
+ private static StringMap getShareableContextData() {
+ ReadOnlyThreadContextMap context = ThreadContext.getThreadContextMap();
+
+ // Return a new object, since StringMap is modifiable.
+ return context == null
+ ? new SortedArrayStringMap(ThreadContext.getImmutableContext())
+ : new SortedArrayStringMap(context.getReadOnlyContextData());
+ }
+
+ private static ReadOnlyStringMap getNonShareableContextData() {
+ ReadOnlyThreadContextMap context = ThreadContext.getThreadContextMap();
+ if (context != null) {
+ return context.getReadOnlyContextData();
+ } else {
+ Map<String, String> contextMap = ThreadContext.getImmutableContext();
+ return contextMap.isEmpty()
+ ? UnmodifiableReadOnlyStringMap.EMPTY
+ : new UnmodifiableReadOnlyStringMap(contextMap);
+ }
+ }
+
+ private static StringMap getShareableContextAndTracingData(SpanContext spanContext) {
+ ReadOnlyThreadContextMap context = ThreadContext.getThreadContextMap();
+ SortedArrayStringMap stringMap;
+ if (context == null) {
+ stringMap = new SortedArrayStringMap(ThreadContext.getImmutableContext());
+ } else {
+ StringMap contextData = context.getReadOnlyContextData();
+ stringMap = new SortedArrayStringMap(contextData.size() + 3);
+ stringMap.putAll(contextData);
+ }
+ // TODO(sebright): Move the calls to TraceId.toLowerBase16() and SpanId.toLowerBase16() out of
+ // the critical path by wrapping the trace and span IDs in objects that call toLowerBase16() in
+ // their toString() methods, after there is a fix for
+ // https://github.com/census-instrumentation/opencensus-java/issues/1436.
+ stringMap.putValue(
+ OpenCensusTraceContextDataInjector.TRACE_ID_CONTEXT_KEY,
+ spanContext.getTraceId().toLowerBase16());
+ stringMap.putValue(
+ OpenCensusTraceContextDataInjector.SPAN_ID_CONTEXT_KEY,
+ spanContext.getSpanId().toLowerBase16());
+ stringMap.putValue(
+ OpenCensusTraceContextDataInjector.TRACE_SAMPLED_CONTEXT_KEY,
+ spanContext.getTraceOptions().isSampled() ? "true" : "false");
+ return stringMap;
+ }
+
+ private static SpanContext getCurrentSpanContext() {
+ Span span = ContextUtils.CONTEXT_SPAN_KEY.get();
+ return span == null ? SpanContext.INVALID : span.getContext();
+ }
+
+ @Immutable
+ private static final class UnmodifiableReadOnlyStringMap implements ReadOnlyStringMap {
+ private static final long serialVersionUID = 0L;
+
+ static final ReadOnlyStringMap EMPTY =
+ new UnmodifiableReadOnlyStringMap(Collections.<String, String>emptyMap());
+
+ private final Map<String, String> map;
+
+ UnmodifiableReadOnlyStringMap(Map<String, String> map) {
+ this.map = map;
+ }
+
+ @Override
+ public boolean containsKey(String key) {
+ return map.containsKey(key);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <V> void forEach(BiConsumer<String, ? super V> action) {
+ for (Entry<String, String> entry : map.entrySet()) {
+ action.accept(entry.getKey(), (V) entry.getValue());
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <V, S> void forEach(TriConsumer<String, ? super V, S> action, S state) {
+ for (Entry<String, String> entry : map.entrySet()) {
+ action.accept(entry.getKey(), (V) entry.getValue(), state);
+ }
+ }
+
+ @Override
+ @Nullable
+ @SuppressWarnings({
+ "unchecked",
+ "TypeParameterUnusedInFormals" // This is an overridden method.
+ })
+ public <V> V getValue(String key) {
+ return (V) map.get(key);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ @Override
+ public int size() {
+ return map.size();
+ }
+
+ @Override
+ public Map<String, String> toMap() {
+ return map;
+ }
+ }
+}
diff --git a/contrib/log_correlation/log4j2/src/main/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusTraceContextDataInjector.java b/contrib/log_correlation/log4j2/src/main/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusTraceContextDataInjector.java
new file mode 100644
index 00000000..38b18826
--- /dev/null
+++ b/contrib/log_correlation/log4j2/src/main/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusTraceContextDataInjector.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.contrib.logcorrelation.log4j2;
+
+import io.opencensus.common.ExperimentalApi;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.logging.log4j.core.ContextDataInjector;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.util.ReadOnlyStringMap;
+import org.apache.logging.log4j.util.StringMap;
+
+/**
+ * A Log4j {@link ContextDataInjector} that adds OpenCensus tracing data to log events.
+ *
+ * <p>This class adds the following key-value pairs:
+ *
+ * <ul>
+ * <li>{@value #TRACE_ID_CONTEXT_KEY} - the lowercase base16 encoding of the current trace ID
+ * <li>{@value #SPAN_ID_CONTEXT_KEY} - the lowercase base16 encoding of the current span ID
+ * <li>{@value #TRACE_SAMPLED_CONTEXT_KEY} - the sampling decision of the current span ({@code
+ * "true"} or {@code "false"})
+ * </ul>
+ *
+ * <p>The tracing data can be accessed with {@link LogEvent#getContextData} or included in a {@link
+ * Layout}. For example, the following patterns could be used to include the tracing data with a <a
+ * href="https://logging.apache.org/log4j/2.x/manual/layouts.html#Pattern_Layout">Pattern
+ * Layout</a>:
+ *
+ * <ul>
+ * <li><code>%X{opencensusTraceId}</code>
+ * <li><code>%X{opencensusSpanId}</code>
+ * <li><code>%X{opencensusTraceSampled}</code>
+ * </ul>
+ *
+ * <p>This feature is currently experimental.
+ *
+ * @since 0.16
+ * @see <a
+ * href="https://logging.apache.org/log4j/2.x/log4j-core/apidocs/org/apache/logging/log4j/core/ContextDataInjector.html">org.apache.logging.log4j.core.ContextDataInjector</a>
+ */
+@ExperimentalApi
+public final class OpenCensusTraceContextDataInjector implements ContextDataInjector {
+ private static final SpanSelection DEFAULT_SPAN_SELECTION = SpanSelection.ALL_SPANS;
+
+ /**
+ * Context key for the current trace ID. The name is {@value}.
+ *
+ * @since 0.16
+ */
+ public static final String TRACE_ID_CONTEXT_KEY = "opencensusTraceId";
+
+ /**
+ * Context key for the current span ID. The name is {@value}.
+ *
+ * @since 0.16
+ */
+ public static final String SPAN_ID_CONTEXT_KEY = "opencensusSpanId";
+
+ /**
+ * Context key for the sampling decision of the current span. The name is {@value}.
+ *
+ * @since 0.16
+ */
+ public static final String TRACE_SAMPLED_CONTEXT_KEY = "opencensusTraceSampled";
+
+ /**
+ * Name of the property that defines the {@link SpanSelection}. The name is {@value}.
+ *
+ * @since 0.16
+ */
+ public static final String SPAN_SELECTION_PROPERTY_NAME =
+ "io.opencensus.contrib.logcorrelation.log4j2."
+ + "OpenCensusTraceContextDataInjector.spanSelection";
+
+ private final SpanSelection spanSelection;
+
+ /**
+ * How to decide whether to add tracing data from the current span to a log entry.
+ *
+ * @since 0.16
+ */
+ public enum SpanSelection {
+
+ /**
+ * Never add tracing data to log entries. This constant disables the log correlation feature.
+ *
+ * @since 0.16
+ */
+ NO_SPANS,
+
+ /**
+ * Add tracing data to a log entry iff the current span is sampled.
+ *
+ * @since 0.16
+ */
+ SAMPLED_SPANS,
+
+ /**
+ * Always add tracing data to log entries, even when the current span is not sampled. This is
+ * the default.
+ *
+ * @since 0.16
+ */
+ ALL_SPANS
+ }
+
+ /**
+ * Returns the {@code SpanSelection} setting for this instance.
+ *
+ * @return the {@code SpanSelection} setting for this instance.
+ * @since 0.16
+ */
+ public SpanSelection getSpanSelection() {
+ return spanSelection;
+ }
+
+ /**
+ * Constructor to be called by Log4j.
+ *
+ * <p>This constructor looks up the {@link SpanSelection} using the system property {@link
+ * #SPAN_SELECTION_PROPERTY_NAME}.
+ *
+ * @since 0.16
+ */
+ public OpenCensusTraceContextDataInjector() {
+ this(lookUpSpanSelectionProperty());
+ }
+
+ // visible for testing
+ OpenCensusTraceContextDataInjector(SpanSelection spanSelection) {
+ this.spanSelection = spanSelection;
+ }
+
+ private static SpanSelection lookUpSpanSelectionProperty() {
+ String spanSelectionProperty = System.getProperty(SPAN_SELECTION_PROPERTY_NAME);
+ return spanSelectionProperty == null || spanSelectionProperty.isEmpty()
+ ? DEFAULT_SPAN_SELECTION
+ : parseSpanSelection(spanSelectionProperty);
+ }
+
+ private static SpanSelection parseSpanSelection(String spanSelection) {
+ try {
+ return SpanSelection.valueOf(spanSelection);
+ } catch (IllegalArgumentException e) {
+ return DEFAULT_SPAN_SELECTION;
+ }
+ }
+
+ // Note that this method must return an object that can be passed to another thread.
+ @Override
+ public StringMap injectContextData(@Nullable List<Property> properties, StringMap reusable) {
+ return ContextDataUtils.injectContextData(spanSelection, properties, reusable);
+ }
+
+ // Note that this method does not need to return an object that can be passed to another thread.
+ @Override
+ public ReadOnlyStringMap rawContextData() {
+ return ContextDataUtils.nonShareableRawContextData(spanSelection);
+ }
+}
diff --git a/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/AbstractOpenCensusLog4jLogCorrelationTest.java b/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/AbstractOpenCensusLog4jLogCorrelationTest.java
new file mode 100644
index 00000000..93ad85e0
--- /dev/null
+++ b/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/AbstractOpenCensusLog4jLogCorrelationTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.contrib.logcorrelation.log4j2;
+
+import io.opencensus.common.Function;
+import io.opencensus.common.Scope;
+import io.opencensus.contrib.logcorrelation.log4j2.OpenCensusTraceContextDataInjector.SpanSelection;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.Tracer;
+import io.opencensus.trace.Tracestate;
+import io.opencensus.trace.Tracing;
+import java.io.StringWriter;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.StringLayout;
+import org.apache.logging.log4j.core.appender.WriterAppender;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+
+/**
+ * Superclass for all Log4j log correlation test classes.
+ *
+ * <p>The tests are split into multiple classes so that each one can be run with a different value
+ * for the system property {@link OpenCensusTraceContextDataInjector#SPAN_SELECTION_PROPERTY_NAME}.
+ * The property must be set when Log4j initializes a static variable, and running each test class in
+ * a separate JVM causes the static variable to be reinitialized.
+ */
+abstract class AbstractOpenCensusLog4jLogCorrelationTest {
+ private static final Tracer tracer = Tracing.getTracer();
+
+ static final String TEST_PATTERN =
+ "traceId=%X{opencensusTraceId} spanId=%X{opencensusSpanId} "
+ + "sampled=%X{opencensusTraceSampled} %-5level - %msg";
+
+ static final Tracestate EMPTY_TRACESTATE = Tracestate.builder().build();
+
+ private static Logger logger;
+
+ // This method initializes Log4j after setting the SpanSelection, which means that Log4j
+ // initializes a static variable with a ContextDataInjector that is constructed with the proper
+ // SpanSelection. This method should be called from a @BeforeClass method in each subclass.
+ static void initializeLog4j(SpanSelection spanSelection) {
+ System.setProperty(
+ OpenCensusTraceContextDataInjector.SPAN_SELECTION_PROPERTY_NAME, spanSelection.toString());
+ logger = (Logger) LogManager.getLogger(AbstractOpenCensusLog4jLogCorrelationTest.class);
+ }
+
+ // Reconfigures Log4j using the given arguments and runs the function with the given SpanContext
+ // in scope.
+ String logWithSpanAndLog4jConfiguration(
+ String log4jPattern, SpanContext spanContext, Function<Logger, Void> loggingFunction) {
+ StringWriter output = new StringWriter();
+ StringLayout layout = PatternLayout.newBuilder().withPattern(log4jPattern).build();
+ Appender appender =
+ WriterAppender.newBuilder()
+ .setTarget(output)
+ .setLayout(layout)
+ .setName("TestAppender")
+ .build();
+ ((LoggerContext) LogManager.getContext(false)).updateLoggers();
+ appender.start();
+ logger.addAppender(appender);
+ logger.setLevel(Level.ALL);
+ try {
+ logWithSpan(spanContext, loggingFunction, logger);
+ return output.toString();
+ } finally {
+ logger.removeAppender(appender);
+ }
+ }
+
+ private static void logWithSpan(
+ SpanContext spanContext, Function<Logger, Void> loggingFunction, Logger logger) {
+ Scope scope = tracer.withSpan(new TestSpan(spanContext));
+ try {
+ loggingFunction.apply(logger);
+ } finally {
+ scope.close();
+ }
+ }
+}
diff --git a/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusLog4jLogCorrelationAllSpansTest.java b/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusLog4jLogCorrelationAllSpansTest.java
new file mode 100644
index 00000000..355c9b67
--- /dev/null
+++ b/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusLog4jLogCorrelationAllSpansTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.contrib.logcorrelation.log4j2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.common.Function;
+import io.opencensus.contrib.logcorrelation.log4j2.OpenCensusTraceContextDataInjector.SpanSelection;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.TraceOptions;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.Logger;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for Log4j log correlation with {@link
+ * OpenCensusTraceContextDataInjector#SPAN_SELECTION_PROPERTY_NAME} set to {@link
+ * SpanSelection#ALL_SPANS}.
+ */
+@RunWith(JUnit4.class)
+public final class OpenCensusLog4jLogCorrelationAllSpansTest
+ extends AbstractOpenCensusLog4jLogCorrelationTest {
+
+ @BeforeClass
+ public static void setUp() {
+ initializeLog4j(SpanSelection.ALL_SPANS);
+ }
+
+ @Test
+ public void addSampledSpanToLogEntryWithAllSpans() {
+ String log =
+ logWithSpanAndLog4jConfiguration(
+ TEST_PATTERN,
+ SpanContext.create(
+ TraceId.fromLowerBase16("b9718fe3d82d36fce0e6a1ada1c21db0"),
+ SpanId.fromLowerBase16("75159dde8c503fee"),
+ TraceOptions.builder().setIsSampled(true).build(),
+ EMPTY_TRACESTATE),
+ new Function<Logger, Void>() {
+ @Override
+ public Void apply(Logger logger) {
+ logger.warn("message #1");
+ return null;
+ }
+ });
+ assertThat(log)
+ .isEqualTo(
+ "traceId=b9718fe3d82d36fce0e6a1ada1c21db0 spanId=75159dde8c503fee "
+ + "sampled=true WARN - message #1");
+ }
+
+ @Test
+ public void addNonSampledSpanToLogEntryWithAllSpans() {
+ String log =
+ logWithSpanAndLog4jConfiguration(
+ TEST_PATTERN,
+ SpanContext.create(
+ TraceId.fromLowerBase16("cd7061dfa9d312cdcc42edab3feab51b"),
+ SpanId.fromLowerBase16("117d42d4c7acd066"),
+ TraceOptions.builder().setIsSampled(false).build(),
+ EMPTY_TRACESTATE),
+ new Function<Logger, Void>() {
+ @Override
+ public Void apply(Logger logger) {
+ logger.info("message #2");
+ return null;
+ }
+ });
+ assertThat(log)
+ .isEqualTo(
+ "traceId=cd7061dfa9d312cdcc42edab3feab51b spanId=117d42d4c7acd066 sampled=false INFO "
+ + "- message #2");
+ }
+
+ @Test
+ public void addBlankSpanToLogEntryWithAllSpans() {
+ String log =
+ logWithSpanAndLog4jConfiguration(
+ TEST_PATTERN,
+ SpanContext.INVALID,
+ new Function<Logger, Void>() {
+ @Override
+ public Void apply(Logger logger) {
+ logger.fatal("message #3");
+ return null;
+ }
+ });
+ assertThat(log)
+ .isEqualTo(
+ "traceId=00000000000000000000000000000000 spanId=0000000000000000 sampled=false FATAL "
+ + "- message #3");
+ }
+
+ @Test
+ public void preserveOtherKeyValuePairs() {
+ String log =
+ logWithSpanAndLog4jConfiguration(
+ "%X{opencensusTraceId} %X{myTestKey} %-5level - %msg",
+ SpanContext.create(
+ TraceId.fromLowerBase16("c95329bb6b7de41afbc51a231c128f97"),
+ SpanId.fromLowerBase16("bf22ea74d38eddad"),
+ TraceOptions.builder().setIsSampled(true).build(),
+ EMPTY_TRACESTATE),
+ new Function<Logger, Void>() {
+ @Override
+ public Void apply(Logger logger) {
+ String key = "myTestKey";
+ ThreadContext.put(key, "myTestValue");
+ try {
+ logger.error("message #4");
+ } finally {
+ ThreadContext.remove(key);
+ }
+ return null;
+ }
+ });
+ assertThat(log).isEqualTo("c95329bb6b7de41afbc51a231c128f97 myTestValue ERROR - message #4");
+ }
+
+ @Test
+ public void overwriteExistingTracingKey() {
+ String log =
+ logWithSpanAndLog4jConfiguration(
+ TEST_PATTERN,
+ SpanContext.create(
+ TraceId.fromLowerBase16("18e4ae44273a0c44e0c9ea4380792c66"),
+ SpanId.fromLowerBase16("199a7e16daa000a7"),
+ TraceOptions.builder().setIsSampled(true).build(),
+ EMPTY_TRACESTATE),
+ new Function<Logger, Void>() {
+ @Override
+ public Void apply(Logger logger) {
+ ThreadContext.put(
+ OpenCensusTraceContextDataInjector.TRACE_ID_CONTEXT_KEY, "existingTraceId");
+ try {
+ logger.error("message #5");
+ } finally {
+ ThreadContext.remove(OpenCensusTraceContextDataInjector.TRACE_ID_CONTEXT_KEY);
+ }
+ return null;
+ }
+ });
+ assertThat(log)
+ .isEqualTo(
+ "traceId=18e4ae44273a0c44e0c9ea4380792c66 spanId=199a7e16daa000a7 "
+ + "sampled=true ERROR - message #5");
+ }
+}
diff --git a/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusLog4jLogCorrelationNoSpansTest.java b/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusLog4jLogCorrelationNoSpansTest.java
new file mode 100644
index 00000000..1205924e
--- /dev/null
+++ b/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusLog4jLogCorrelationNoSpansTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.contrib.logcorrelation.log4j2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.common.Function;
+import io.opencensus.contrib.logcorrelation.log4j2.OpenCensusTraceContextDataInjector.SpanSelection;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.TraceOptions;
+import org.apache.logging.log4j.core.Logger;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for Log4j log correlation with {@link
+ * OpenCensusTraceContextDataInjector#SPAN_SELECTION_PROPERTY_NAME} set to {@link
+ * SpanSelection#NO_SPANS}.
+ */
+@RunWith(JUnit4.class)
+public final class OpenCensusLog4jLogCorrelationNoSpansTest
+ extends AbstractOpenCensusLog4jLogCorrelationTest {
+
+ @BeforeClass
+ public static void setUp() {
+ initializeLog4j(SpanSelection.NO_SPANS);
+ }
+
+ @Test
+ public void doNotAddSampledSpanToLogEntryWithNoSpans() {
+ String log =
+ logWithSpanAndLog4jConfiguration(
+ TEST_PATTERN,
+ SpanContext.create(
+ TraceId.fromLowerBase16("03d2ada98f6eb8330605a45a88c7e67d"),
+ SpanId.fromLowerBase16("ce5b1cf09fe58bcb"),
+ TraceOptions.builder().setIsSampled(true).build(),
+ EMPTY_TRACESTATE),
+ new Function<Logger, Void>() {
+ @Override
+ public Void apply(Logger logger) {
+ logger.trace("message #1");
+ return null;
+ }
+ });
+ assertThat(log).isEqualTo("traceId= spanId= sampled= TRACE - message #1");
+ }
+
+ @Test
+ public void doNotAddNonSampledSpanToLogEntryWithNoSpans() {
+ String log =
+ logWithSpanAndLog4jConfiguration(
+ TEST_PATTERN,
+ SpanContext.create(
+ TraceId.fromLowerBase16("09664283d189791de5218ffe3be88d54"),
+ SpanId.fromLowerBase16("a7203a50089a4029"),
+ TraceOptions.builder().setIsSampled(false).build(),
+ EMPTY_TRACESTATE),
+ new Function<Logger, Void>() {
+ @Override
+ public Void apply(Logger logger) {
+ logger.warn("message #2");
+ return null;
+ }
+ });
+ assertThat(log).isEqualTo("traceId= spanId= sampled= WARN - message #2");
+ }
+}
diff --git a/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusLog4jLogCorrelationSampledSpansTest.java b/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusLog4jLogCorrelationSampledSpansTest.java
new file mode 100644
index 00000000..bbce4135
--- /dev/null
+++ b/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusLog4jLogCorrelationSampledSpansTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.contrib.logcorrelation.log4j2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.common.Function;
+import io.opencensus.contrib.logcorrelation.log4j2.OpenCensusTraceContextDataInjector.SpanSelection;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.TraceOptions;
+import org.apache.logging.log4j.core.Logger;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for Log4j log correlation with {@link
+ * OpenCensusTraceContextDataInjector#SPAN_SELECTION_PROPERTY_NAME} set to {@link
+ * SpanSelection#SAMPLED_SPANS}.
+ */
+@RunWith(JUnit4.class)
+public final class OpenCensusLog4jLogCorrelationSampledSpansTest
+ extends AbstractOpenCensusLog4jLogCorrelationTest {
+
+ @BeforeClass
+ public static void setUp() {
+ initializeLog4j(SpanSelection.SAMPLED_SPANS);
+ }
+
+ @Test
+ public void addSampledSpanToLogEntryWithSampledSpans() {
+ String log =
+ logWithSpanAndLog4jConfiguration(
+ TEST_PATTERN,
+ SpanContext.create(
+ TraceId.fromLowerBase16("0af7a7bef890695f1c5e85a8e7290164"),
+ SpanId.fromLowerBase16("d3f07c467ec2fbb2"),
+ TraceOptions.builder().setIsSampled(true).build(),
+ EMPTY_TRACESTATE),
+ new Function<Logger, Void>() {
+ @Override
+ public Void apply(Logger logger) {
+ logger.error("message #1");
+ return null;
+ }
+ });
+ assertThat(log)
+ .isEqualTo(
+ "traceId=0af7a7bef890695f1c5e85a8e7290164 spanId=d3f07c467ec2fbb2 sampled=true ERROR "
+ + "- message #1");
+ }
+
+ @Test
+ public void doNotAddNonSampledSpanToLogEntryWithSampledSpans() {
+ String log =
+ logWithSpanAndLog4jConfiguration(
+ TEST_PATTERN,
+ SpanContext.create(
+ TraceId.fromLowerBase16("9e09b559ebb8f7f7ed7451aff68cf441"),
+ SpanId.fromLowerBase16("0fc9ef54c50a1816"),
+ TraceOptions.builder().setIsSampled(false).build(),
+ EMPTY_TRACESTATE),
+ new Function<Logger, Void>() {
+ @Override
+ public Void apply(Logger logger) {
+ logger.debug("message #2");
+ return null;
+ }
+ });
+ assertThat(log).isEqualTo("traceId= spanId= sampled= DEBUG - message #2");
+ }
+}
diff --git a/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusTraceContextDataInjectorTest.java b/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusTraceContextDataInjectorTest.java
new file mode 100644
index 00000000..3b704058
--- /dev/null
+++ b/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/OpenCensusTraceContextDataInjectorTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.contrib.logcorrelation.log4j2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Lists;
+import io.opencensus.common.Scope;
+import io.opencensus.contrib.logcorrelation.log4j2.OpenCensusTraceContextDataInjector.SpanSelection;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.TraceOptions;
+import io.opencensus.trace.Tracer;
+import io.opencensus.trace.Tracestate;
+import io.opencensus.trace.Tracing;
+import java.util.Collections;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.util.SortedArrayStringMap;
+import org.apache.logging.log4j.util.StringMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link OpenCensusTraceContextDataInjector}. */
+@RunWith(JUnit4.class)
+public final class OpenCensusTraceContextDataInjectorTest {
+ static final Tracestate EMPTY_TRACESTATE = Tracestate.builder().build();
+
+ private final Tracer tracer = Tracing.getTracer();
+
+ @Test
+ @SuppressWarnings("TruthConstantAsserts")
+ public void spanSelectionPropertyName() {
+ assertThat(OpenCensusTraceContextDataInjector.SPAN_SELECTION_PROPERTY_NAME)
+ .isEqualTo(OpenCensusTraceContextDataInjector.class.getName() + ".spanSelection");
+ }
+
+ @Test
+ public void traceIdKey() {
+ assertThat(OpenCensusTraceContextDataInjector.TRACE_ID_CONTEXT_KEY)
+ .isEqualTo("opencensusTraceId");
+ }
+
+ @Test
+ public void spanIdKey() {
+ assertThat(OpenCensusTraceContextDataInjector.SPAN_ID_CONTEXT_KEY)
+ .isEqualTo("opencensusSpanId");
+ }
+
+ @Test
+ public void traceSampledKey() {
+ assertThat(OpenCensusTraceContextDataInjector.TRACE_SAMPLED_CONTEXT_KEY)
+ .isEqualTo("opencensusTraceSampled");
+ }
+
+ @Test
+ public void spanSelectionDefaultIsAllSpans() {
+ assertThat(new OpenCensusTraceContextDataInjector().getSpanSelection())
+ .isEqualTo(SpanSelection.ALL_SPANS);
+ }
+
+ @Test
+ public void setSpanSelectionWithSystemProperty() {
+ try {
+ System.setProperty(
+ OpenCensusTraceContextDataInjector.SPAN_SELECTION_PROPERTY_NAME, "NO_SPANS");
+ assertThat(new OpenCensusTraceContextDataInjector().getSpanSelection())
+ .isEqualTo(SpanSelection.NO_SPANS);
+ } finally {
+ System.clearProperty(OpenCensusTraceContextDataInjector.SPAN_SELECTION_PROPERTY_NAME);
+ }
+ }
+
+ @Test
+ public void useDefaultValueForInvalidSpanSelection() {
+ try {
+ System.setProperty(
+ OpenCensusTraceContextDataInjector.SPAN_SELECTION_PROPERTY_NAME,
+ "INVALID_SPAN_SELECTION");
+ assertThat(new OpenCensusTraceContextDataInjector().getSpanSelection())
+ .isEqualTo(SpanSelection.ALL_SPANS);
+ } finally {
+ System.clearProperty(OpenCensusTraceContextDataInjector.SPAN_SELECTION_PROPERTY_NAME);
+ }
+ }
+
+ @Test
+ public void insertConfigurationProperties() {
+ assertThat(
+ new OpenCensusTraceContextDataInjector(SpanSelection.ALL_SPANS)
+ .injectContextData(
+ Lists.newArrayList(
+ Property.createProperty("property1", "value1"),
+ Property.createProperty("property2", "value2")),
+ new SortedArrayStringMap())
+ .toMap())
+ .containsExactly(
+ "property1",
+ "value1",
+ "property2",
+ "value2",
+ "opencensusTraceId",
+ "00000000000000000000000000000000",
+ "opencensusSpanId",
+ "0000000000000000",
+ "opencensusTraceSampled",
+ "false");
+ }
+
+ @Test
+ public void handleEmptyConfigurationProperties() {
+ assertContainsOnlyDefaultTracingEntries(
+ new OpenCensusTraceContextDataInjector(SpanSelection.ALL_SPANS)
+ .injectContextData(Collections.<Property>emptyList(), new SortedArrayStringMap()));
+ }
+
+ @Test
+ public void handleNullConfigurationProperties() {
+ assertContainsOnlyDefaultTracingEntries(
+ new OpenCensusTraceContextDataInjector(SpanSelection.ALL_SPANS)
+ .injectContextData(null, new SortedArrayStringMap()));
+ }
+
+ private static void assertContainsOnlyDefaultTracingEntries(StringMap stringMap) {
+ assertThat(stringMap.toMap())
+ .containsExactly(
+ "opencensusTraceId",
+ "00000000000000000000000000000000",
+ "opencensusSpanId",
+ "0000000000000000",
+ "opencensusTraceSampled",
+ "false");
+ }
+
+ @Test
+ public void rawContextDataWithTracingData() {
+ OpenCensusTraceContextDataInjector plugin =
+ new OpenCensusTraceContextDataInjector(SpanSelection.ALL_SPANS);
+ SpanContext spanContext =
+ SpanContext.create(
+ TraceId.fromLowerBase16("e17944156660f55b8cae5ce3f45d4a40"),
+ SpanId.fromLowerBase16("fc3d2ba0d283b66a"),
+ TraceOptions.builder().setIsSampled(true).build(),
+ EMPTY_TRACESTATE);
+ Scope scope = tracer.withSpan(new TestSpan(spanContext));
+ try {
+ String key = "myTestKey";
+ ThreadContext.put(key, "myTestValue");
+ try {
+ assertThat(plugin.rawContextData().toMap())
+ .containsExactly(
+ "myTestKey",
+ "myTestValue",
+ "opencensusTraceId",
+ "e17944156660f55b8cae5ce3f45d4a40",
+ "opencensusSpanId",
+ "fc3d2ba0d283b66a",
+ "opencensusTraceSampled",
+ "true");
+ } finally {
+ ThreadContext.remove(key);
+ }
+ } finally {
+ scope.close();
+ }
+ }
+
+ @Test
+ public void rawContextDataWithoutTracingData() {
+ OpenCensusTraceContextDataInjector plugin =
+ new OpenCensusTraceContextDataInjector(SpanSelection.NO_SPANS);
+ SpanContext spanContext =
+ SpanContext.create(
+ TraceId.fromLowerBase16("ea236000f6d387fe7c06c5a6d6458b53"),
+ SpanId.fromLowerBase16("f3b39dbbadb73074"),
+ TraceOptions.builder().setIsSampled(true).build(),
+ EMPTY_TRACESTATE);
+ Scope scope = tracer.withSpan(new TestSpan(spanContext));
+ try {
+ String key = "myTestKey";
+ ThreadContext.put(key, "myTestValue");
+ try {
+ assertThat(plugin.rawContextData().toMap()).containsExactly("myTestKey", "myTestValue");
+ } finally {
+ ThreadContext.remove(key);
+ }
+ } finally {
+ scope.close();
+ }
+ }
+}
diff --git a/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/TestSpan.java b/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/TestSpan.java
new file mode 100644
index 00000000..7af46064
--- /dev/null
+++ b/contrib/log_correlation/log4j2/src/test/java/io/opencensus/contrib/logcorrelation/log4j2/TestSpan.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.opencensus.contrib.logcorrelation.log4j2;
+
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.AttributeValue;
+import io.opencensus.trace.EndSpanOptions;
+import io.opencensus.trace.Link;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanContext;
+import java.util.EnumSet;
+import java.util.Map;
+
+// Simple test Span that holds a SpanContext. The tests cannot use Span directly, since it is
+// abstract.
+final class TestSpan extends Span {
+ TestSpan(SpanContext context) {
+ super(context, EnumSet.of(Options.RECORD_EVENTS));
+ }
+
+ @Override
+ public void end(EndSpanOptions options) {}
+
+ @Override
+ public void addLink(Link link) {}
+
+ @Override
+ public void addAnnotation(Annotation annotation) {}
+
+ @Override
+ public void addAnnotation(String description, Map<String, AttributeValue> attributes) {}
+}
diff --git a/settings.gradle b/settings.gradle
index 75060d26..7c224edf 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -21,6 +21,7 @@ include ":opencensus-contrib-exemplar-util"
include ":opencensus-contrib-grpc-metrics"
include ":opencensus-contrib-grpc-util"
include ":opencensus-contrib-http-util"
+include ":opencensus-contrib-log-correlation-log4j2"
include ":opencensus-contrib-log-correlation-stackdriver"
include ":opencensus-contrib-monitored-resource-util"
include ":opencensus-contrib-spring"
@@ -39,6 +40,8 @@ project(':opencensus-contrib-exemplar-util').projectDir = "$rootDir/contrib/exem
project(':opencensus-contrib-grpc-metrics').projectDir = "$rootDir/contrib/grpc_metrics" as File
project(':opencensus-contrib-grpc-util').projectDir = "$rootDir/contrib/grpc_util" as File
project(':opencensus-contrib-http-util').projectDir = "$rootDir/contrib/http_util" as File
+project(':opencensus-contrib-log-correlation-log4j2').projectDir =
+ "$rootDir/contrib/log_correlation/log4j2" as File
project(':opencensus-contrib-log-correlation-stackdriver').projectDir =
"$rootDir/contrib/log_correlation/stackdriver" as File
project(':opencensus-contrib-monitored-resource-util').projectDir =