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 map; + + TraceRequestContext(Map map) { + this.map = map; + } + + @Override + public T get(Object key) { + return (T) map.get(key); + } + + @Override + public boolean hasKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public void put(Object key, Object value) { + map.put(key, value); + } + + @Override + public void delete(Object key) { + map.remove(key); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public Stream> stream() { + return map.entrySet().stream(); + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceSynchronousMongoClientSettingsBuilderCustomizer.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceSynchronousMongoClientSettingsBuilderCustomizer.java new file mode 100644 index 000000000..9ccb2e3a9 --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceSynchronousMongoClientSettingsBuilderCustomizer.java @@ -0,0 +1,73 @@ +/* + * 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.concurrent.ConcurrentHashMap; + +import com.mongodb.MongoClientSettings; +import com.mongodb.client.SynchronousContextProvider; + +import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.TraceContext; +import org.springframework.cloud.sleuth.Tracer; + +/** + * Trace representation of a {@link MongoClientSettingsBuilderCustomizer} that passes + * through the Reactor context. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TraceSynchronousMongoClientSettingsBuilderCustomizer implements MongoClientSettingsBuilderCustomizer { + + private final Tracer tracer; + + public TraceSynchronousMongoClientSettingsBuilderCustomizer(Tracer tracer) { + this.tracer = tracer; + } + + @Override + public void customize(MongoClientSettings.Builder clientSettingsBuilder) { + clientSettingsBuilder.contextProvider(contextProvider(this.tracer)); + } + + static SynchronousContextProvider contextProvider(Tracer tracer) { + return (SynchronousContextProvider) () -> new SynchronousTraceRequestContext(tracer); + } + + static class SynchronousTraceRequestContext extends TraceRequestContext { + + SynchronousTraceRequestContext(Tracer tracer) { + super(context(tracer)); + } + + private static Map context(Tracer tracer) { + Map map = new ConcurrentHashMap<>(); + Span currentSpan = tracer.currentSpan(); + if (currentSpan == null) { + return map; + } + map.put(Span.class, currentSpan); + map.put(TraceContext.class, currentSpan.context()); + return map; + } + + } + +} diff --git a/tests/brave/pom.xml b/tests/brave/pom.xml index 04ffa5604..20b8ff4c7 100644 --- a/tests/brave/pom.xml +++ b/tests/brave/pom.xml @@ -51,6 +51,7 @@ spring-cloud-sleuth-instrumentation-kotlin-tests spring-cloud-sleuth-instrumentation-lettuce-tests spring-cloud-sleuth-instrumentation-messaging-tests + spring-cloud-sleuth-instrumentation-mongodb-tests spring-cloud-sleuth-instrumentation-mvc-tests spring-cloud-sleuth-instrumentation-quartz-tests spring-cloud-sleuth-instrumentation-reactor-tests diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-mongodb-tests/pom.xml b/tests/brave/spring-cloud-sleuth-instrumentation-mongodb-tests/pom.xml new file mode 100644 index 000000000..8c57c153a --- /dev/null +++ b/tests/brave/spring-cloud-sleuth-instrumentation-mongodb-tests/pom.xml @@ -0,0 +1,97 @@ + + + + + 4.0.0 + + spring-cloud-sleuth-instrumentation-mongodb-reactive-tests + jar + Spring Cloud Sleuth Brave MongoDB Reactive Instrumentation Tests + Spring Cloud Sleuth Brave MongoDB Reactive Instrumentation Tests + + + org.springframework.cloud + spring-cloud-sleuth-tests-brave + 3.1.0-SNAPSHOT + .. + + + + true + + + + + + + maven-deploy-plugin + + true + + + + + + + + org.springframework.cloud + spring-cloud-sleuth-tests-common + ${project.version} + + + io.projectreactor + reactor-core + + + org.springframework.boot + spring-boot-starter-aop + + + org.mongodb + mongodb-driver-reactivestreams + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + org.springframework.cloud + spring-cloud-starter-sleuth + + + org.springframework.boot + spring-boot-starter-test + + + org.testcontainers + mongodb + + + io.zipkin.brave + brave-tests + + + org.awaitility + awaitility + + + + diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-mongodb-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/mongodb/ReactiveMongoDbIntegrationTests.java b/tests/brave/spring-cloud-sleuth-instrumentation-mongodb-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/mongodb/ReactiveMongoDbIntegrationTests.java new file mode 100644 index 000000000..fb262ad80 --- /dev/null +++ b/tests/brave/spring-cloud-sleuth-instrumentation-mongodb-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/mongodb/ReactiveMongoDbIntegrationTests.java @@ -0,0 +1,53 @@ +/* + * 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.brave.instrument.mongodb; + +import brave.sampler.Sampler; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.sleuth.brave.BraveTestSpanHandler; +import org.springframework.cloud.sleuth.test.TestSpanHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; + +@SpringBootTest +@ContextConfiguration(classes = ReactiveMongoDbIntegrationTests.Config.class) +public class ReactiveMongoDbIntegrationTests + extends org.springframework.cloud.sleuth.instrument.mongodb.ReactiveMongoDbIntegrationTests { + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + TestSpanHandler testSpanHandlerSupplier(brave.test.TestSpanHandler testSpanHandler) { + return new BraveTestSpanHandler(testSpanHandler); + } + + @Bean + Sampler alwaysSampler() { + return Sampler.ALWAYS_SAMPLE; + } + + @Bean + brave.test.TestSpanHandler braveTestSpanHandler() { + return new brave.test.TestSpanHandler(); + } + + } + +} diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-mongodb-tests/src/test/resources/application.yml b/tests/brave/spring-cloud-sleuth-instrumentation-mongodb-tests/src/test/resources/application.yml new file mode 100644 index 000000000..990436771 --- /dev/null +++ b/tests/brave/spring-cloud-sleuth-instrumentation-mongodb-tests/src/test/resources/application.yml @@ -0,0 +1,4 @@ +logging.level.org.springframework.cloud: DEBUG +logging.level.org.springframework.cloud.sleuth.instrument.mongodb: TRACE + +spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration, org.springframework.cloud.gateway.config.GatewayAutoConfiguration, org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration, org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration diff --git a/tests/common/pom.xml b/tests/common/pom.xml index a930ecd33..dfad52da9 100644 --- a/tests/common/pom.xml +++ b/tests/common/pom.xml @@ -174,6 +174,16 @@ spring-boot-starter-data-r2dbc true + + 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; + } + +}