diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 72ecd9a3a..f9e0f4534 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -420,6 +420,11 @@ There are two properties to achieve this. With the `spring.sleuth.baggage-keys`, you set keys that get prefixed with `baggage-` for HTTP calls and `baggage_` for messaging. You can also use the `spring.sleuth.propagation-keys` property to pass a list of prefixed keys that are whitelisted without any prefix. +In order to automatically set the baggage values to Slf4j's MDC, you have to set +the `spring.sleuth.log.slf4j.whitelisted-mdc-keys` property with a list of whitelisted +baggage keys. E.g. `spring.sleuth.log.slf4j.whitelisted-mdc-keys=foo` will set the value of the +`foo` baggage into MDC. + ==== Extracting a Propagated Context The `TraceContext.Extractor` reads trace identifiers and sampling status from an incoming request or message. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java index 2f8a31c51..b731adcf6 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java @@ -22,6 +22,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -47,8 +48,9 @@ public class SleuthLogAutoConfiguration { @Bean @ConditionalOnProperty(value = "spring.sleuth.log.slf4j.enabled", matchIfMissing = true) - public CurrentTraceContext.ScopeDecorator slf4jSpanDecorator() { - return new Slf4jScopeDecorator(); + public CurrentTraceContext.ScopeDecorator slf4jSpanDecorator(SleuthProperties sleuthProperties, + SleuthSlf4jProperties sleuthSlf4jProperties) { + return new Slf4jScopeDecorator(sleuthProperties, sleuthSlf4jProperties); } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java index 3b7eec873..6eb114c9c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java @@ -16,6 +16,9 @@ package org.springframework.cloud.sleuth.log; +import java.util.ArrayList; +import java.util.List; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -28,10 +31,15 @@ import org.springframework.boot.context.properties.ConfigurationProperties; public class SleuthSlf4jProperties { /** - * Enable a {@link Slf4jCurrentTraceContext} that prints tracing information in the logs. + * Enable a {@link Slf4jScopeDecorator} that prints tracing information in the logs. */ private boolean enabled = true; + /** + * A list of keys to be put from baggage to MDC + */ + private List whitelistedMdcKeys = new ArrayList<>(); + public boolean isEnabled() { return this.enabled; } @@ -39,4 +47,12 @@ public class SleuthSlf4jProperties { public void setEnabled(boolean enabled) { this.enabled = enabled; } + + public List getWhitelistedMdcKeys() { + return this.whitelistedMdcKeys; + } + + public void setWhitelistedMdcKeys(List whitelistedMdcKeys) { + this.whitelistedMdcKeys = whitelistedMdcKeys; + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jScopeDecorator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jScopeDecorator.java index 80ed3b0e1..3f1ce318e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jScopeDecorator.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jScopeDecorator.java @@ -16,13 +16,21 @@ package org.springframework.cloud.sleuth.log; +import java.util.AbstractMap; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import brave.internal.HexCodec; import brave.internal.Nullable; import brave.propagation.CurrentTraceContext; +import brave.propagation.ExtraFieldPropagation; import brave.propagation.TraceContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; +import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; +import org.springframework.util.StringUtils; /** * Adds {@linkplain MDC} properties "traceId", "parentId", "spanId" and "spanExportable" when a {@link @@ -44,6 +52,14 @@ final class Slf4jScopeDecorator implements CurrentTraceContext.ScopeDecorator { private static final Logger log = LoggerFactory.getLogger(Slf4jScopeDecorator.class); + private final SleuthProperties sleuthProperties; + private final SleuthSlf4jProperties sleuthSlf4jProperties; + + Slf4jScopeDecorator(SleuthProperties sleuthProperties, SleuthSlf4jProperties sleuthSlf4jProperties) { + this.sleuthProperties = sleuthProperties; + this.sleuthSlf4jProperties = sleuthSlf4jProperties; + } + @Override public CurrentTraceContext.Scope decorateScope(TraceContext currentSpan, CurrentTraceContext.Scope scope) { final String previousTraceId = MDC.get("traceId"); @@ -54,6 +70,10 @@ final class Slf4jScopeDecorator implements CurrentTraceContext.ScopeDecorator { final String legacyPreviousParentId = MDC.get(LEGACY_PARENT_ID_NAME); final String legacyPreviousSpanId = MDC.get(LEGACY_SPAN_ID_NAME); final String legacySpanExportable = MDC.get(LEGACY_EXPORTABLE_NAME); + final List> previousMdc = + whitelistedBaggageKeys(currentSpan) + .map(s -> new AbstractMap.SimpleEntry<>(s, MDC.get(s))) + .collect(Collectors.toList()); if (currentSpan != null) { String traceIdString = currentSpan.traceIdString(); @@ -76,6 +96,8 @@ final class Slf4jScopeDecorator implements CurrentTraceContext.ScopeDecorator { log.trace("With parent: {}", currentSpan.parentId()); } } + whitelistedBaggageKeys(currentSpan) + .forEach(s -> MDC.put(s, ExtraFieldPropagation.get(currentSpan, s))); } else { MDC.remove("traceId"); @@ -86,6 +108,7 @@ final class Slf4jScopeDecorator implements CurrentTraceContext.ScopeDecorator { MDC.remove(LEGACY_PARENT_ID_NAME); MDC.remove(LEGACY_SPAN_ID_NAME); MDC.remove(LEGACY_EXPORTABLE_NAME); + whitelistedBaggageKeys(currentSpan).forEach(MDC::remove); } class ThreadContextCurrentTraceContextScope implements CurrentTraceContext.Scope { @@ -100,11 +123,18 @@ final class Slf4jScopeDecorator implements CurrentTraceContext.ScopeDecorator { replace(LEGACY_PARENT_ID_NAME, legacyPreviousParentId); replace(LEGACY_SPAN_ID_NAME, legacyPreviousSpanId); replace(LEGACY_EXPORTABLE_NAME, legacySpanExportable); + previousMdc.forEach(e -> replace(e.getKey(), e.getValue())); } } return new ThreadContextCurrentTraceContextScope(); } + private Stream whitelistedBaggageKeys(TraceContext context) { + return this.sleuthProperties.getBaggageKeys().stream() + .filter(s -> this.sleuthSlf4jProperties.getWhitelistedMdcKeys().contains(s) && + context != null && StringUtils.hasText(ExtraFieldPropagation.get(context, s))); + } + private void log(String text, TraceContext span) { if (span == null) { return; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java index 30390b1d5..515bf67f0 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java @@ -17,43 +17,53 @@ package org.springframework.cloud.sleuth.log; import brave.Span; -import brave.Tracing; +import brave.Tracer; import brave.propagation.CurrentTraceContext.Scope; -import brave.propagation.StrictCurrentTraceContext; +import brave.propagation.ExtraFieldPropagation; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.slf4j.MDC; -import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; /** * @author Marcin Grzejszczak */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { + "spring.sleuth.baggage-keys=my-baggage", + "spring.sleuth.log.slf4j.whitelisted-mdc-keys=my-baggage" + }) +@SpringBootConfiguration +@EnableAutoConfiguration public class Slf4JSpanLoggerTest { - ArrayListSpanReporter reporter = new ArrayListSpanReporter(); - Tracing tracing = Tracing.newBuilder() - .currentTraceContext(new StrictCurrentTraceContext()) - .spanReporter(this.reporter) - .build(); - - Span span = this.tracing.tracer().nextSpan().name("span").start(); - Slf4jScopeDecorator slf4jScopeDecorator = new Slf4jScopeDecorator(); + @Autowired Tracer tracer; + @Autowired Slf4jScopeDecorator slf4jScopeDecorator; + Span span; + @Before @After public void setup() { MDC.clear(); + this.span = this.tracer.nextSpan().name("span").start(); } @Test public void should_set_entries_to_mdc_from_span() throws Exception { Scope scope = this.slf4jScopeDecorator.decorateScope(this.span.context(), () -> { }); - assertThat(MDC.get("X-B3-TraceId")).isEqualTo(span.context().traceIdString()); - assertThat(MDC.get("traceId")).isEqualTo(span.context().traceIdString()); + assertThat(MDC.get("X-B3-TraceId")).isEqualTo(this.span.context().traceIdString()); + assertThat(MDC.get("traceId")).isEqualTo(this.span.context().traceIdString()); scope.close(); @@ -61,6 +71,18 @@ public class Slf4JSpanLoggerTest { assertThat(MDC.get("traceId")).isNullOrEmpty(); } + @Test + public void should_set_entries_to_mdc_from_span_with_baggage() throws Exception { + ExtraFieldPropagation.set(this.span.context(), "my-baggage", "my-value"); + Scope scope = this.slf4jScopeDecorator.decorateScope(this.span.context(), () -> { }); + + assertThat(MDC.get("my-baggage")).isEqualTo("my-value"); + + scope.close(); + + assertThat(MDC.get("my-baggage")).isNullOrEmpty(); + } + @Test public void should_remove_entries_from_mdc_from_null_span() throws Exception { MDC.put("X-B3-TraceId", "A");