From 3f7cebd802a346528051eb7e6958d178d63fc617 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 25 Aug 2026 09:29:28 +0200 Subject: [PATCH] Polish "Allow configuring Micrometer Tracing MDC keys" Derive logging.pattern.correlation from the configured MDC keys so that log correlation keeps working when the keys are customized, instead of silently rendering a blank correlation field. Reject empty MDC keys and only clear Brave's default correlation fields when the keys have been customized, so that applications using the defaults are unaffected if Brave adds a default field. Replace the tests that asserted on bean wiring with integration tests covering the MDC contents, plus a smoke test for the log output. See gh-50595 --- .../reference/pages/actuator/tracing.adoc | 7 ++ .../BravePropagationConfigurations.java | 14 ++-- .../BraveAutoConfigurationTests.java | 35 ++++------ ...aveBaggagePropagationIntegrationTests.java | 23 +++++++ .../BravePropagationConfigurationsTests.java | 48 ------------- ...tryBaggagePropagationIntegrationTests.java | 25 +++++++ ...elemetryTracingAutoConfigurationTests.java | 21 ------ ...ogCorrelationEnvironmentPostProcessor.java | 40 ++++++++++- .../autoconfigure/TracingProperties.java | 19 +++++- ...relationEnvironmentPostProcessorTests.java | 38 ++++++++++- .../autoconfigure/TracingPropertiesTests.java | 62 +++++++++++++++++ .../build.gradle | 1 + .../opentelemetry/LogCorrelationTests.java | 68 +++++++++++++++++++ 13 files changed, 296 insertions(+), 105 deletions(-) delete mode 100644 module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BravePropagationConfigurationsTests.java create mode 100644 module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/TracingPropertiesTests.java create mode 100644 smoke-test/spring-boot-smoke-test-opentelemetry/src/test/java/smoketest/opentelemetry/LogCorrelationTests.java 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 4476800596f..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 @@ -141,14 +141,12 @@ class BravePropagationConfigurations { CorrelationScopeDecorator.Builder mdcCorrelationScopeDecoratorBuilder( ObjectProvider correlationScopeCustomizers) { Mdc mdc = this.tracingProperties.getMdc(); - CorrelationScopeDecorator.Builder builder = MDCScopeDecorator.newBuilder() - // Clear existing traceId/spanId backage field mappings - // so the MDC key names can be customized below. - // BravePropagationConfigurationsTests validates the assumption that - // the builder only configures the trace/span id by default. - .clear() - .add(SingleCorrelationField.newBuilder(BaggageFields.TRACE_ID).name(mdc.getTraceIdKey()).build()) - .add(SingleCorrelationField.newBuilder(BaggageFields.SPAN_ID).name(mdc.getSpanIdKey()).build()); + 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 a121b3c0857..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,29 +260,18 @@ 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 correlationScopeDecoratorUsesDefaultMdcKeys() { - this.contextRunner.run((context) -> { - ScopeDecorator scopeDecorator = context.getBean(ScopeDecorator.class); - assertThat(scopeDecorator) - .extracting("fields", InstanceOfAssertFactories.array(SingleCorrelationField[].class)) - .extracting(SingleCorrelationField::name) - .containsExactly("traceId", "spanId"); - }); - } - - @Test - void correlationScopeDecoratorUsesCustomMdcKeys() { - this.contextRunner - .withPropertyValues("management.tracing.mdc.trace-id-key=customTraceId", - "management.tracing.mdc.span-id-key=customSpanId") - .run((context) -> { - ScopeDecorator scopeDecorator = context.getBean(ScopeDecorator.class); - assertThat(scopeDecorator) - .extracting("fields", InstanceOfAssertFactories.array(SingleCorrelationField[].class)) - .extracting(SingleCorrelationField::name) - .containsExactly("customTraceId", "customSpanId"); - }); + void shouldOnlyHaveTraceIdAndSpanIdCorrelationFieldsByDefaultInBrave() { + assertThat(MDCScopeDecorator.newBuilder().configs()) + .extracting((config) -> ((SingleCorrelationField) config).name()) + .containsExactlyInAnyOrder(BaggageFields.TRACE_ID.name(), BaggageFields.SPAN_ID.name()); } @Test 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-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BravePropagationConfigurationsTests.java b/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BravePropagationConfigurationsTests.java deleted file mode 100644 index 44c7ae20325..00000000000 --- a/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/BravePropagationConfigurationsTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.brave.autoconfigure; - -import brave.baggage.BaggageFields; -import brave.baggage.CorrelationScopeConfig.SingleCorrelationField; -import brave.baggage.CorrelationScopeDecorator; -import brave.context.slf4j.MDCScopeDecorator; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link BravePropagationConfigurations}. - */ -class BravePropagationConfigurationsTests { - - /** - * Validates the assumption that {@link MDCScopeDecorator#newBuilder()} only - * configures traceId and spanId by default. The - * {@link BravePropagationConfigurations.PropagationWithBaggage#mdcCorrelationScopeDecoratorBuilder} - * method clears the builder's defaults and re-adds only these two fields so their MDC - * key names can be customized. If Brave adds new default fields in the future, the - * {@code .clear()} call in that method would silently drop them, and this test will - * catch that. - */ - @Test - void mdcScopeDecoratorBuilderShouldOnlyHaveTraceIdAndSpanIdByDefault() { - CorrelationScopeDecorator.Builder builder = MDCScopeDecorator.newBuilder(); - assertThat(builder.configs()).extracting((config) -> ((SingleCorrelationField) config).name()) - .containsExactlyInAnyOrder(BaggageFields.TRACE_ID.name(), BaggageFields.SPAN_ID.name()); - } - -} 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-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java b/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java index b3ca60e46fe..9e86db47034 100644 --- a/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java +++ b/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java @@ -192,27 +192,6 @@ class OpenTelemetryTracingAutoConfigurationTests { }); } - @Test - void slf4jEventListenerUsesDefaultMdcKeys() { - this.contextRunner.run((context) -> { - Slf4JEventListener listener = context.getBean(Slf4JEventListener.class); - assertThat(listener).hasFieldOrPropertyWithValue("traceIdKey", "traceId") - .hasFieldOrPropertyWithValue("spanIdKey", "spanId"); - }); - } - - @Test - void slf4jEventListenerUsesCustomMdcKeys() { - this.contextRunner - .withPropertyValues("management.tracing.mdc.trace-id-key=customTraceId", - "management.tracing.mdc.span-id-key=customSpanId") - .run((context) -> { - Slf4JEventListener listener = context.getBean(Slf4JEventListener.class); - assertThat(listener).hasFieldOrPropertyWithValue("traceIdKey", "customTraceId") - .hasFieldOrPropertyWithValue("spanIdKey", "customSpanId"); - }); - } - @ParameterizedTest @ValueSource(strings = { "io.micrometer.tracing.otel", "io.opentelemetry.sdk", "io.opentelemetry.api" }) void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) { 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 02f70a7c64f..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. @@ -272,21 +273,26 @@ public class TracingProperties { */ 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 = "traceId"; + private String traceIdKey = DEFAULT_TRACE_ID_KEY; /** * Key under which the span ID is added to the {@code org.slf4j.MDC}. */ - private String spanIdKey = "spanId"; + 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; } @@ -295,9 +301,18 @@ public class TracingProperties { } 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); + } + } /** 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())); + } + +}