diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/mongodb/BraveMongoDbAutoConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/mongodb/BraveMongoDbAutoConfiguration.java
index 0f9d424b8..78f5ffe03 100644
--- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/mongodb/BraveMongoDbAutoConfiguration.java
+++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/mongodb/BraveMongoDbAutoConfiguration.java
@@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer;
import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
+import org.springframework.cloud.sleuth.autoconfig.instrument.mongodb.TraceMongoDbAutoConfiguration;
import org.springframework.cloud.sleuth.brave.instrument.mongodb.TraceMongoClientSettingsBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -38,22 +39,28 @@ import org.springframework.context.annotation.Configuration;
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} enables MongoDb span information propagation.
*
+ * Will only be applied if for some reason the main {@link TraceMongoDbAutoConfiguration}
+ * will not be applied.
+ *
* @author Marcin Grzejszczak
* @since 3.0.0
+ * @deprecated use {@link TraceMongoDbAutoConfiguration}
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("com.mongodb.reactivestreams.client.MongoClient")
@ConditionalOnBean(Tracing.class)
-@AutoConfigureAfter(BraveAutoConfiguration.class)
+@AutoConfigureAfter({ BraveAutoConfiguration.class, TraceMongoDbAutoConfiguration.class })
@AutoConfigureBefore(MongoAutoConfiguration.class)
@ConditionalOnProperty(value = "spring.sleuth.mongodb.enabled", matchIfMissing = true)
@ConditionalOnClass({ MongoClientSettings.Builder.class, MongoDBTracing.class })
+@Deprecated
public class BraveMongoDbAutoConfiguration {
@Bean
// for tests
- @ConditionalOnMissingBean(TraceMongoClientSettingsBuilderCustomizer.class)
- MongoClientSettingsBuilderCustomizer traceMongoClientSettingsBuilderCustomizer(Tracing tracing) {
+ @ConditionalOnMissingBean({ TraceMongoClientSettingsBuilderCustomizer.class,
+ org.springframework.cloud.sleuth.instrument.mongodb.TraceMongoClientSettingsBuilderCustomizer.class })
+ MongoClientSettingsBuilderCustomizer braveTraceMongoClientSettingsBuilderCustomizer(Tracing tracing) {
return new TraceMongoClientSettingsBuilderCustomizer(tracing);
}
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/mongodb/TraceMongoDbAutoConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/mongodb/TraceMongoDbAutoConfiguration.java
new file mode 100644
index 000000000..33bbd2d64
--- /dev/null
+++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/mongodb/TraceMongoDbAutoConfiguration.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.autoconfig.instrument.mongodb;
+
+import com.mongodb.MongoClientSettings;
+
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
+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.ConditionalOnMissingClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
+import org.springframework.cloud.sleuth.CurrentTraceContext;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
+import org.springframework.cloud.sleuth.instrument.mongodb.TraceAllTypesMongoClientSettingsBuilderCustomizer;
+import org.springframework.cloud.sleuth.instrument.mongodb.TraceMongoClientSettingsBuilderCustomizer;
+import org.springframework.cloud.sleuth.instrument.mongodb.TraceReactiveMongoClientSettingsBuilderCustomizer;
+import org.springframework.cloud.sleuth.instrument.mongodb.TraceSynchronousMongoClientSettingsBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
+ * Auto-configuration} enables MongoDb span information propagation.
+ *
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnBean(Tracer.class)
+@AutoConfigureAfter(BraveAutoConfiguration.class)
+@AutoConfigureBefore(MongoAutoConfiguration.class)
+@ConditionalOnProperty(value = "spring.sleuth.mongodb.enabled", matchIfMissing = true)
+@ConditionalOnClass(MongoClientSettings.Builder.class)
+public class TraceMongoDbAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ @Conditional(EitherSynchronousOrReactiveContextProviderPresent.class)
+ TraceMongoClientSettingsBuilderCustomizer traceMongoClientSettingsBuilderCustomizer(Tracer tracer,
+ CurrentTraceContext currentTraceContext) {
+ return new TraceMongoClientSettingsBuilderCustomizer(tracer, currentTraceContext);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ @ConditionalOnMissingClass("com.mongodb.client.SynchronousContextProvider")
+ @ConditionalOnClass(name = "com.mongodb.reactivestreams.client.ReactiveContextProvider")
+ TraceReactiveMongoClientSettingsBuilderCustomizer traceReactiveMongoClientSettingsBuilderCustomizer() {
+ return new TraceReactiveMongoClientSettingsBuilderCustomizer();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ @ConditionalOnMissingClass("com.mongodb.reactivestreams.client.ReactiveContextProvider")
+ @ConditionalOnClass(name = "com.mongodb.client.SynchronousContextProvider")
+ TraceSynchronousMongoClientSettingsBuilderCustomizer traceSynchronousMongoClientSettingsBuilderCustomizer(
+ Tracer tracer) {
+ return new TraceSynchronousMongoClientSettingsBuilderCustomizer(tracer);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ @ConditionalOnClass(name = { "com.mongodb.client.SynchronousContextProvider",
+ "com.mongodb.reactivestreams.client.ReactiveContextProvider" })
+ TraceAllTypesMongoClientSettingsBuilderCustomizer traceAllTypesMongoClientSettingsBuilderCustomizer(Tracer tracer) {
+ return new TraceAllTypesMongoClientSettingsBuilderCustomizer(tracer);
+ }
+
+ static class EitherSynchronousOrReactiveContextProviderPresent extends AnyNestedCondition {
+
+ EitherSynchronousOrReactiveContextProviderPresent() {
+ super(ConfigurationPhase.REGISTER_BEAN);
+ }
+
+ @ConditionalOnClass(name = "com.mongodb.client.SynchronousContextProvider")
+ static class OnSychronousContextProvider {
+
+ }
+
+ @ConditionalOnClass(name = "com.mongodb.reactivestreams.client.ReactiveContextProvider")
+ static class OnReactiveContextProvider {
+
+ }
+
+ }
+
+}
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories
index 1bdfee08b..1f79b88a0 100644
--- a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -14,6 +14,7 @@ org.springframework.cloud.sleuth.autoconfig.instrument.config.TraceSpringCloudCo
org.springframework.cloud.sleuth.autoconfig.instrument.circuitbreaker.TraceCircuitBreakerAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.deployer.TraceDeployerAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.jdbc.TraceJdbcAutoConfiguration,\
+org.springframework.cloud.sleuth.autoconfig.instrument.mongodb.TraceMongoDbAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.rxjava.TraceRxJavaAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.quartz.TraceQuartzAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.task.TraceTaskAutoConfiguration,\
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/mongodb/BraveMongoDbAutoConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/mongodb/BraveMongoDbAutoConfigurationTests.java
index c240a0374..518b397bc 100644
--- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/mongodb/BraveMongoDbAutoConfigurationTests.java
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/mongodb/BraveMongoDbAutoConfigurationTests.java
@@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.sleuth.DisableSecurity;
+import org.springframework.cloud.sleuth.autoconfig.instrument.mongodb.TraceMongoDbAutoConfiguration;
import org.springframework.cloud.sleuth.brave.instrument.mongodb.TraceMongoClientSettingsBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -51,7 +52,7 @@ class BraveMongoDbAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
- @EnableAutoConfiguration
+ @EnableAutoConfiguration(exclude = TraceMongoDbAutoConfiguration.class)
@DisableSecurity
static class TestTraceMongoDbAutoConfiguration {
@@ -79,8 +80,9 @@ class TestMongoClientSettingsBuilderCustomizer extends TraceMongoClientSettingsB
public void customize(MongoClientSettings.Builder clientSettingsBuilder) {
super.customize(clientSettingsBuilder);
CommandListener listener = clientSettingsBuilder.build().getCommandListeners().get(0);
- listener.commandStarted(new CommandStartedEvent(0, null, "", "", BDDMockito.mock(BsonDocument.class)));
- listener.commandSucceeded(new CommandSucceededEvent(1, null, "", BDDMockito.mock(BsonDocument.class), 100));
+ listener.commandStarted(new CommandStartedEvent(null, 0, null, "", "", BDDMockito.mock(BsonDocument.class)));
+ listener.commandSucceeded(
+ new CommandSucceededEvent(null, 1, null, "", BDDMockito.mock(BsonDocument.class), 100));
}
}
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/mongodb/TraceMongoDbAutoConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/mongodb/TraceMongoDbAutoConfigurationTests.java
new file mode 100644
index 000000000..50ff8bb01
--- /dev/null
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/mongodb/TraceMongoDbAutoConfigurationTests.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.autoconfig.instrument.mongodb;
+
+import com.mongodb.client.SynchronousContextProvider;
+import com.mongodb.reactivestreams.client.ReactiveContextProvider;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
+import org.springframework.boot.logging.LogLevel;
+import org.springframework.boot.test.context.FilteredClassLoader;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.cloud.sleuth.autoconfig.TraceNoOpAutoConfiguration;
+import org.springframework.cloud.sleuth.instrument.mongodb.TraceAllTypesMongoClientSettingsBuilderCustomizer;
+import org.springframework.cloud.sleuth.instrument.mongodb.TraceMongoClientSettingsBuilderCustomizer;
+import org.springframework.cloud.sleuth.instrument.mongodb.TraceReactiveMongoClientSettingsBuilderCustomizer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class TraceMongoDbAutoConfigurationTests {
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withPropertyValues("spring.sleuth.noop.enabled=true")
+ .withConfiguration(
+ AutoConfigurations.of(TraceNoOpAutoConfiguration.class, TraceMongoDbAutoConfiguration.class))
+ .withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO));
+
+ @Test
+ void should_create_synchronous_customizer_when_reactive_context_missing() {
+ this.contextRunner.withClassLoader(new FilteredClassLoader(ReactiveContextProvider.class))
+ .run((context) -> assertThat(context).hasSingleBean(TraceMongoClientSettingsBuilderCustomizer.class)
+ .doesNotHaveBean(TraceAllTypesMongoClientSettingsBuilderCustomizer.class)
+ .doesNotHaveBean(TraceReactiveMongoClientSettingsBuilderCustomizer.class));
+ }
+
+ @Test
+ void should_create_reactive_customizer_when_synchronous_context_missing() {
+ this.contextRunner.withClassLoader(new FilteredClassLoader(SynchronousContextProvider.class))
+ .run((context) -> assertThat(context).hasSingleBean(TraceMongoClientSettingsBuilderCustomizer.class)
+ .hasSingleBean(TraceReactiveMongoClientSettingsBuilderCustomizer.class)
+ .doesNotHaveBean(TraceAllTypesMongoClientSettingsBuilderCustomizer.class));
+ }
+
+ @Test
+ void should_create_all_types_customizer_when_both_contexts_are_present() {
+ this.contextRunner
+ .run((context) -> assertThat(context).hasSingleBean(TraceMongoClientSettingsBuilderCustomizer.class)
+ .hasSingleBean(TraceAllTypesMongoClientSettingsBuilderCustomizer.class)
+ .doesNotHaveBean(TraceReactiveMongoClientSettingsBuilderCustomizer.class));
+ }
+
+ @Test
+ void should_not_create_any_command_listeners_when_there_is_no_context_provider() {
+ this.contextRunner
+ .withClassLoader(
+ new FilteredClassLoader(ReactiveContextProvider.class, SynchronousContextProvider.class))
+ .run((context) -> assertThat(context).doesNotHaveBean(TraceMongoClientSettingsBuilderCustomizer.class)
+ .doesNotHaveBean(TraceAllTypesMongoClientSettingsBuilderCustomizer.class)
+ .doesNotHaveBean(TraceReactiveMongoClientSettingsBuilderCustomizer.class));
+ }
+
+}
diff --git a/spring-cloud-sleuth-brave/pom.xml b/spring-cloud-sleuth-brave/pom.xml
index 1069e4dbe..d9756b993 100644
--- a/spring-cloud-sleuth-brave/pom.xml
+++ b/spring-cloud-sleuth-brave/pom.xml
@@ -186,6 +186,11 @@
spring-boot-starter-data-mongodb
true
+
+ org.mongodb
+ mongodb-driver-reactivestreams
+ true
+
org.aspectj
aspectjrt
diff --git a/spring-cloud-sleuth-instrumentation/pom.xml b/spring-cloud-sleuth-instrumentation/pom.xml
index dae97ec42..bcd228f78 100644
--- a/spring-cloud-sleuth-instrumentation/pom.xml
+++ b/spring-cloud-sleuth-instrumentation/pom.xml
@@ -241,6 +241,16 @@
kotlinx-coroutines-reactor
true
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+ true
+
+
+ org.mongodb
+ mongodb-driver-reactivestreams
+ true
+
p6spy
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceAllTypesMongoClientSettingsBuilderCustomizer.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceAllTypesMongoClientSettingsBuilderCustomizer.java
new file mode 100644
index 000000000..ff4ffd59c
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceAllTypesMongoClientSettingsBuilderCustomizer.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.instrument.mongodb;
+
+import com.mongodb.MongoClientSettings;
+import com.mongodb.RequestContext;
+import com.mongodb.client.SynchronousContextProvider;
+import com.mongodb.reactivestreams.client.ReactiveContextProvider;
+import org.reactivestreams.Subscriber;
+
+import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer;
+import org.springframework.cloud.sleuth.Tracer;
+
+/**
+ * Trace representation of a {@link MongoClientSettingsBuilderCustomizer} that passes both
+ * types of context providers.
+ *
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+public class TraceAllTypesMongoClientSettingsBuilderCustomizer implements MongoClientSettingsBuilderCustomizer {
+
+ private final Tracer tracer;
+
+ public TraceAllTypesMongoClientSettingsBuilderCustomizer(Tracer tracer) {
+ this.tracer = tracer;
+ }
+
+ @Override
+ public void customize(MongoClientSettings.Builder clientSettingsBuilder) {
+ clientSettingsBuilder.contextProvider(new AllTypesContextProvider(this.tracer));
+ }
+
+ static class AllTypesContextProvider implements SynchronousContextProvider, ReactiveContextProvider {
+
+ private final SynchronousContextProvider synchronousContextProvider;
+
+ private final ReactiveContextProvider reactiveContextProvider;
+
+ AllTypesContextProvider(Tracer tracer) {
+ this.synchronousContextProvider = TraceSynchronousMongoClientSettingsBuilderCustomizer
+ .contextProvider(tracer);
+ this.reactiveContextProvider = TraceReactiveMongoClientSettingsBuilderCustomizer.contextProvider();
+ }
+
+ @Override
+ public RequestContext getContext() {
+ return this.synchronousContextProvider.getContext();
+ }
+
+ @Override
+ public RequestContext getContext(Subscriber> subscriber) {
+ return this.reactiveContextProvider.getContext(subscriber);
+ }
+
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceMongoClientSettingsBuilderCustomizer.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceMongoClientSettingsBuilderCustomizer.java
new file mode 100644
index 000000000..f3c10b466
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceMongoClientSettingsBuilderCustomizer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.instrument.mongodb;
+
+import com.mongodb.MongoClientSettings;
+
+import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer;
+import org.springframework.cloud.sleuth.CurrentTraceContext;
+import org.springframework.cloud.sleuth.Tracer;
+
+/**
+ * Trace representation of a {@link MongoClientSettingsBuilderCustomizer}.
+ *
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+public class TraceMongoClientSettingsBuilderCustomizer implements MongoClientSettingsBuilderCustomizer {
+
+ private final Tracer tracer;
+
+ private final CurrentTraceContext currentTraceContext;
+
+ public TraceMongoClientSettingsBuilderCustomizer(Tracer tracer, CurrentTraceContext currentTraceContext) {
+ this.tracer = tracer;
+ this.currentTraceContext = currentTraceContext;
+ }
+
+ @Override
+ public void customize(MongoClientSettings.Builder clientSettingsBuilder) {
+ clientSettingsBuilder.addCommandListener(new TraceMongoCommandListener(this.tracer, this.currentTraceContext));
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceMongoCommandListener.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceMongoCommandListener.java
new file mode 100644
index 000000000..089c0cb27
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceMongoCommandListener.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.instrument.mongodb;
+
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import com.mongodb.MongoSocketException;
+import com.mongodb.RequestContext;
+import com.mongodb.connection.ConnectionDescription;
+import com.mongodb.connection.ConnectionId;
+import com.mongodb.event.CommandFailedEvent;
+import com.mongodb.event.CommandListener;
+import com.mongodb.event.CommandStartedEvent;
+import com.mongodb.event.CommandSucceededEvent;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.bson.BsonDocument;
+import org.bson.BsonValue;
+
+import org.springframework.cloud.sleuth.CurrentTraceContext;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.TraceContext;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.lang.Nullable;
+
+/**
+ * Altered the Brave MongoDb instrumentation code. The code is available here:
+ * https://github.com/openzipkin/brave/blob/release-5.13.0/instrumentation/mongodb/src/main/java/brave/mongodb/TraceMongoCommandListener.java
+ *
+ * @author OpenZipkin Brave Authors
+ */
+final class TraceMongoCommandListener implements CommandListener {
+
+ private static final Log log = LogFactory.getLog(TraceMongoCommandListener.class);
+
+ // See https://docs.mongodb.com/manual/reference/command for the command reference
+ static final Set COMMANDS_WITH_COLLECTION_NAME = new LinkedHashSet<>(
+ Arrays.asList("aggregate", "count", "distinct", "mapReduce", "geoSearch", "delete", "find", "findAndModify",
+ "insert", "update", "collMod", "compact", "convertToCapped", "create", "createIndexes", "drop",
+ "dropIndexes", "killCursors", "listIndexes", "reIndex"));
+
+ private final Tracer tracer;
+
+ private final CurrentTraceContext currentTraceContext;
+
+ TraceMongoCommandListener(Tracer tracer, CurrentTraceContext currentTraceContext) {
+ this.tracer = tracer;
+ this.currentTraceContext = currentTraceContext;
+ }
+
+ @Override
+ public void commandStarted(CommandStartedEvent event) {
+ if (log.isDebugEnabled()) {
+ log.debug("Instrumenting the command started event");
+ }
+ String databaseName = event.getDatabaseName();
+ if ("admin".equals(databaseName)) {
+ return; // don't trace commands like "endSessions"
+ }
+
+ RequestContext requestContext = event.getRequestContext();
+ if (requestContext == null) {
+ return;
+ }
+ Span parent = spanFromContext(this.tracer, this.currentTraceContext, requestContext);
+ if (log.isDebugEnabled()) {
+ log.debug("Found the following span passed from the mongo context [" + parent + "]");
+ }
+ if (parent == null) {
+ return;
+ }
+ Span.Builder childSpanBuilder = this.tracer.spanBuilder();
+ childSpanBuilder.setParent(parent.context());
+
+ String commandName = event.getCommandName();
+ BsonDocument command = event.getCommand();
+ String collectionName = getCollectionName(command, commandName);
+
+ childSpanBuilder.name(getSpanName(commandName, collectionName)).kind(Span.Kind.CLIENT)
+ .remoteServiceName("mongodb-" + databaseName).tag("mongodb.command", commandName);
+
+ if (collectionName != null) {
+ childSpanBuilder.tag("mongodb.collection", collectionName);
+ }
+
+ ConnectionDescription connectionDescription = event.getConnectionDescription();
+ if (connectionDescription != null) {
+ ConnectionId connectionId = connectionDescription.getConnectionId();
+ if (connectionId != null) {
+ childSpanBuilder.tag("mongodb.cluster_id", connectionId.getServerId().getClusterId().getValue());
+ }
+
+ try {
+ InetSocketAddress socketAddress = connectionDescription.getServerAddress().getSocketAddress();
+ childSpanBuilder.remoteIpAndPort(socketAddress.getAddress().getHostAddress(), socketAddress.getPort());
+ }
+ catch (MongoSocketException ignored) {
+ if (log.isDebugEnabled()) {
+ log.debug("Ignored exception when setting remote ip and port", ignored);
+ }
+ }
+ }
+
+ Span childSpan = childSpanBuilder.start();
+ // TODO: What about retries? We might override the parent span
+ requestContext.put(Span.class, childSpan);
+ requestContext.put(TraceContext.class, childSpan.context());
+ if (log.isDebugEnabled()) {
+ log.debug("Created a child span [" + childSpan
+ + "] for mongo instrumentation and put it in Reactor context");
+ }
+ }
+
+ private static Span spanFromContext(Tracer tracer, CurrentTraceContext currentTraceContext,
+ RequestContext context) {
+ Span span = context.getOrDefault(Span.class, null);
+ if (span != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("Found a span in mongo context [" + span + "]");
+ }
+ return span;
+ }
+ TraceContext traceContext = context.getOrDefault(TraceContext.class, null);
+ if (traceContext != null) {
+ try (CurrentTraceContext.Scope scope = currentTraceContext.maybeScope(traceContext)) {
+ if (log.isDebugEnabled()) {
+ log.debug("Found a trace context in mongo context [" + traceContext + "]");
+ }
+ return tracer.currentSpan();
+ }
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("No span was found - will not create any child spans");
+ }
+ return null;
+ }
+
+ @Override
+ public void commandSucceeded(CommandSucceededEvent event) {
+ RequestContext requestContext = event.getRequestContext();
+ if (requestContext == null) {
+ return;
+ }
+ Span span = requestContext.getOrDefault(Span.class, null);
+ if (span == null) {
+ return;
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Command succeeded - will close span [" + span + "]");
+ }
+ span.end();
+ requestContext.delete(Span.class);
+ requestContext.delete(TraceContext.class);
+ }
+
+ @Override
+ public void commandFailed(CommandFailedEvent event) {
+ RequestContext requestContext = event.getRequestContext();
+ if (requestContext == null) {
+ return;
+ }
+ Span span = requestContext.getOrDefault(Span.class, null);
+ if (span == null) {
+ return;
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Command failed - will close span [" + span + "]");
+ }
+ span.error(event.getThrowable());
+ span.end();
+ requestContext.delete(Span.class);
+ requestContext.delete(TraceContext.class);
+ }
+
+ @Nullable
+ String getCollectionName(BsonDocument command, String commandName) {
+ if (COMMANDS_WITH_COLLECTION_NAME.contains(commandName)) {
+ String collectionName = getNonEmptyBsonString(command.get(commandName));
+ if (collectionName != null) {
+ return collectionName;
+ }
+ }
+ // Some other commands, like getMore, have a field like {"collection":
+ // collectionName}.
+ return getNonEmptyBsonString(command.get("collection"));
+ }
+
+ /**
+ * @return trimmed string from {@code bsonValue} or null if the trimmed string was
+ * empty or the value wasn't a string
+ */
+ @Nullable
+ static String getNonEmptyBsonString(BsonValue bsonValue) {
+ if (bsonValue == null || !bsonValue.isString()) {
+ return null;
+ }
+ String stringValue = bsonValue.asString().getValue().trim();
+ return stringValue.isEmpty() ? null : stringValue;
+ }
+
+ static String getSpanName(String commandName, @Nullable String collectionName) {
+ if (collectionName == null) {
+ return commandName;
+ }
+ return commandName + " " + collectionName;
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceReactiveMongoClientSettingsBuilderCustomizer.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceReactiveMongoClientSettingsBuilderCustomizer.java
new file mode 100644
index 000000000..e89aad35d
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceReactiveMongoClientSettingsBuilderCustomizer.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.instrument.mongodb;
+
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import com.mongodb.MongoClientSettings;
+import com.mongodb.reactivestreams.client.ReactiveContextProvider;
+import reactor.core.CoreSubscriber;
+import reactor.util.context.Context;
+import reactor.util.context.ContextView;
+
+import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer;
+
+/**
+ * Trace representation of a {@link MongoClientSettingsBuilderCustomizer} that passes
+ * through the Reactor context.
+ *
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+public class TraceReactiveMongoClientSettingsBuilderCustomizer implements MongoClientSettingsBuilderCustomizer {
+
+ @Override
+ public void customize(MongoClientSettings.Builder clientSettingsBuilder) {
+ clientSettingsBuilder.contextProvider(contextProvider());
+ }
+
+ static ReactiveContextProvider contextProvider() {
+ return (ReactiveContextProvider) subscriber -> {
+ if (subscriber instanceof CoreSubscriber) {
+ return new ReactiveTraceRequestContext(((CoreSubscriber>) subscriber).currentContext());
+ }
+ return new ReactiveTraceRequestContext(Context.empty());
+ };
+ }
+
+ static class ReactiveTraceRequestContext extends TraceRequestContext {
+
+ ReactiveTraceRequestContext(ContextView context) {
+ super(new ConcurrentHashMap<>(context.stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue))));
+ }
+
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceRequestContext.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceRequestContext.java
new file mode 100644
index 000000000..552c93e63
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceRequestContext.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.instrument.mongodb;
+
+import java.util.Map;
+import java.util.stream.Stream;
+
+import com.mongodb.RequestContext;
+
+class TraceRequestContext implements RequestContext {
+
+ private final Map
+
+ org.mongodb
+ mongodb-driver-reactivestreams
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+ true
+
io.r2dbc
r2dbc-proxy
@@ -194,6 +204,11 @@
kafka
true
+
+ org.testcontainers
+ mongodb
+ true
+
org.springframework.boot
spring-boot-starter-rsocket
diff --git a/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/ReactiveMongoDbIntegrationTests.java b/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/ReactiveMongoDbIntegrationTests.java
new file mode 100644
index 000000000..95e1a96a3
--- /dev/null
+++ b/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/ReactiveMongoDbIntegrationTests.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.instrument.mongodb;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.MongoDBContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.TraceContext;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.exporter.FinishedSpan;
+import org.springframework.cloud.sleuth.test.TestSpanHandler;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.repository.reactive.ReactiveCrudRepository;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+@ContextConfiguration(classes = ReactiveMongoDbIntegrationTests.TestConfig.class)
+@Testcontainers
+public abstract class ReactiveMongoDbIntegrationTests {
+
+ private static final Log log = LogFactory.getLog(ReactiveMongoDbIntegrationTests.class);
+
+ @Autowired
+ TestSpanHandler spans;
+
+ @Autowired
+ Tracer tracer;
+
+ @Autowired
+ BasicUserRepository basicUserRepository;
+
+ @Container
+ static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo").withTag("4.4.7"));
+
+ @DynamicPropertySource
+ static void setProperties(DynamicPropertyRegistry registry) {
+ mongoDBContainer.start();
+ registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);
+ }
+
+ @BeforeEach
+ void setup() {
+ this.spans.clear();
+ }
+
+ @Test
+ public void should_pass_tracing_information_when_using_reactive_mongodb() {
+ // given
+ Span nextSpan = this.tracer.nextSpan().name("mongo-reactive-app");
+
+ // when
+ Mono.just(nextSpan).doOnNext(span -> this.tracer.withSpan(nextSpan.start())).flatMap(span -> {
+ log.info("Hello from flat map");
+ return this.basicUserRepository.save(new User("foo", "bar", "baz", null))
+ .flatMap(user -> this.basicUserRepository.findUserByUsername("foo"));
+ }).contextWrite(context -> context.put(Span.class, nextSpan).put(TraceContext.class, nextSpan.context()))
+ .doFinally(signalType -> nextSpan.end()).block(Duration.ofMinutes(1));
+
+ // then
+ List reportedSpans = this.spans.reportedSpans();
+ then(reportedSpans.stream().map(FinishedSpan::getTraceId).collect(Collectors.toSet()))
+ .as("There must be only 1 trace id").hasSize(1);
+ List mongoSpanNames = reportedSpans.stream()
+ .filter(fs -> fs.getName().equals("insert user") || fs.getName().equals("find user"))
+ .map(FinishedSpan::getName).collect(Collectors.toList());
+ then(mongoSpanNames).as("There must be first an insert then a find").containsExactly("insert user",
+ "find user");
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @EnableAutoConfiguration
+ public static class TestConfig {
+
+ }
+
+}
+
+interface BasicUserRepository extends ReactiveCrudRepository {
+
+ Mono findUserByUsername(String username);
+
+}
+
+class User {
+
+ private String id;
+
+ private String username;
+
+ private String firstname;
+
+ private String lastname;
+
+ User() {
+ }
+
+ User(String id) {
+ this.setId(id);
+ }
+
+ User(String username, String firstname, String lastname, String id) {
+ this.username = username;
+ this.firstname = firstname;
+ this.lastname = lastname;
+ this.id = id;
+ }
+
+ String getId() {
+ return this.id;
+ }
+
+ void setId(String id) {
+ this.id = id;
+ }
+
+ String getUsername() {
+ return this.username;
+ }
+
+ void setUsername(String username) {
+ this.username = username;
+ }
+
+ String getFirstname() {
+ return this.firstname;
+ }
+
+ void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ String getLastname() {
+ return this.lastname;
+ }
+
+ void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+}