aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--all/build.gradle2
-rw-r--r--build.gradle6
-rw-r--r--buildscripts/import-control.xml4
-rw-r--r--contrib/log_correlation/log4j/README.md3
-rw-r--r--contrib/log_correlation/log4j/build.gradle26
-rw-r--r--contrib/log_correlation/log4j/src/main/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusTraceContextDataInjector.java222
-rw-r--r--contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/AbstractOpenCensusLog4jLogCorrelationTest.java122
-rw-r--r--contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusLog4jLogCorrelationAllSpansTest.java139
-rw-r--r--contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusLog4jLogCorrelationNoSpansTest.java86
-rw-r--r--contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusLog4jLogCorrelationSampledSpansTest.java89
-rw-r--r--contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusTraceContextDataInjectorTest.java85
-rw-r--r--settings.gradle3
12 files changed, 787 insertions, 0 deletions
diff --git a/all/build.gradle b/all/build.gradle
index 640eb81b..e0bbab12 100644
--- a/all/build.gradle
+++ b/all/build.gradle
@@ -18,6 +18,7 @@ def subprojects = [
project(':opencensus-contrib-grpc-util'),
project(':opencensus-contrib-grpc-metrics'),
project(':opencensus-contrib-http-util'),
+ project(':opencensus-contrib-log-correlation-log4j'),
project(':opencensus-contrib-log-correlation-stackdriver'),
project(':opencensus-contrib-monitored-resource-util'),
project(':opencensus-contrib-spring'),
@@ -41,6 +42,7 @@ def subprojects_javadoc = [
project(':opencensus-contrib-grpc-util'),
project(':opencensus-contrib-grpc-metrics'),
project(':opencensus-contrib-http-util'),
+ project(':opencensus-contrib-log-correlation-log4j'),
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 30865c3a..0e08ed37 100644
--- a/build.gradle
+++ b/build.gradle
@@ -154,6 +154,7 @@ subprojects {
googleAuthVersion = '0.10.0'
googleCloudBetaVersion = '0.58.0-beta'
googleCloudGaVersion = '1.40.0'
+ log4jVersion = '2.11.1'
signalfxVersion = '0.0.39'
springVersion = '4.3.12.RELEASE'
prometheusVersion = '0.4.0'
@@ -174,6 +175,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}",
+ log4j: "org.apache.logging.log4j:log4j-core:${log4jVersion}",
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}",
@@ -371,6 +373,10 @@ subprojects {
'opencensus-contrib-grpc-metrics',
'opencensus-contrib-grpc-util',
'opencensus-contrib-http-util',
+
+ // TODO(sebright): Uncomment opencensus-contrib-log-correlation-log4j when it is complete.
+ // 'opencensus-contrib-log-correlation-log4j',
+
'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 f0574df7..7375eb5f 100644
--- a/buildscripts/import-control.xml
+++ b/buildscripts/import-control.xml
@@ -97,6 +97,10 @@ General guidelines on imports:
<allow pkg="io.opencensus.tags"/>
<allow pkg="io.opencensus.trace"/>
</subpackage>
+ <subpackage name="logcorrelation.log4j">
+ <allow pkg="io.opencensus.trace"/>
+ <allow pkg="org.apache.logging.log4j"/>
+ </subpackage>
<subpackage name="logcorrelation.stackdriver">
<allow pkg="com.google.cloud"/>
<allow pkg="io.opencensus.trace"/>
diff --git a/contrib/log_correlation/log4j/README.md b/contrib/log_correlation/log4j/README.md
new file mode 100644
index 00000000..f6b812e4
--- /dev/null
+++ b/contrib/log_correlation/log4j/README.md
@@ -0,0 +1,3 @@
+# OpenCensus Log4j Log Correlation
+
+TODO(sebright): Add more documentation.
diff --git a/contrib/log_correlation/log4j/build.gradle b/contrib/log_correlation/log4j/build.gradle
new file mode 100644
index 00000000..a832e146
--- /dev/null
+++ b/contrib/log_correlation/log4j/build.gradle
@@ -0,0 +1,26 @@
+description = 'OpenCensus Log4j Log Correlation'
+
+apply plugin: 'java'
+
+dependencies {
+ compile project(':opencensus-api'),
+ libraries.log4j
+
+ 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.log4j.OpenCensusTraceContextDataInjector'
+
+ // Each test class should run in a separate JVM. See the comment in
+ // AbstractOpenCensusLog4jLogCorrelationTest.
+ forkEvery = 1
+}
diff --git a/contrib/log_correlation/log4j/src/main/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusTraceContextDataInjector.java b/contrib/log_correlation/log4j/src/main/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusTraceContextDataInjector.java
new file mode 100644
index 00000000..2d637732
--- /dev/null
+++ b/contrib/log_correlation/log4j/src/main/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusTraceContextDataInjector.java
@@ -0,0 +1,222 @@
+/*
+ * 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.log4j;
+
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.unsafe.ContextUtils;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.logging.log4j.ThreadContext;
+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.core.impl.ThreadContextDataInjector;
+import org.apache.logging.log4j.util.SortedArrayStringMap;
+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.log4j."
+ + "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());
+ }
+
+ private 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;
+ }
+ }
+
+ // The implementation of this method is based on the example in the Javadocs for
+ // ContextDataInjector.injectContextData.
+ @Override
+ public StringMap injectContextData(@Nullable List<Property> properties, StringMap reusable) {
+ if (properties == null || properties.isEmpty()) {
+ return rawContextData();
+ }
+ // Context data has precedence over configuration properties.
+ ThreadContextDataInjector.copyProperties(properties, reusable);
+ reusable.putAll(rawContextData());
+ return reusable;
+ }
+
+ // This method avoids getting the current span when the feature is disabled, for efficiency.
+ @Override
+ public StringMap rawContextData() {
+ switch (spanSelection) {
+ case NO_SPANS:
+ return getContextData();
+ case SAMPLED_SPANS:
+ SpanContext spanContext = getCurrentSpanContext();
+ if (spanContext.getTraceOptions().isSampled()) {
+ return getContextAndTracingData(spanContext);
+ } else {
+ return getContextData();
+ }
+ case ALL_SPANS:
+ return getContextAndTracingData(getCurrentSpanContext());
+ }
+ throw new AssertionError("Unknown spanSelection: " + spanSelection);
+ }
+
+ private static SpanContext getCurrentSpanContext() {
+ Span span = ContextUtils.CONTEXT_SPAN_KEY.get();
+ return span == null ? SpanContext.INVALID : span.getContext();
+ }
+
+ // TODO(sebright): Improve the implementation of this method, including handling null.
+ private static StringMap getContextData() {
+ return ThreadContext.getThreadContextMap().getReadOnlyContextData();
+ }
+
+ // TODO(sebright): Improve the implementation of this method, including handling null.
+ private static StringMap getContextAndTracingData(SpanContext spanContext) {
+ Map<String, String> map = ThreadContext.getThreadContextMap().getCopy();
+ map.put(TRACE_ID_CONTEXT_KEY, spanContext.getTraceId().toLowerBase16());
+ map.put(SPAN_ID_CONTEXT_KEY, spanContext.getSpanId().toLowerBase16());
+ map.put(
+ TRACE_SAMPLED_CONTEXT_KEY, spanContext.getTraceOptions().isSampled() ? "true" : "false");
+ return new SortedArrayStringMap(map);
+ }
+}
diff --git a/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/AbstractOpenCensusLog4jLogCorrelationTest.java b/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/AbstractOpenCensusLog4jLogCorrelationTest.java
new file mode 100644
index 00000000..104e7012
--- /dev/null
+++ b/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/AbstractOpenCensusLog4jLogCorrelationTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.log4j;
+
+import io.opencensus.common.Function;
+import io.opencensus.common.Scope;
+import io.opencensus.contrib.logcorrelation.log4j.OpenCensusTraceContextDataInjector.SpanSelection;
+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 io.opencensus.trace.Tracer;
+import io.opencensus.trace.Tracestate;
+import io.opencensus.trace.Tracing;
+import java.io.StringWriter;
+import java.util.EnumSet;
+import java.util.Map;
+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();
+ }
+ }
+
+ private static 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/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusLog4jLogCorrelationAllSpansTest.java b/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusLog4jLogCorrelationAllSpansTest.java
new file mode 100644
index 00000000..7b992e45
--- /dev/null
+++ b/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusLog4jLogCorrelationAllSpansTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.log4j;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.common.Function;
+import io.opencensus.contrib.logcorrelation.log4j.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}.
+ */
+// TODO(sebright): Add a test with non-empty configuration properties.
+@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{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("myTestValue ERROR - message #4");
+ }
+}
diff --git a/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusLog4jLogCorrelationNoSpansTest.java b/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusLog4jLogCorrelationNoSpansTest.java
new file mode 100644
index 00000000..9e37cb27
--- /dev/null
+++ b/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/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.log4j;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.common.Function;
+import io.opencensus.contrib.logcorrelation.log4j.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/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusLog4jLogCorrelationSampledSpansTest.java b/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusLog4jLogCorrelationSampledSpansTest.java
new file mode 100644
index 00000000..ae3ec4e9
--- /dev/null
+++ b/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/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.log4j;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.common.Function;
+import io.opencensus.contrib.logcorrelation.log4j.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/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusTraceContextDataInjectorTest.java b/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusTraceContextDataInjectorTest.java
new file mode 100644
index 00000000..65c37f1f
--- /dev/null
+++ b/contrib/log_correlation/log4j/src/test/java/io/opencensus/contrib/logcorrelation/log4j/OpenCensusTraceContextDataInjectorTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.log4j;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.contrib.logcorrelation.log4j.OpenCensusTraceContextDataInjector.SpanSelection;
+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 {
+
+ @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);
+ }
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index fccae387..f1688b60 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -19,6 +19,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-log4j"
include ":opencensus-contrib-log-correlation-stackdriver"
include ":opencensus-contrib-monitored-resource-util"
include ":opencensus-contrib-spring"
@@ -35,6 +36,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-log4j').projectDir =
+ "$rootDir/contrib/log_correlation/log4j" as File
project(':opencensus-contrib-log-correlation-stackdriver').projectDir =
"$rootDir/contrib/log_correlation/stackdriver" as File
project(':opencensus-contrib-monitored-resource-util').projectDir =