diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java index fc0ad3678..2244c40e8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java @@ -23,11 +23,11 @@ import brave.CurrentSpanCustomizer; import brave.ErrorParser; import brave.Tracer; import brave.Tracing; -import brave.context.log4j2.ThreadContextCurrentTraceContext; import brave.propagation.B3Propagation; import brave.propagation.CurrentTraceContext; import brave.propagation.ExtraFieldPropagation; import brave.propagation.Propagation; +import brave.propagation.ThreadLocalCurrentTraceContext; import brave.sampler.Sampler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -58,6 +58,7 @@ public class TraceAutoConfiguration { public static final String TRACER_BEAN_NAME = "tracer"; @Autowired(required = false) List spanAdjusters = new ArrayList<>(); + @Autowired(required = false) List scopeDecorators = new ArrayList<>(); @Bean @ConditionalOnMissingBean @@ -132,10 +133,18 @@ public class TraceAutoConfiguration { return factoryBuilder.build(); } + @Bean + CurrentTraceContext currentTraceContext(CurrentTraceContext.Builder builder) { + for (CurrentTraceContext.ScopeDecorator scopeDecorator : this.scopeDecorators) { + builder.addScopeDecorator(scopeDecorator); + } + return builder.build(); + } + @Bean @ConditionalOnMissingBean - CurrentTraceContext sleuthCurrentTraceContext() { - return ThreadContextCurrentTraceContext.create(); + CurrentTraceContext.Builder sleuthCurrentTraceContextBuilder() { + return ThreadLocalCurrentTraceContext.newBuilder(); } @Bean 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 b86d16631..2f8a31c51 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 @@ -18,12 +18,8 @@ package org.springframework.cloud.sleuth.log; import brave.propagation.CurrentTraceContext; import org.slf4j.MDC; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; @@ -51,32 +47,8 @@ public class SleuthLogAutoConfiguration { @Bean @ConditionalOnProperty(value = "spring.sleuth.log.slf4j.enabled", matchIfMissing = true) - @ConditionalOnMissingBean - public CurrentTraceContext slf4jSpanLogger() { - return Slf4jCurrentTraceContext.create(); - } - - @Bean - @ConditionalOnProperty(value = "spring.sleuth.log.slf4j.enabled", matchIfMissing = true) - @ConditionalOnBean(CurrentTraceContext.class) - public static BeanPostProcessor slf4jSpanLoggerBPP() { - return new Slf4jBeanPostProcessor(); - } - - static class Slf4jBeanPostProcessor implements BeanPostProcessor { - - @Override public Object postProcessBeforeInitialization(Object bean, - String beanName) throws BeansException { - return bean; - } - - @Override public Object postProcessAfterInitialization(Object bean, - String beanName) throws BeansException { - if (bean instanceof CurrentTraceContext && !(bean instanceof Slf4jCurrentTraceContext)) { - return Slf4jCurrentTraceContext.create((CurrentTraceContext) bean); - } - return bean; - } + public CurrentTraceContext.ScopeDecorator slf4jSpanDecorator() { + return new Slf4jScopeDecorator(); } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jCurrentTraceContext.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jCurrentTraceContext.java index aeb0000c2..bbc70dba8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jCurrentTraceContext.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jCurrentTraceContext.java @@ -30,10 +30,16 @@ import org.slf4j.MDC; * Supports backward compatibility of MDC entries by adding legacy "X-B3" entries to MDC context * "X-B3-TraceId", "X-B3-ParentSpanId", "X-B3-SpanId" and "X-B3-Sampled" * + * Due to the migration to {@link brave.propagation.CurrentTraceContext.ScopeDecorator} approach, + * we are making the default implementation package scope since you can register your + * own implementation of the Scope Decorator. + * * @author Marcin Grzejszczak * * @since 2.0.0 + * @deprecated {@link Slf4jScopeDecorator} will be used */ +@Deprecated public final class Slf4jCurrentTraceContext extends CurrentTraceContext { // Backward compatibility for all logging patterns 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 new file mode 100644 index 000000000..80ed3b0e1 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jScopeDecorator.java @@ -0,0 +1,125 @@ +/* + * Copyright 2013-2018 the original author or 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 org.springframework.cloud.sleuth.log; + +import brave.internal.HexCodec; +import brave.internal.Nullable; +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +/** + * Adds {@linkplain MDC} properties "traceId", "parentId", "spanId" and "spanExportable" when a {@link + * brave.Tracer#currentSpan() span is current}. These can be used in log correlation. + * Supports backward compatibility of MDC entries by adding legacy "X-B3" entries to MDC context + * "X-B3-TraceId", "X-B3-ParentSpanId", "X-B3-SpanId" and "X-B3-Sampled" + * + * @author Marcin Grzejszczak + * + * @since 2.1.0 + */ +final class Slf4jScopeDecorator implements CurrentTraceContext.ScopeDecorator { + + // Backward compatibility for all logging patterns + private static final String LEGACY_EXPORTABLE_NAME = "X-Span-Export"; + private static final String LEGACY_PARENT_ID_NAME = "X-B3-ParentSpanId"; + private static final String LEGACY_TRACE_ID_NAME = "X-B3-TraceId"; + private static final String LEGACY_SPAN_ID_NAME = "X-B3-SpanId"; + + private static final Logger log = LoggerFactory.getLogger(Slf4jScopeDecorator.class); + + @Override public CurrentTraceContext.Scope decorateScope(TraceContext currentSpan, + CurrentTraceContext.Scope scope) { + final String previousTraceId = MDC.get("traceId"); + final String previousParentId = MDC.get("parentId"); + final String previousSpanId = MDC.get("spanId"); + final String spanExportable = MDC.get("spanExportable"); + final String legacyPreviousTraceId = MDC.get(LEGACY_TRACE_ID_NAME); + 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); + + if (currentSpan != null) { + String traceIdString = currentSpan.traceIdString(); + MDC.put("traceId", traceIdString); + MDC.put(LEGACY_TRACE_ID_NAME, traceIdString); + String parentId = currentSpan.parentId() != null ? + HexCodec.toLowerHex(currentSpan.parentId()) : + null; + replace("parentId", parentId); + replace(LEGACY_PARENT_ID_NAME, parentId); + String spanId = HexCodec.toLowerHex(currentSpan.spanId()); + MDC.put("spanId", spanId); + MDC.put(LEGACY_SPAN_ID_NAME, spanId); + String sampled = String.valueOf(currentSpan.sampled()); + MDC.put("spanExportable", sampled); + MDC.put(LEGACY_EXPORTABLE_NAME, sampled); + log("Starting scope for span: {}", currentSpan); + if (currentSpan.parentId() != null) { + if (log.isTraceEnabled()) { + log.trace("With parent: {}", currentSpan.parentId()); + } + } + } + else { + MDC.remove("traceId"); + MDC.remove("parentId"); + MDC.remove("spanId"); + MDC.remove("spanExportable"); + MDC.remove(LEGACY_TRACE_ID_NAME); + MDC.remove(LEGACY_PARENT_ID_NAME); + MDC.remove(LEGACY_SPAN_ID_NAME); + MDC.remove(LEGACY_EXPORTABLE_NAME); + } + + class ThreadContextCurrentTraceContextScope implements CurrentTraceContext.Scope { + @Override public void close() { + log("Closing scope for span: {}", currentSpan); + scope.close(); + replace("traceId", previousTraceId); + replace("parentId", previousParentId); + replace("spanId", previousSpanId); + replace("spanExportable", spanExportable); + replace(LEGACY_TRACE_ID_NAME, legacyPreviousTraceId); + replace(LEGACY_PARENT_ID_NAME, legacyPreviousParentId); + replace(LEGACY_SPAN_ID_NAME, legacyPreviousSpanId); + replace(LEGACY_EXPORTABLE_NAME, legacySpanExportable); + } + } + return new ThreadContextCurrentTraceContextScope(); + } + + private void log(String text, TraceContext span) { + if (span == null) { + return; + } + if (log.isTraceEnabled()) { + log.trace(text, span); + } + } + + static void replace(String key, @Nullable String value) { + if (value != null) { + MDC.put(key, value); + } + else { + MDC.remove(key); + } + } +} \ No newline at end of file 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 ae76e5490..30390b1d5 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 @@ -40,8 +40,7 @@ public class Slf4JSpanLoggerTest { .build(); Span span = this.tracing.tracer().nextSpan().name("span").start(); - Slf4jCurrentTraceContext slf4jCurrentTraceContext = - new Slf4jCurrentTraceContext(new StrictCurrentTraceContext()); + Slf4jScopeDecorator slf4jScopeDecorator = new Slf4jScopeDecorator(); @Before @After @@ -51,7 +50,7 @@ public class Slf4JSpanLoggerTest { @Test public void should_set_entries_to_mdc_from_span() throws Exception { - Scope scope = this.slf4jCurrentTraceContext.newScope(this.span.context()); + 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()); @@ -67,8 +66,7 @@ public class Slf4JSpanLoggerTest { MDC.put("X-B3-TraceId", "A"); MDC.put("traceId", "A"); - Scope scope = this.slf4jCurrentTraceContext - .newScope(null); + Scope scope = this.slf4jScopeDecorator.decorateScope(null, () -> { }); assertThat(MDC.get("X-B3-TraceId")).isNullOrEmpty(); assertThat(MDC.get("traceId")).isNullOrEmpty();