diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc index 72c7cb0d6fa..26d90052026 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc @@ -75,7 +75,14 @@ If you are using Micrometer Tracing, Spring Boot will include correlation IDs in The default correlation ID is built from `traceId` and `spanId` https://logback.qos.ch/manual/mdc.html[MDC] values. For example, if Micrometer Tracing has added an MDC `traceId` of `803B448A0489F84084905D3093480352` and an MDC `spanId` of `3425F23BB2432450` the log output will include the correlation ID `[803B448A0489F84084905D3093480352-3425F23BB2432450]`. +The `traceId` and `spanId` MDC key names can be customized using the configprop:management.tracing.mdc.trace-id-key[] and configprop:management.tracing.mdc.span-id-key[] properties. +The default correlation ID format follows those keys automatically, padding them to 32 and 16 characters respectively, so log correlation keeps working without further configuration. + +NOTE: With Brave, the MDC key names are only applied when baggage is enabled. +Setting configprop:management.tracing.baggage.enabled[] to `false` stops Brave from writing the trace and span IDs to the MDC altogether, which disables log correlation. + If you prefer to use a different format for your correlation ID, you can use the configprop:logging.pattern.correlation[] property to define one. +A value that you set yourself takes precedence over the format derived from the MDC key names, so it has to reference any customized key names itself. For example, the following will provide a correlation ID for Logback in format previously used by Spring Cloud Sleuth: [configprops,yaml] diff --git a/module/spring-boot-micrometer-tracing-brave/src/main/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BravePropagationConfigurations.java b/module/spring-boot-micrometer-tracing-brave/src/main/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BravePropagationConfigurations.java index 626a28beb65..009b11c735e 100644 --- a/module/spring-boot-micrometer-tracing-brave/src/main/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BravePropagationConfigurations.java +++ b/module/spring-boot-micrometer-tracing-brave/src/main/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BravePropagationConfigurations.java @@ -19,6 +19,7 @@ package org.springframework.boot.micrometer.tracing.brave.autoconfigure; import java.util.List; import brave.baggage.BaggageField; +import brave.baggage.BaggageFields; import brave.baggage.BaggagePropagation; import brave.baggage.BaggagePropagation.FactoryBuilder; import brave.baggage.BaggagePropagationConfig; @@ -40,6 +41,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.micrometer.tracing.autoconfigure.ConditionalOnEnabledTracingExport; import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties; import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Baggage.Correlation; +import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Mdc; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; @@ -138,7 +140,13 @@ class BravePropagationConfigurations { @ConditionalOnMissingBean CorrelationScopeDecorator.Builder mdcCorrelationScopeDecoratorBuilder( ObjectProvider correlationScopeCustomizers) { + Mdc mdc = this.tracingProperties.getMdc(); CorrelationScopeDecorator.Builder builder = MDCScopeDecorator.newBuilder(); + if (mdc.isCustomized()) { + builder.clear() + .add(SingleCorrelationField.newBuilder(BaggageFields.TRACE_ID).name(mdc.getTraceIdKey()).build()) + .add(SingleCorrelationField.newBuilder(BaggageFields.SPAN_ID).name(mdc.getSpanIdKey()).build()); + } correlationScopeCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder; } diff --git a/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BraveAutoConfigurationTests.java b/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BraveAutoConfigurationTests.java index e3daafbd45d..125ea3f35ae 100644 --- a/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BraveAutoConfigurationTests.java +++ b/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BraveAutoConfigurationTests.java @@ -26,8 +26,10 @@ import brave.Span; import brave.SpanCustomizer; import brave.Tracer; import brave.Tracing; +import brave.baggage.BaggageFields; import brave.baggage.BaggagePropagation; import brave.baggage.CorrelationScopeConfig.SingleCorrelationField; +import brave.context.slf4j.MDCScopeDecorator; import brave.handler.SpanHandler; import brave.propagation.CurrentTraceContext; import brave.propagation.CurrentTraceContext.ScopeDecorator; @@ -258,6 +260,20 @@ class BraveAutoConfigurationTests { .run((context) -> assertThat(context).hasBean("mdcCorrelationScopeDecoratorBuilder")); } + /** + * Guards the assumption behind the {@code clear()} call that + * {@code BravePropagationConfigurations} makes when the MDC keys have been + * customized: if Brave ever adds another default correlation field, this fails + * instead of that field silently disappearing from the MDC of applications using + * custom keys. + */ + @Test + void shouldOnlyHaveTraceIdAndSpanIdCorrelationFieldsByDefaultInBrave() { + assertThat(MDCScopeDecorator.newBuilder().configs()) + .extracting((config) -> ((SingleCorrelationField) config).name()) + .containsExactlyInAnyOrder(BaggageFields.TRACE_ID.name(), BaggageFields.SPAN_ID.name()); + } + @Test void shouldHave128BitTraceId() { this.contextRunner.run((context) -> { diff --git a/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BraveBaggagePropagationIntegrationTests.java b/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BraveBaggagePropagationIntegrationTests.java index 466116164cd..6aba67eb5b7 100644 --- a/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BraveBaggagePropagationIntegrationTests.java +++ b/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BraveBaggagePropagationIntegrationTests.java @@ -24,6 +24,7 @@ import io.micrometer.tracing.Span; import io.micrometer.tracing.Tracer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.slf4j.MDC; @@ -79,6 +80,28 @@ class BraveBaggagePropagationIntegrationTests { }); } + @Test + void shouldUseCustomMdcKeysForTraceAndSpanId() { + new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(BraveAutoConfiguration.class)) + .withPropertyValues("management.tracing.mdc.trace-id-key=customTraceId", + "management.tracing.mdc.span-id-key=customSpanId") + .run((context) -> { + Tracer tracer = tracer(context); + Span span = createSpan(tracer); + try (Tracer.SpanInScope scope = tracer.withSpan(span.start())) { + assertMdcValue("customTraceId", span.context().traceId()); + assertMdcValue("customSpanId", span.context().spanId()); + assertUnsetMdc("traceId"); + assertUnsetMdc("spanId"); + } + finally { + span.end(); + } + assertUnsetMdc("customTraceId"); + assertUnsetMdc("customSpanId"); + }); + } + @ParameterizedTest @EnumSource void shouldRemoveEntriesFromMdcForNullSpan(AutoConfig autoConfig) { diff --git a/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfiguration.java b/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfiguration.java index c2bd6e6ed3d..fec4ac315cf 100644 --- a/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfiguration.java +++ b/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfiguration.java @@ -59,6 +59,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.micrometer.tracing.autoconfigure.MicrometerTracingAutoConfiguration; import org.springframework.boot.micrometer.tracing.autoconfigure.NoopTracerAutoConfiguration; import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties; +import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Mdc; import org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.OpenTelemetryPropagationConfigurations.NoPropagation; import org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.OpenTelemetryPropagationConfigurations.PropagationWithBaggage; import org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.OpenTelemetryPropagationConfigurations.PropagationWithoutBaggage; @@ -219,7 +220,8 @@ public final class OpenTelemetryTracingAutoConfiguration { @Bean @ConditionalOnMissingBean Slf4JEventListener otelSlf4JEventListener() { - return new Slf4JEventListener(); + Mdc mdc = this.tracingProperties.getMdc(); + return new Slf4JEventListener(mdc.getTraceIdKey(), mdc.getSpanIdKey()); } @Bean diff --git a/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryBaggagePropagationIntegrationTests.java b/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryBaggagePropagationIntegrationTests.java index 900f2ab5c63..af729975eb8 100644 --- a/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryBaggagePropagationIntegrationTests.java +++ b/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryBaggagePropagationIntegrationTests.java @@ -26,6 +26,7 @@ import io.opentelemetry.context.Context; import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.slf4j.MDC; @@ -85,6 +86,30 @@ class OpenTelemetryBaggagePropagationIntegrationTests { }); } + @Test + void shouldUseCustomMdcKeysForTraceAndSpanId() { + new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) + .withConfiguration(AutoConfigurations.of(OpenTelemetrySdkAutoConfiguration.class, + OpenTelemetryTracingAutoConfiguration.class)) + .withPropertyValues("management.tracing.mdc.trace-id-key=customTraceId", + "management.tracing.mdc.span-id-key=customSpanId") + .run((context) -> { + Tracer tracer = tracer(context); + Span span = createSpan(tracer); + try (Tracer.SpanInScope scope = tracer.withSpan(span.start())) { + assertMdcValue("customTraceId", span.context().traceId()); + assertMdcValue("customSpanId", span.context().spanId()); + assertUnsetMdc("traceId"); + assertUnsetMdc("spanId"); + } + finally { + span.end(); + } + assertUnsetMdc("customTraceId"); + assertUnsetMdc("customSpanId"); + }); + } + @ParameterizedTest @EnumSource void shouldRemoveEntriesFromMdcForNullSpan(AutoConfig autoConfig) { diff --git a/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/LogCorrelationEnvironmentPostProcessor.java b/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/LogCorrelationEnvironmentPostProcessor.java index d88b898de82..c6bd009326f 100644 --- a/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/LogCorrelationEnvironmentPostProcessor.java +++ b/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/LogCorrelationEnvironmentPostProcessor.java @@ -21,6 +21,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.boot.EnvironmentPostProcessor; import org.springframework.boot.SpringApplication; import org.springframework.boot.logging.LoggingSystem; +import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Mdc; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.Environment; @@ -31,10 +32,13 @@ import org.springframework.util.ClassUtils; * {@link EnvironmentPostProcessor} to add a {@link PropertySource} to support log * correlation IDs when Micrometer Tracing is present. Adds support for the * {@value LoggingSystem#EXPECT_CORRELATION_ID_PROPERTY} property by delegating to - * {@code management.tracing.export.enabled}. + * {@code management.tracing.export.enabled}, and defaults + * {@code logging.pattern.correlation} to the MDC keys configured through + * {@code management.tracing.mdc.*}. * * @author Jonatan Ivanov * @author Phillip Webb + * @author Moritz Halbritter */ class LogCorrelationEnvironmentPostProcessor implements EnvironmentPostProcessor { @@ -52,6 +56,18 @@ class LogCorrelationEnvironmentPostProcessor implements EnvironmentPostProcessor private static final String NAME = "logCorrelation"; + private static final String CORRELATION_PATTERN_PROPERTY = "logging.pattern.correlation"; + + /** + * Expected trace ID length, matching {@code CorrelationIdFormatter.DEFAULT}. + */ + private static final int TRACE_ID_LENGTH = 32; + + /** + * Expected span ID length, matching {@code CorrelationIdFormatter.DEFAULT}. + */ + private static final int SPAN_ID_LENGTH = 16; + private final Environment environment; LogCorrelationPropertySource(Object source, Environment environment) { @@ -61,17 +77,35 @@ class LogCorrelationEnvironmentPostProcessor implements EnvironmentPostProcessor @Override public String[] getPropertyNames() { - return new String[] { LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY }; + return new String[] { LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, CORRELATION_PATTERN_PROPERTY }; } @Override public @Nullable Object getProperty(String name) { if (name.equals(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY)) { - return this.environment.getProperty("management.tracing.export.enabled", Boolean.class, Boolean.TRUE); + return isExpectCorrelationId(); + } + if (name.equals(CORRELATION_PATTERN_PROPERTY)) { + return getCorrelationPattern(); } return null; } + private Boolean isExpectCorrelationId() { + return this.environment.getProperty("management.tracing.export.enabled", Boolean.class, Boolean.TRUE); + } + + private @Nullable String getCorrelationPattern() { + if (!isExpectCorrelationId()) { + return null; + } + String traceIdKey = this.environment.getProperty("management.tracing.mdc.trace-id-key", + Mdc.DEFAULT_TRACE_ID_KEY); + String spanIdKey = this.environment.getProperty("management.tracing.mdc.span-id-key", + Mdc.DEFAULT_SPAN_ID_KEY); + return "%%correlationId{%s(%d),%s(%d)}".formatted(traceIdKey, TRACE_ID_LENGTH, spanIdKey, SPAN_ID_LENGTH); + } + } } diff --git a/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/TracingProperties.java b/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/TracingProperties.java index b1588f9f054..aa34ffee6ce 100644 --- a/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/TracingProperties.java +++ b/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/TracingProperties.java @@ -22,6 +22,7 @@ import java.util.List; import org.jspecify.annotations.Nullable; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.Assert; /** * Configuration properties for tracing. @@ -53,6 +54,11 @@ public class TracingProperties { */ private final Exemplars exemplars = new Exemplars(); + /** + * {@code org.slf4j.MDC} key configuration for trace and span IDs. + */ + private final Mdc mdc = new Mdc(); + public Sampling getSampling() { return this.sampling; } @@ -69,6 +75,10 @@ public class TracingProperties { return this.exemplars; } + public Mdc getMdc() { + return this.mdc; + } + public static class Sampling { /** @@ -258,6 +268,53 @@ public class TracingProperties { } + /** + * {@code org.slf4j.MDC} key configuration for trace and span IDs. + */ + public static class Mdc { + + static final String DEFAULT_TRACE_ID_KEY = "traceId"; + + static final String DEFAULT_SPAN_ID_KEY = "spanId"; + + /** + * Key under which the trace ID is added to the {@code org.slf4j.MDC}. + */ + private String traceIdKey = DEFAULT_TRACE_ID_KEY; + + /** + * Key under which the span ID is added to the {@code org.slf4j.MDC}. + */ + private String spanIdKey = DEFAULT_SPAN_ID_KEY; + + public String getTraceIdKey() { + return this.traceIdKey; + } + + public void setTraceIdKey(String traceIdKey) { + Assert.hasText(traceIdKey, "'traceIdKey' must not be empty"); + this.traceIdKey = traceIdKey; + } + + public String getSpanIdKey() { + return this.spanIdKey; + } + + public void setSpanIdKey(String spanIdKey) { + Assert.hasText(spanIdKey, "'spanIdKey' must not be empty"); + this.spanIdKey = spanIdKey; + } + + /** + * Return whether any of the keys differs from its default. + * @return whether the keys have been customized + */ + public boolean isCustomized() { + return !DEFAULT_TRACE_ID_KEY.equals(this.traceIdKey) || !DEFAULT_SPAN_ID_KEY.equals(this.spanIdKey); + } + + } + /** * Exemplars configuration. */ diff --git a/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/LogCorrelationEnvironmentPostProcessorTests.java b/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/LogCorrelationEnvironmentPostProcessorTests.java index 668f6714f68..d0861624473 100644 --- a/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/LogCorrelationEnvironmentPostProcessorTests.java +++ b/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/LogCorrelationEnvironmentPostProcessorTests.java @@ -34,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Jonatan Ivanov * @author Phillip Webb + * @author Moritz Halbritter */ class LogCorrelationEnvironmentPostProcessorTests { @@ -72,7 +73,42 @@ class LogCorrelationEnvironmentPostProcessorTests { PropertySource propertySource = this.environment.getPropertySources().get("logCorrelation"); assertThat(propertySource).isInstanceOf(EnumerablePropertySource.class); assertThat(((EnumerablePropertySource) propertySource).getPropertyNames()) - .containsExactly(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY); + .containsExactly(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, "logging.pattern.correlation"); + } + + @Test + void getCorrelationPatternWhenMdcKeysAreDefaultReturnsPatternUsingDefaultKeys() { + this.postProcessor.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty("logging.pattern.correlation")) + .isEqualTo("%correlationId{traceId(32),spanId(16)}"); + } + + @Test + void getCorrelationPatternWhenMdcKeysAreCustomizedReturnsPatternUsingThoseKeys() { + TestPropertyValues + .of("management.tracing.mdc.trace-id-key=customTraceId", "management.tracing.mdc.span-id-key=customSpanId") + .applyTo(this.environment); + this.postProcessor.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty("logging.pattern.correlation")) + .isEqualTo("%correlationId{customTraceId(32),customSpanId(16)}"); + } + + @Test + void getCorrelationPatternWhenSetByUserDoesNotOverride() { + TestPropertyValues + .of("management.tracing.mdc.trace-id-key=customTraceId", "logging.pattern.correlation=%correlationId{x(1)}") + .applyTo(this.environment); + this.postProcessor.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty("logging.pattern.correlation")).isEqualTo("%correlationId{x(1)}"); + } + + @Test + void getCorrelationPatternWhenTracingDisabledReturnsNull() { + TestPropertyValues + .of("management.tracing.export.enabled=false", "management.tracing.mdc.trace-id-key=customTraceId") + .applyTo(this.environment); + this.postProcessor.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty("logging.pattern.correlation")).isNull(); } } diff --git a/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/TracingPropertiesTests.java b/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/TracingPropertiesTests.java new file mode 100644 index 00000000000..0a7dd3ba6c1 --- /dev/null +++ b/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/TracingPropertiesTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-present 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 + * + * https://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.boot.micrometer.tracing.autoconfigure; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Mdc; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link TracingProperties}. + * + * @author Moritz Halbritter + */ +class TracingPropertiesTests { + + private final Mdc mdc = new TracingProperties().getMdc(); + + @Test + void shouldUseTraceIdAndSpanIdAsDefaultMdcKeys() { + assertThat(this.mdc.getTraceIdKey()).isEqualTo("traceId"); + assertThat(this.mdc.getSpanIdKey()).isEqualTo("spanId"); + } + + @Test + void shouldNotBeCustomizedWhenMdcKeysAreDefault() { + assertThat(this.mdc.isCustomized()).isFalse(); + } + + @Test + void shouldBeCustomizedWhenAnMdcKeyDiffersFromItsDefault() { + this.mdc.setSpanIdKey("customSpanId"); + assertThat(this.mdc.isCustomized()).isTrue(); + } + + @Test + void shouldRejectBlankTraceIdKey() { + assertThatIllegalArgumentException().isThrownBy(() -> this.mdc.setTraceIdKey(" ")); + } + + @Test + void shouldRejectBlankSpanIdKey() { + assertThatIllegalArgumentException().isThrownBy(() -> this.mdc.setSpanIdKey(" ")); + } + +} diff --git a/smoke-test/spring-boot-smoke-test-opentelemetry/build.gradle b/smoke-test/spring-boot-smoke-test-opentelemetry/build.gradle index cafa3a49a15..abae3ef89f5 100644 --- a/smoke-test/spring-boot-smoke-test-opentelemetry/build.gradle +++ b/smoke-test/spring-boot-smoke-test-opentelemetry/build.gradle @@ -26,6 +26,7 @@ dependencies { implementation(project(":starter:spring-boot-starter-opentelemetry")) testImplementation(project(":starter:spring-boot-starter-test")) + testImplementation(project(":test-support:spring-boot-test-support")) testImplementation("io.micrometer:micrometer-registry-otlp") dockerTestImplementation(project(":core:spring-boot-testcontainers")) diff --git a/smoke-test/spring-boot-smoke-test-opentelemetry/src/test/java/smoketest/opentelemetry/LogCorrelationTests.java b/smoke-test/spring-boot-smoke-test-opentelemetry/src/test/java/smoketest/opentelemetry/LogCorrelationTests.java new file mode 100644 index 00000000000..30f7dfeab24 --- /dev/null +++ b/smoke-test/spring-boot-smoke-test-opentelemetry/src/test/java/smoketest/opentelemetry/LogCorrelationTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-present 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 + * + * https://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 smoketest.opentelemetry; + +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.boot.testsupport.classpath.ForkedClassPath; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests that the correlation ID in the log output follows the MDC keys configured through + * {@code management.tracing.mdc.*}. + *

+ * Runs with a forked class path because {@code LOG_CORRELATION_PATTERN} is a JVM-wide + * system property that is only ever written once, so a context started by another test + * class would pin it to the default pattern. + * + * @author Moritz Halbritter + */ +@SpringBootTest(properties = { "management.tracing.mdc.trace-id-key=customTraceId", + "management.tracing.mdc.span-id-key=customSpanId" }) +@ExtendWith(OutputCaptureExtension.class) +@ForkedClassPath +class LogCorrelationTests { + + private static final Log logger = LogFactory.getLog(LogCorrelationTests.class); + + @Autowired + private Tracer tracer; + + @Test + void shouldUseCustomMdcKeysForCorrelationId(CapturedOutput output) { + Span span = this.tracer.nextSpan().name("test"); + try (Tracer.SpanInScope scope = this.tracer.withSpan(span.start())) { + logger.info("Hello from a span"); + } + finally { + span.end(); + } + assertThat(output).containsPattern( + "\\[%s-%s\\].*Hello from a span".formatted(span.context().traceId(), span.context().spanId())); + } + +}