diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index 002b4788aa..25d1111499 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -220,6 +220,7 @@ dependencies { testImplementation("com.mysql:mysql-connector-j") testImplementation("com.squareup.okhttp3:mockwebserver") testImplementation("com.sun.xml.messaging.saaj:saaj-impl") + testImplementation("io.micrometer:context-propagation") testImplementation("io.projectreactor:reactor-test") testImplementation("io.r2dbc:r2dbc-h2") testImplementation("jakarta.json:jakarta.json-api") diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfiguration.java new file mode 100644 index 0000000000..c247b1a9c0 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfiguration.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2023 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.autoconfigure.reactor; + +import reactor.core.publisher.Hooks; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Reactor. + * + * @author Brian Clozel + * @since 3.0.2 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(Hooks.class) +@EnableConfigurationProperties(ReactorProperties.class) +public class ReactorAutoConfiguration { + + public ReactorAutoConfiguration(ReactorProperties properties) { + if (properties.getContextPropagation() == ReactorProperties.ContextPropagationMode.AUTO) { + Hooks.enableAutomaticContextPropagation(); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorProperties.java new file mode 100644 index 0000000000..6725ee1ba4 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorProperties.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2023 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.autoconfigure.reactor; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for Reactor. + * + * @author Brian Clozel + * @since 3.0.3 + */ +@ConfigurationProperties(prefix = "spring.reactor") +public class ReactorProperties { + + /** + * Context Propagation support mode for Reactor operators. + */ + private ContextPropagationMode contextPropagation = ContextPropagationMode.AUTO; + + public ContextPropagationMode getContextPropagation() { + return this.contextPropagation; + } + + public void setContextPropagation(ContextPropagationMode contextPropagation) { + this.contextPropagation = contextPropagation; + } + + public enum ContextPropagationMode { + + /** + * Context Propagation is applied to all Reactor operators. + */ + AUTO, + + /** + * Context Propagation is only applied to "tap" and "handle" Reactor operators. + */ + LIMITED + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/package-info.java new file mode 100644 index 0000000000..4b55cfe4d5 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 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. + */ + +/** + * Auto-configuration for Reactor. + */ +package org.springframework.boot.autoconfigure.reactor; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index d1d75b8e19..f12c04ea45 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -2090,6 +2090,10 @@ "level": "error" } }, + { + "name": "spring.reactor.context-propagation", + "defaultValue": "auto" + }, { "name": "spring.reactor.stacktrace-mode.enabled", "description": "Whether Reactor should collect stacktrace information at runtime.", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index c8fbc3b8d5..3a2a6ab1e7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -97,6 +97,7 @@ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration org.springframework.boot.autoconfigure.pulsar.PulsarAutoConfiguration org.springframework.boot.autoconfigure.pulsar.PulsarReactiveAutoConfiguration org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration +org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfigurationTests.java new file mode 100644 index 0000000000..73642c5883 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfigurationTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2023 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.autoconfigure.reactor; + +import java.util.concurrent.atomic.AtomicReference; + +import io.micrometer.context.ContextRegistry; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ReactorAutoConfiguration}. + * + * @author Brian Clozel + */ +class ReactorAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ReactorAutoConfiguration.class)); + + private static final String THREADLOCAL_KEY = "ReactorAutoConfigurationTests"; + + private static final ThreadLocal THREADLOCAL_VALUE = ThreadLocal.withInitial(() -> "failure"); + + @BeforeAll + static void initializeThreadLocalAccessors() { + ContextRegistry globalRegistry = ContextRegistry.getInstance(); + globalRegistry.registerThreadLocalAccessor(THREADLOCAL_KEY, THREADLOCAL_VALUE); + } + + @Test + void shouldConfigureAutomaticContextPropagation() { + AtomicReference threadLocalValue = new AtomicReference<>(); + this.contextRunner.run((applicationContext) -> { + Mono.just("test") + .doOnNext((element) -> threadLocalValue.set(THREADLOCAL_VALUE.get())) + .contextWrite(Context.of(THREADLOCAL_KEY, "success")) + .block(); + assertThat(threadLocalValue.get()).isEqualTo("success"); + }); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc index 2b8fcccdb4..b1256b684e 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc @@ -80,4 +80,7 @@ The attributes of the `Resource` can be configured via the configprop:management NOTE: Spring Boot does not provide auto-configuration for OpenTelemetry metrics or logging. OpenTelemetry tracing is only auto-configured when used together with <>. +Observability support relies on the https://github.com/micrometer-metrics/context-propagation[Context Propagation library] for forwarding the current observation across threads and reactive pipelines. +`ThreadLocal` values are automatically reinstated in reactive operators, this behavior is controlled with the configprop:spring.reactor.context-propagation[] property. + The next sections will provide more details about logging, metrics and traces. diff --git a/spring-boot-project/spring-boot-parent/build.gradle b/spring-boot-project/spring-boot-parent/build.gradle index a90f00a696..0c0f26a225 100644 --- a/spring-boot-project/spring-boot-parent/build.gradle +++ b/spring-boot-project/spring-boot-parent/build.gradle @@ -138,6 +138,13 @@ bom { ] } } + library("Micrometer Context Propagation", "1.0.2") { + group("io.micrometer") { + modules = [ + "context-propagation" + ] + } + } library("MockK", "1.13.5") { group("io.mockk") { modules = [