From 5007e68cc192d045b7106dd4f3e3c5a1c64ed2fd Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 20 Oct 2022 16:39:05 +0200 Subject: [PATCH] Polishing. See: #4216 --- .../observability/ContextProviderFactory.java | 139 +++++++++++ ...aultMongoHandlerObservationConvention.java | 22 +- .../EnableMongoObservability.java | 16 -- ...estContext.java => MapRequestContext.java} | 8 +- .../observability/MongoHandlerContext.java | 16 +- .../MongoHandlerObservationConvention.java | 4 +- .../MongoMetricsConfiguration.java | 20 +- .../MongoMetricsConfigurationHelper.java | 21 -- ...ngoMetricsReactiveConfigurationHelper.java | 24 -- .../observability/MongoObservation.java | 4 +- .../MongoObservationCommandListener.java | 68 +++--- .../MongoTracingObservationHandler.java | 122 ---------- .../ReactiveTraceRequestContext.java | 20 -- .../SynchronousTraceRequestContext.java | 38 --- .../mongodb/observability/package-info.java | 5 + .../ImperativeIntegrationTests.java | 77 ++++++ ...rvationCommandListenerForTracingTests.java | 56 ++--- .../MongoObservationCommandListenerTests.java | 34 +-- .../ReactiveIntegrationTests.java | 80 +++++++ .../mongodb/observability/TestConfig.java | 171 ++++++++++++++ .../observability/TestRequestContext.java | 26 --- .../observability/ZipkinIntegrationTests.java | 221 ------------------ 22 files changed, 590 insertions(+), 602 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/ContextProviderFactory.java delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/EnableMongoObservability.java rename spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/{TraceRequestContext.java => MapRequestContext.java} (88%) delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfigurationHelper.java delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsReactiveConfigurationHelper.java delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoTracingObservationHandler.java delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/ReactiveTraceRequestContext.java delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/SynchronousTraceRequestContext.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/package-info.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ImperativeIntegrationTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ReactiveIntegrationTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestConfig.java delete mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestRequestContext.java delete mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ZipkinIntegrationTests.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/ContextProviderFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/ContextProviderFactory.java new file mode 100644 index 0000000000..2357b334a2 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/ContextProviderFactory.java @@ -0,0 +1,139 @@ +/* + * Copyright 2022 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.data.mongodb.observability; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import org.reactivestreams.Subscriber; +import org.springframework.data.repository.util.ReactiveWrappers; +import org.springframework.data.repository.util.ReactiveWrappers.ReactiveLibrary; +import org.springframework.util.ClassUtils; + +import com.mongodb.ContextProvider; +import com.mongodb.RequestContext; +import com.mongodb.client.SynchronousContextProvider; +import com.mongodb.reactivestreams.client.ReactiveContextProvider; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; +import reactor.core.CoreSubscriber; + +/** + * Factory to create a {@link ContextProvider} to propagate the request context across tasks. Requires either + * {@link SynchronousContextProvider} or {@link ReactiveContextProvider} to be present. + * + * @author Mark Paluch + * @since 3.0 + */ +public class ContextProviderFactory { + + private static final boolean SYNCHRONOUS_PRESENT = ClassUtils + .isPresent("com.mongodb.client.SynchronousContextProvider", ContextProviderFactory.class.getClassLoader()); + + private static final boolean REACTIVE_PRESENT = ClassUtils.isPresent( + "com.mongodb.reactivestreams.client.ReactiveContextProvider", ContextProviderFactory.class.getClassLoader()) + && ReactiveWrappers.isAvailable(ReactiveLibrary.PROJECT_REACTOR); + + /** + * Create a {@link ContextProvider} given {@link ObservationRegistry}. The factory method attempts to create a + * {@link ContextProvider} that is capable to propagate request contexts across imperative or reactive usage, + * depending on their class path presence. + * + * @param observationRegistry must not be {@literal null}. + * @return + */ + public static ContextProvider create(ObservationRegistry observationRegistry) { + + if (SYNCHRONOUS_PRESENT && REACTIVE_PRESENT) { + return new CompositeContextProvider(observationRegistry); + } + + if (SYNCHRONOUS_PRESENT) { + return new DefaultSynchronousContextProvider(observationRegistry); + } + + if (REACTIVE_PRESENT) { + return DefaultReactiveContextProvider.INSTANCE; + } + + throw new IllegalStateException( + "Cannot create ContextProvider. Neither SynchronousContextProvider nor ReactiveContextProvider is on the class path."); + } + + record DefaultSynchronousContextProvider( + ObservationRegistry observationRegistry) implements SynchronousContextProvider { + + @Override + public RequestContext getContext() { + + MapRequestContext requestContext = new MapRequestContext(); + + Observation currentObservation = observationRegistry.getCurrentObservation(); + if (currentObservation != null) { + requestContext.put(Observation.class, currentObservation); + } + + return requestContext; + } + + } + + enum DefaultReactiveContextProvider implements ReactiveContextProvider { + + INSTANCE; + + @Override + public RequestContext getContext(Subscriber subscriber) { + + if (subscriber instanceof CoreSubscriber cs) { + + Map map = cs.currentContext().stream() + .collect(Collectors.toConcurrentMap(Entry::getKey, Entry::getValue)); + if (map.containsKey(ObservationThreadLocalAccessor.KEY)) { + map.put(Observation.class, map.get(ObservationThreadLocalAccessor.KEY)); + } + + return new MapRequestContext(map); + } + + return new MapRequestContext(); + } + } + + record CompositeContextProvider(DefaultSynchronousContextProvider synchronousContextProvider) + implements + SynchronousContextProvider, + ReactiveContextProvider { + + CompositeContextProvider(ObservationRegistry observationRegistry) { + this(new DefaultSynchronousContextProvider(observationRegistry)); + } + + @Override + public RequestContext getContext() { + return synchronousContextProvider.getContext(); + } + + @Override + public RequestContext getContext(Subscriber subscriber) { + return DefaultReactiveContextProvider.INSTANCE.getContext(subscriber); + } + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/DefaultMongoHandlerObservationConvention.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/DefaultMongoHandlerObservationConvention.java index 69b4152157..9f80c0535b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/DefaultMongoHandlerObservationConvention.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/DefaultMongoHandlerObservationConvention.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2022 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. @@ -15,30 +15,32 @@ */ package org.springframework.data.mongodb.observability; -import io.micrometer.common.KeyValue; -import io.micrometer.common.KeyValues; - import org.springframework.data.mongodb.observability.MongoObservation.HighCardinalityCommandKeyNames; import org.springframework.data.mongodb.observability.MongoObservation.LowCardinalityCommandKeyNames; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; import com.mongodb.connection.ConnectionDescription; import com.mongodb.connection.ConnectionId; import com.mongodb.event.CommandStartedEvent; +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; + /** * Default {@link MongoHandlerObservationConvention} implementation. * * @author Greg Turnquist - * @since 4.0.0 + * @since 4 */ -public class DefaultMongoHandlerObservationConvention implements MongoHandlerObservationConvention { +class DefaultMongoHandlerObservationConvention implements MongoHandlerObservationConvention { @Override public KeyValues getLowCardinalityKeyValues(MongoHandlerContext context) { KeyValues keyValues = KeyValues.empty(); - if (context.getCollectionName() != null) { + if (!ObjectUtils.isEmpty(context.getCollectionName())) { keyValues = keyValues .and(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.withValue(context.getCollectionName())); } @@ -58,12 +60,18 @@ public KeyValues getHighCardinalityKeyValues(MongoHandlerContext context) { HighCardinalityCommandKeyNames.MONGODB_COMMAND.withValue(context.getCommandStartedEvent().getCommandName())); } + @Override + public String getContextualName(MongoHandlerContext context) { + return context.getContextualName(); + } + /** * Extract connection details for a MongoDB connection into a {@link KeyValue}. * * @param event * @return */ + @Nullable private static KeyValue connectionTag(CommandStartedEvent event) { ConnectionDescription connectionDescription = event.getConnectionDescription(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/EnableMongoObservability.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/EnableMongoObservability.java deleted file mode 100644 index 6d6b8dde2d..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/EnableMongoObservability.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.springframework.data.mongodb.observability; - -import java.lang.annotation.*; - -import org.springframework.context.annotation.Import; - -/** - * Annotation to active Spring Data MongoDB's usage of Micrometer's Observation API. - */ -@Inherited -@Documented -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Import(MongoMetricsConfiguration.class) -public @interface EnableMongoObservability { -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/TraceRequestContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MapRequestContext.java similarity index 88% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/TraceRequestContext.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MapRequestContext.java index 7c35764ca6..562a23007f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/TraceRequestContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MapRequestContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2022 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. @@ -28,15 +28,15 @@ * @author Greg Turnquist * @since 4.0.0 */ -class TraceRequestContext implements RequestContext { +class MapRequestContext implements RequestContext { private final Map map; - public TraceRequestContext() { + public MapRequestContext() { this(new HashMap<>()); } - public TraceRequestContext(Map context) { + public MapRequestContext(Map context) { this.map = context; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoHandlerContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoHandlerContext.java index 7fece69fe5..847f0f5c91 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoHandlerContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoHandlerContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2022 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. @@ -15,8 +15,6 @@ */ package org.springframework.data.mongodb.observability; -import io.micrometer.observation.Observation; - import java.util.Arrays; import java.util.LinkedHashSet; import java.util.Set; @@ -30,6 +28,10 @@ import com.mongodb.event.CommandStartedEvent; import com.mongodb.event.CommandSucceededEvent; +import io.micrometer.observation.Observation; +import io.micrometer.observation.transport.Kind; +import io.micrometer.observation.transport.SenderContext; + /** * A {@link Observation.Context} that contains MongoDB events. * @@ -37,10 +39,12 @@ * @author Greg Turnquist * @since 4.0.0 */ -public class MongoHandlerContext extends Observation.Context { +public class MongoHandlerContext extends SenderContext { /** - * @see https://docs.mongodb.com/manual/reference/command for the command reference + * @see https://docs.mongodb.com/manual/reference/command for + * the command reference */ private static final Set COMMANDS_WITH_COLLECTION_NAME = new LinkedHashSet<>( Arrays.asList("aggregate", "count", "distinct", "mapReduce", "geoSearch", "delete", "find", "findAndModify", @@ -55,7 +59,7 @@ public class MongoHandlerContext extends Observation.Context { private CommandFailedEvent commandFailedEvent; public MongoHandlerContext(CommandStartedEvent commandStartedEvent, RequestContext requestContext) { - + super((carrier, key, value) -> {}, Kind.CLIENT); this.commandStartedEvent = commandStartedEvent; this.requestContext = requestContext; this.collectionName = getCollectionName(commandStartedEvent); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoHandlerObservationConvention.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoHandlerObservationConvention.java index e7b5a8e1be..f677711061 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoHandlerObservationConvention.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoHandlerObservationConvention.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2022 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. @@ -22,7 +22,7 @@ * {@link ObservationConvention} for {@link MongoHandlerContext}. * * @author Greg Turnquist - * @since 4.0.0 + * @since 4 */ public interface MongoHandlerObservationConvention extends ObservationConvention { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfiguration.java index 75e41fd3d7..5917fdb864 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfiguration.java @@ -1,21 +1,21 @@ package org.springframework.data.mongodb.observability; -import io.micrometer.observation.ObservationRegistry; -import io.micrometer.tracing.Tracer; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.micrometer.observation.ObservationRegistry; /** * Class to configure needed beans for MongoDB + Micrometer. + * + * @since 3.0 */ +@Configuration public class MongoMetricsConfiguration { - @Bean - MongoObservationCommandListener mongoObservationCommandListener(ObservationRegistry registry) { - return new MongoObservationCommandListener(registry); - } + @Bean + public MongoObservationCommandListener mongoObservationCommandListener(ObservationRegistry registry) { + return new MongoObservationCommandListener(registry); + } - @Bean - MongoTracingObservationHandler mongoTracingObservationHandler(Tracer tracer) { - return new MongoTracingObservationHandler(tracer); - } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfigurationHelper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfigurationHelper.java deleted file mode 100644 index 3ec2032077..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfigurationHelper.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.springframework.data.mongodb.observability; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationRegistry; -import io.micrometer.tracing.Tracer; - -import com.mongodb.client.SynchronousContextProvider; - -/** - * Helper functions to ease registration of Spring Data MongoDB's observability. - */ -public class MongoMetricsConfigurationHelper { - - public static SynchronousContextProvider synchronousContextProvider(Tracer tracer, ObservationRegistry registry) { - return () -> new SynchronousTraceRequestContext(tracer).withObservation(Observation.start("name", registry)); - } - - public static void addObservationHandler(ObservationRegistry registry, Tracer tracer) { - registry.observationConfig().observationHandler(new MongoTracingObservationHandler(tracer)); - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsReactiveConfigurationHelper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsReactiveConfigurationHelper.java deleted file mode 100644 index 6e690b405e..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsReactiveConfigurationHelper.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.springframework.data.mongodb.observability; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationRegistry; -import reactor.core.CoreSubscriber; -import reactor.util.context.Context; - -import com.mongodb.reactivestreams.client.ReactiveContextProvider; - -/** - * Helper functions to ease registration of Spring Data MongoDB's observability. - */ -public class MongoMetricsReactiveConfigurationHelper { - - public static ReactiveContextProvider reactiveContextProvider(ObservationRegistry registry) { - return subscriber -> { - if (subscriber instanceof CoreSubscriber coreSubscriber) { - return new ReactiveTraceRequestContext(coreSubscriber.currentContext()) - .withObservation(Observation.start("name", registry)); - } - return new ReactiveTraceRequestContext(Context.empty()).withObservation(Observation.start("name", registry)); - }; - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservation.java index 298dfd8d04..f25ef081f4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2022 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. @@ -23,7 +23,7 @@ * * @author Marcin Grzejszczak * @author Greg Turnquist - * @since 4.0.0 + * @since 4.0 */ enum MongoObservation implements ObservationDocumentation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservationCommandListener.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservationCommandListener.java index 34d921efc9..e849a7f8d7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservationCommandListener.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservationCommandListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2022 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. @@ -15,11 +15,10 @@ */ package org.springframework.data.mongodb.observability; -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationRegistry; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import com.mongodb.RequestContext; import com.mongodb.event.CommandFailedEvent; @@ -27,27 +26,35 @@ import com.mongodb.event.CommandStartedEvent; import com.mongodb.event.CommandSucceededEvent; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; + /** * Implement MongoDB's {@link CommandListener} using Micrometer's {@link Observation} API. * - * @see https://github.com/openzipkin/brave/blob/release-5.13.0/instrumentation/mongodb/src/main/java/brave/mongodb/TraceMongoCommandListener.java * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak * @author Greg Turnquist - * @since 4.0.0 + * @since 4.0 */ -public final class MongoObservationCommandListener implements CommandListener { +public class MongoObservationCommandListener implements CommandListener { private static final Log log = LogFactory.getLog(MongoObservationCommandListener.class); private final ObservationRegistry observationRegistry; - private MongoHandlerObservationConvention observationConvention; + private final MongoHandlerObservationConvention observationConvention = new DefaultMongoHandlerObservationConvention(); + /** + * Create a new {@link MongoObservationCommandListener} to record {@link Observation}s. + * + * @param observationRegistry must not be {@literal null} + */ public MongoObservationCommandListener(ObservationRegistry observationRegistry) { + Assert.notNull(observationRegistry, "ObservationRegistry must not be null"); + this.observationRegistry = observationRegistry; - this.observationConvention = new DefaultMongoHandlerObservationConvention(); } @Override @@ -75,11 +82,26 @@ public void commandStarted(CommandStartedEvent event) { log.debug("Found the following observation passed from the mongo context [" + parent + "]"); } - if (parent == null) { - return; + MongoHandlerContext observationContext = new MongoHandlerContext(event, requestContext); + observationContext.setRemoteServiceName("mongo"); + + Observation observation = MongoObservation.MONGODB_COMMAND_OBSERVATION + .observation(this.observationRegistry, () -> observationContext) // + .observationConvention(this.observationConvention); + + if (parent != null) { + observation.parentObservation(parent); } - setupObservability(event, requestContext); + observation.start(); + + requestContext.put(Observation.class, observation); + requestContext.put(MongoHandlerContext.class, observationContext); + + if (log.isDebugEnabled()) { + log.debug( + "Created a child observation [" + observation + "] for Mongo instrumentation and put it in Mongo context"); + } } @Override @@ -133,6 +155,7 @@ public void commandFailed(CommandFailedEvent event) { * @param context * @return */ + @Nullable private static Observation observationFromContext(RequestContext context) { Observation observation = context.getOrDefault(Observation.class, null); @@ -140,7 +163,7 @@ private static Observation observationFromContext(RequestContext context) { if (observation != null) { if (log.isDebugEnabled()) { - log.debug("Found a observation in mongo context [" + observation + "]"); + log.debug("Found a observation in Mongo context [" + observation + "]"); } return observation; } @@ -151,23 +174,4 @@ private static Observation observationFromContext(RequestContext context) { return null; } - - private void setupObservability(CommandStartedEvent event, RequestContext requestContext) { - - MongoHandlerContext observationContext = new MongoHandlerContext(event, requestContext); - - Observation observation = MongoObservation.MONGODB_COMMAND_OBSERVATION - .observation(this.observationRegistry, () -> observationContext) // - .contextualName(observationContext.getContextualName()) // - .observationConvention(this.observationConvention) // - .start(); - - requestContext.put(Observation.class, observation); - requestContext.put(MongoHandlerContext.class, observationContext); - - if (log.isDebugEnabled()) { - log.debug( - "Created a child observation [" + observation + "] for mongo instrumentation and put it in mongo context"); - } - } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoTracingObservationHandler.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoTracingObservationHandler.java deleted file mode 100644 index d3ef97d051..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoTracingObservationHandler.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2013-2022 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.data.mongodb.observability; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationRegistry; -import io.micrometer.tracing.Span; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.handler.TracingObservationHandler; - -import java.net.InetSocketAddress; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.mongodb.MongoSocketException; -import com.mongodb.connection.ConnectionDescription; -import com.mongodb.event.CommandStartedEvent; - -/** - * A {@link TracingObservationHandler} that handles {@link MongoHandlerContext}. It configures a span specific to Mongo - * operations. - * - * @author Marcin Grzejszczak - * @author Greg Turnquist - * @since 4.0.0 - */ -public class MongoTracingObservationHandler implements TracingObservationHandler { - - private static final Log log = LogFactory.getLog(MongoTracingObservationHandler.class); - - private final Tracer tracer; - - private boolean setRemoteIpAndPortEnabled; - - public MongoTracingObservationHandler(Tracer tracer) { - this.tracer = tracer; - } - - public void register(ObservationRegistry observationRegistry) { - observationRegistry.observationConfig().observationHandler(this); - } - - @Override - public Tracer getTracer() { - return this.tracer; - } - - @Override - public void onStart(MongoHandlerContext context) { - - CommandStartedEvent event = context.getCommandStartedEvent(); - - Span.Builder builder = this.tracer.spanBuilder() // - .name(context.getContextualName()) // - .kind(Span.Kind.CLIENT) // - .remoteServiceName("mongodb-" + event.getDatabaseName()); - - if (this.setRemoteIpAndPortEnabled) { - - ConnectionDescription connectionDescription = event.getConnectionDescription(); - - if (connectionDescription != null) { - - try { - - InetSocketAddress socketAddress = connectionDescription.getServerAddress().getSocketAddress(); - builder.remoteIpAndPort(socketAddress.getAddress().getHostAddress(), socketAddress.getPort()); - } catch (MongoSocketException e) { - if (log.isDebugEnabled()) { - log.debug("Ignored exception when setting remote ip and port", e); - } - } - } - } - - getTracingContext(context).setSpan(builder.start()); - } - - @Override - public void onStop(MongoHandlerContext context) { - - Span span = getRequiredSpan(context); - tagSpan(context, span); - - context.getRequestContext().delete(Observation.class); - context.getRequestContext().delete(MongoHandlerContext.class); - - span.end(); - } - - @Override - public boolean supportsContext(Observation.Context context) { - return context instanceof MongoHandlerContext; - } - - /** - * Should remote ip and port be set on the span. - * - * @return {@code true} when the remote ip and port should be set - */ - public boolean isSetRemoteIpAndPortEnabled() { - return this.setRemoteIpAndPortEnabled; - } - - public void setSetRemoteIpAndPortEnabled(boolean setRemoteIpAndPortEnabled) { - this.setRemoteIpAndPortEnabled = setRemoteIpAndPortEnabled; - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/ReactiveTraceRequestContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/ReactiveTraceRequestContext.java deleted file mode 100644 index b53ea0c56b..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/ReactiveTraceRequestContext.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.springframework.data.mongodb.observability; - -import io.micrometer.observation.Observation; -import reactor.util.context.ContextView; - -import java.util.Map; -import java.util.stream.Collectors; - -class ReactiveTraceRequestContext extends TraceRequestContext { - - ReactiveTraceRequestContext withObservation(Observation value) { - - put(Observation.class, value); - return this; - } - - ReactiveTraceRequestContext(ContextView context) { - super(context.stream().collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue))); - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/SynchronousTraceRequestContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/SynchronousTraceRequestContext.java deleted file mode 100644 index d061755343..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/SynchronousTraceRequestContext.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.springframework.data.mongodb.observability; - -import io.micrometer.observation.Observation; -import io.micrometer.tracing.Span; -import io.micrometer.tracing.TraceContext; -import io.micrometer.tracing.Tracer; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -class SynchronousTraceRequestContext extends TraceRequestContext { - - SynchronousTraceRequestContext(Tracer tracer) { - super(context(tracer)); - } - - SynchronousTraceRequestContext withObservation(Observation value) { - - put(Observation.class, value); - return this; - } - - 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/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/package-info.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/package-info.java new file mode 100644 index 0000000000..d240e12f9e --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/package-info.java @@ -0,0 +1,5 @@ +/** + * Infrastructure to provide driver observability using Micrometer. + */ +@org.springframework.lang.NonNullApi +package org.springframework.data.mongodb.observability; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ImperativeIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ImperativeIntegrationTests.java new file mode 100644 index 0000000000..b0335e74a7 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ImperativeIntegrationTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013-2022 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.data.mongodb.observability; + +import static org.springframework.data.mongodb.test.util.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.repository.Person; +import org.springframework.data.mongodb.repository.PersonRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.test.SampleTestRunner; + +/** + * Collection of tests that log metrics and tracing with an external tracing tool. + * + * @author Greg Turnquist + * @author Mark Paluch + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = TestConfig.class) +public class ImperativeIntegrationTests extends SampleTestRunner { + + @Autowired PersonRepository repository; + + ImperativeIntegrationTests() { + super(SampleRunnerConfig.builder().build()); + } + + @Override + protected MeterRegistry createMeterRegistry() { + return TestConfig.METER_REGISTRY; + } + + @Override + protected ObservationRegistry createObservationRegistry() { + return TestConfig.OBSERVATION_REGISTRY; + } + + @Override + public SampleTestRunnerConsumer yourCode() { + + return (tracer, meterRegistry) -> { + + repository.deleteAll(); + repository.save(new Person("Dave", "Matthews", 42)); + List people = repository.findByLastname("Matthews"); + + assertThat(people).hasSize(1); + assertThat(people.get(0)).extracting("firstname", "lastname").containsExactly("Dave", "Matthews"); + + repository.deleteAll(); + + System.out.println(((SimpleMeterRegistry) meterRegistry).getMetersAsString()); + }; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerForTracingTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerForTracingTests.java index 5a9133e40f..3b41fe80c4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerForTracingTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerForTracingTests.java @@ -15,16 +15,6 @@ */ package org.springframework.data.mongodb.observability; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationRegistry; -import io.micrometer.tracing.Span; -import io.micrometer.tracing.test.simple.SimpleTracer; -import io.micrometer.tracing.test.simple.SpanAssert; -import io.micrometer.tracing.test.simple.TracerAssert; - import org.bson.BsonDocument; import org.bson.BsonString; import org.jetbrains.annotations.NotNull; @@ -33,7 +23,9 @@ import org.springframework.data.mongodb.observability.MongoObservation.HighCardinalityCommandKeyNames; import org.springframework.data.mongodb.observability.MongoObservation.LowCardinalityCommandKeyNames; +import com.mongodb.RequestContext; import com.mongodb.ServerAddress; +import com.mongodb.client.SynchronousContextProvider; import com.mongodb.connection.ClusterId; import com.mongodb.connection.ConnectionDescription; import com.mongodb.connection.ServerId; @@ -41,19 +33,26 @@ import com.mongodb.event.CommandStartedEvent; import com.mongodb.event.CommandSucceededEvent; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.test.simple.SimpleTracer; +import io.micrometer.tracing.test.simple.SpanAssert; +import io.micrometer.tracing.test.simple.TracerAssert; + /** * Series of test cases exercising {@link MongoObservationCommandListener} to ensure proper creation of {@link Span}s. * * @author Marcin Grzejszczak * @author Greg Turnquist - * @since 4.0.0 */ class MongoObservationCommandListenerForTracingTests { SimpleTracer simpleTracer; - MongoTracingObservationHandler handler; - MeterRegistry meterRegistry; ObservationRegistry observationRegistry; @@ -63,12 +62,10 @@ class MongoObservationCommandListenerForTracingTests { void setup() { this.simpleTracer = new SimpleTracer(); - this.handler = new MongoTracingObservationHandler(simpleTracer); this.meterRegistry = new SimpleMeterRegistry(); this.observationRegistry = ObservationRegistry.create(); this.observationRegistry.observationConfig().observationHandler(new DefaultMeterObservationHandler(meterRegistry)); - this.observationRegistry.observationConfig().observationHandler(handler); this.listener = new MongoObservationCommandListener(observationRegistry); } @@ -77,7 +74,7 @@ void setup() { void successfullyCompletedCommandShouldCreateSpanWhenParentSampleInRequestContext() { // given - TraceRequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt(); + RequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt(); // when commandStartedAndSucceeded(traceRequestContext); @@ -86,25 +83,12 @@ void successfullyCompletedCommandShouldCreateSpanWhenParentSampleInRequestContex assertThatMongoSpanIsClientWithTags().hasIpThatIsBlank().hasPortThatIsNotSet(); } - @Test - void successfullyCompletedCommandShouldCreateSpanWithAddressInfoWhenParentSampleInRequestContextAndHandlerAddressInfoEnabled() { - - // given - handler.setSetRemoteIpAndPortEnabled(true); - TraceRequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt(); - - // when - commandStartedAndSucceeded(traceRequestContext); - - // then - assertThatMongoSpanIsClientWithTags().hasIpThatIsNotBlank().hasPortThatIsSet(); - } @Test void commandWithErrorShouldCreateTimerWhenParentSampleInRequestContext() { // given - TraceRequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt(); + RequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt(); // when listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, // @@ -122,23 +106,21 @@ void commandWithErrorShouldCreateTimerWhenParentSampleInRequestContext() { } /** - * Create a parent {@link Observation} then wrap it inside a {@link TraceRequestContext}. + * Create a parent {@link Observation} then wrap it inside a {@link MapRequestContext}. */ @NotNull - private TraceRequestContext createTestRequestContextWithParentObservationAndStartIt() { - - Observation parent = Observation.start("name", observationRegistry); - return TestRequestContext.withObservation(parent); + private RequestContext createTestRequestContextWithParentObservationAndStartIt() { + return ((SynchronousContextProvider) ContextProviderFactory.create(observationRegistry)).getContext(); } /** * Execute MongoDB's {@link com.mongodb.event.CommandListener#commandStarted(CommandStartedEvent)} and * {@link com.mongodb.event.CommandListener#commandSucceeded(CommandSucceededEvent)} operations against the - * {@link TraceRequestContext} in order to inject some test data. + * {@link MapRequestContext} in order to inject some test data. * * @param traceRequestContext */ - private void commandStartedAndSucceeded(TraceRequestContext traceRequestContext) { + private void commandStartedAndSucceeded(RequestContext traceRequestContext) { listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, // new ConnectionDescription( // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerTests.java index 94b454c79f..09818da7d2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerTests.java @@ -15,14 +15,7 @@ */ package org.springframework.data.mongodb.observability; -import static io.micrometer.core.tck.MeterRegistryAssert.assertThat; - -import io.micrometer.common.KeyValues; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationRegistry; +import static io.micrometer.core.tck.MeterRegistryAssert.*; import org.bson.BsonDocument; import org.bson.BsonString; @@ -31,7 +24,9 @@ import org.springframework.data.mongodb.observability.MongoObservation.HighCardinalityCommandKeyNames; import org.springframework.data.mongodb.observability.MongoObservation.LowCardinalityCommandKeyNames; +import com.mongodb.RequestContext; import com.mongodb.ServerAddress; +import com.mongodb.client.SynchronousContextProvider; import com.mongodb.connection.ClusterId; import com.mongodb.connection.ConnectionDescription; import com.mongodb.connection.ServerId; @@ -39,12 +34,18 @@ import com.mongodb.event.CommandStartedEvent; import com.mongodb.event.CommandSucceededEvent; +import io.micrometer.common.KeyValues; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; + /** * Series of test cases exercising {@link MongoObservationCommandListener}. * * @author Marcin Grzejszczak * @author Greg Turnquist - * @since 4.0.0 */ class MongoObservationCommandListenerTests { @@ -87,7 +88,7 @@ void commandStartedShouldNotInstrumentWhenNoRequestContext() { void commandStartedShouldNotInstrumentWhenNoParentSampleInRequestContext() { // when - listener.commandStarted(new CommandStartedEvent(new TraceRequestContext(), 0, null, "some name", "", null)); + listener.commandStarted(new CommandStartedEvent(new MapRequestContext(), 0, null, "some name", "", null)); // then assertThat(meterRegistry).hasNoMetrics(); @@ -98,7 +99,7 @@ void successfullyCompletedCommandShouldCreateTimerWhenParentSampleInRequestConte // given Observation parent = Observation.start("name", observationRegistry); - TraceRequestContext traceRequestContext = TestRequestContext.withObservation(parent); + RequestContext traceRequestContext = getContext(); // when listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, // @@ -119,7 +120,7 @@ void successfullyCompletedCommandWithCollectionHavingCommandNameShouldCreateTime // given Observation parent = Observation.start("name", observationRegistry); - TraceRequestContext traceRequestContext = TestRequestContext.withObservation(parent); + RequestContext traceRequestContext = getContext(); // when listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, // @@ -135,12 +136,13 @@ void successfullyCompletedCommandWithCollectionHavingCommandNameShouldCreateTime assertThatTimerRegisteredWithTags(); } + @Test void successfullyCompletedCommandWithoutClusterInformationShouldCreateTimerWhenParentSampleInRequestContext() { // given Observation parent = Observation.start("name", observationRegistry); - TraceRequestContext traceRequestContext = TestRequestContext.withObservation(parent); + RequestContext traceRequestContext = getContext(); // when listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, null, "database", "insert", @@ -157,7 +159,7 @@ void commandWithErrorShouldCreateTimerWhenParentSampleInRequestContext() { // given Observation parent = Observation.start("name", observationRegistry); - TraceRequestContext traceRequestContext = TestRequestContext.withObservation(parent); + RequestContext traceRequestContext = getContext(); // when listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, // @@ -174,6 +176,10 @@ void commandWithErrorShouldCreateTimerWhenParentSampleInRequestContext() { assertThatTimerRegisteredWithTags(); } + private RequestContext getContext() { + return ((SynchronousContextProvider) ContextProviderFactory.create(observationRegistry)).getContext(); + } + private void assertThatTimerRegisteredWithTags() { assertThat(meterRegistry) // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ReactiveIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ReactiveIntegrationTests.java new file mode 100644 index 0000000000..c969458a87 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ReactiveIntegrationTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2013-2022 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.data.mongodb.observability; + +import static org.springframework.data.mongodb.test.util.Assertions.*; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.repository.Person; +import org.springframework.data.mongodb.repository.ReactivePersonRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.test.SampleTestRunner; +import reactor.test.StepVerifier; +import reactor.util.context.Context; + +/** + * Collection of tests that log metrics and tracing with an external tracing tool. + * + * @author Mark Paluch + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = TestConfig.class) +public class ReactiveIntegrationTests extends SampleTestRunner { + + @Autowired ReactivePersonRepository repository; + + ReactiveIntegrationTests() { + super(SampleRunnerConfig.builder().build()); + } + + @Override + protected MeterRegistry createMeterRegistry() { + return TestConfig.METER_REGISTRY; + } + + @Override + protected ObservationRegistry createObservationRegistry() { + return TestConfig.OBSERVATION_REGISTRY; + } + + @Override + public SampleTestRunnerConsumer yourCode() { + + return (tracer, meterRegistry) -> { + + Observation intermediate = Observation.start("intermediate", createObservationRegistry()); + + repository.deleteAll().then(repository.save(new Person("Dave", "Matthews", 42))) + .contextWrite(Context.of(Observation.class, intermediate)).as(StepVerifier::create).expectNextCount(1) + .verifyComplete(); + + repository.findByLastname("Matthews").contextWrite(Context.of(Observation.class, intermediate)) + .as(StepVerifier::create).assertNext(actual -> { + + assertThat(actual).extracting("firstname", "lastname").containsExactly("Dave", "Matthews"); + }).verifyComplete(); + + System.out.println(((SimpleMeterRegistry) meterRegistry).getMetersAsString()); + }; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestConfig.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestConfig.java new file mode 100644 index 0000000000..13500b5d23 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestConfig.java @@ -0,0 +1,171 @@ +/* + * Copyright 2022 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.data.mongodb.observability; + +import java.util.Properties; + +import org.springframework.beans.factory.config.PropertiesFactoryBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.mongodb.MongoDatabaseFactory; +import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.ReactiveMongoOperations; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; +import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory; +import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.repository.Person; +import org.springframework.data.mongodb.repository.PersonRepository; +import org.springframework.data.mongodb.repository.ReactivePersonRepository; +import org.springframework.data.mongodb.repository.SampleEvaluationContextExtension; +import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean; +import org.springframework.data.mongodb.repository.support.ReactiveMongoRepositoryFactoryBean; +import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClients; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.test.simple.SimpleTracer; + +/** + * @author Mark Paluch + */ +@Configuration +class TestConfig { + + static final MeterRegistry METER_REGISTRY = new SimpleMeterRegistry(); + static final ObservationRegistry OBSERVATION_REGISTRY = ObservationRegistry.create(); + + static { + OBSERVATION_REGISTRY.observationConfig().observationHandler(new DefaultMeterObservationHandler(METER_REGISTRY)); + } + + @Bean + MongoObservationCommandListener mongoObservationCommandListener(ObservationRegistry registry) { + return new MongoObservationCommandListener(registry); + } + + @Bean + MongoDatabaseFactory mongoDatabaseFactory(MongoClientSettings settings) { + return new SimpleMongoClientDatabaseFactory(MongoClients.create(settings), "observable"); + } + + @Bean + ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory(MongoClientSettings settings) { + return new SimpleReactiveMongoDatabaseFactory(com.mongodb.reactivestreams.client.MongoClients.create(settings), + "observable"); + } + + @Bean + MongoClientSettings mongoClientSettings(MongoObservationCommandListener commandListener, + ObservationRegistry observationRegistry) { + + ConnectionString connectionString = new ConnectionString( + String.format("mongodb://%s:%s/?w=majority&uuidrepresentation=javaLegacy", "127.0.0.1", 27017)); + + MongoClientSettings settings = MongoClientSettings.builder() // + .addCommandListener(commandListener) // + .contextProvider(ContextProviderFactory.create(observationRegistry)) // + .applyConnectionString(connectionString) // + .build(); + + return settings; + } + + @Bean + MappingMongoConverter mongoConverter(MongoMappingContext mappingContext, MongoDatabaseFactory factory) { + return new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext); + } + + @Bean + MongoMappingContext mappingContext() { + return new MongoMappingContext(); + } + + @Bean + MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory, MongoConverter mongoConverter) { + + MongoTemplate template = new MongoTemplate(mongoDatabaseFactory, mongoConverter); + return template; + } + + @Bean + ReactiveMongoTemplate reactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory, + MongoConverter mongoConverter) { + + ReactiveMongoTemplate template = new ReactiveMongoTemplate(mongoDatabaseFactory, mongoConverter); + return template; + } + + @Bean + public PropertiesFactoryBean namedQueriesProperties() { + + PropertiesFactoryBean bean = new PropertiesFactoryBean(); + bean.setLocation(new ClassPathResource("META-INF/mongo-named-queries.properties")); + return bean; + } + + @Bean + MongoRepositoryFactoryBean personRepositoryFactoryBean(MongoOperations operations, + Properties namedQueriesProperties) { + + MongoRepositoryFactoryBean factoryBean = new MongoRepositoryFactoryBean<>( + PersonRepository.class); + factoryBean.setNamedQueries(new PropertiesBasedNamedQueries(namedQueriesProperties)); + factoryBean.setMongoOperations(operations); + factoryBean.setCreateIndexesForQueryMethods(true); + return factoryBean; + } + + @Bean + ReactiveMongoRepositoryFactoryBean reactivePersonRepositoryFactoryBean( + ReactiveMongoOperations operations, Properties namedQueriesProperties) { + + ReactiveMongoRepositoryFactoryBean factoryBean = new ReactiveMongoRepositoryFactoryBean<>( + ReactivePersonRepository.class); + factoryBean.setNamedQueries(new PropertiesBasedNamedQueries(namedQueriesProperties)); + factoryBean.setReactiveMongoOperations(operations); + factoryBean.setCreateIndexesForQueryMethods(true); + return factoryBean; + } + + @Bean + SampleEvaluationContextExtension contextExtension() { + return new SampleEvaluationContextExtension(); + } + + @Bean + ObservationRegistry registry() { + return OBSERVATION_REGISTRY; + } + + @Bean + Tracer tracer() { + return new SimpleTracer(); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestRequestContext.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestRequestContext.java deleted file mode 100644 index 4ff0721d3a..0000000000 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestRequestContext.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.springframework.data.mongodb.observability; - -import io.micrometer.observation.Observation; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -class TestRequestContext extends TraceRequestContext { - - static TestRequestContext withObservation(Observation value) { - return new TestRequestContext(value); - } - - private TestRequestContext(Observation value) { - super(context(value)); - } - - private static Map context(Observation value) { - - Map map = new ConcurrentHashMap<>(); - - map.put(Observation.class, value); - - return map; - } -} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ZipkinIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ZipkinIntegrationTests.java deleted file mode 100644 index 7e66a0908e..0000000000 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ZipkinIntegrationTests.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2013-2022 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.data.mongodb.observability; - -import static org.springframework.data.mongodb.test.util.Assertions.assertThat; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationHandler; -import io.micrometer.observation.ObservationRegistry; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.test.SampleTestRunner; -import io.micrometer.tracing.test.reporter.BuildingBlocks; -import io.micrometer.tracing.test.simple.SimpleTracer; - -import java.io.IOException; -import java.util.Deque; -import java.util.List; -import java.util.function.BiConsumer; - -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.PropertiesFactoryBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; -import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.data.mongodb.core.mapping.MongoMappingContext; -import org.springframework.data.mongodb.repository.Person; -import org.springframework.data.mongodb.repository.PersonRepository; -import org.springframework.data.mongodb.repository.SampleEvaluationContextExtension; -import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; -import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean; -import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import com.mongodb.ConnectionString; -import com.mongodb.ContextProvider; -import com.mongodb.MongoClientSettings; -import com.mongodb.WriteConcern; -import com.mongodb.client.MongoClients; -import com.mongodb.client.SynchronousContextProvider; - -/** - * Collection of tests that log metrics and tracing with an external tracing tool. Since this external tool must be up - * and running after the test is completed, this test is ONLY run manually. Needed: - * {@code docker run -p 9411:9411 openzipkin/zipkin} and {@code docker run -p 27017:27017 mongo:latest} (either from - * Docker Desktop or within separate shells). - * - * @author Greg Turnquist - * @since 4.0.0 - */ -@ExtendWith(SpringExtension.class) -@ContextConfiguration -public class ZipkinIntegrationTests extends SampleTestRunner { - - private static final MeterRegistry METER_REGISTRY = new SimpleMeterRegistry(); - private static final ObservationRegistry OBSERVATION_REGISTRY = ObservationRegistry.create(); - - static { - OBSERVATION_REGISTRY.observationConfig().observationHandler(new DefaultMeterObservationHandler(METER_REGISTRY)); - } - - @Autowired PersonRepository repository; - - ZipkinIntegrationTests() { - super(SampleRunnerConfig.builder().build()); - } - - @Override - protected MeterRegistry createMeterRegistry() { - return METER_REGISTRY; - } - - @Override - protected ObservationRegistry createObservationRegistry() { - return OBSERVATION_REGISTRY; - } - - @Override - public BiConsumer>> customizeObservationHandlers() { - - return (buildingBlocks, observationHandlers) -> observationHandlers - .addLast(new MongoTracingObservationHandler(buildingBlocks.getTracer())); - } - - @Override - public TracingSetup[] getTracingSetup() { - return new TracingSetup[] { TracingSetup.ZIPKIN_BRAVE }; - } - - @Override - public SampleTestRunnerConsumer yourCode() { - - return (tracer, meterRegistry) -> { - - repository.deleteAll(); - repository.save(new Person("Dave", "Matthews", 42)); - List people = repository.findByLastname("Matthews"); - - assertThat(people).hasSize(1); - assertThat(people.get(0)).extracting("firstname", "lastname").containsExactly("Dave", "Matthews"); - - repository.deleteAll(); - - System.out.println(((SimpleMeterRegistry) meterRegistry).getMetersAsString()); - }; - } - - @Configuration - @EnableMongoRepositories - static class TestConfig { - - @Bean - MongoObservationCommandListener mongoObservationCommandListener(ObservationRegistry registry) { - return new MongoObservationCommandListener(registry); - } - - @Bean - MongoDatabaseFactory mongoDatabaseFactory(MongoClientSettings settings) { - return new SimpleMongoClientDatabaseFactory(MongoClients.create(settings), "observable"); - } - - @Bean - MongoClientSettings mongoClientSettings(MongoObservationCommandListener commandListener, - ContextProvider contextProvider) { - - ConnectionString connectionString = new ConnectionString( - String.format("mongodb://%s:%s/?w=majority&uuidrepresentation=javaLegacy", "127.0.0.1", 27017)); - - MongoClientSettings settings = MongoClientSettings.builder() // - .addCommandListener(commandListener) // - .contextProvider(contextProvider) // - .applyConnectionString(connectionString) // - .build(); - - return settings; - } - - @Bean - SynchronousContextProvider contextProvider(ObservationRegistry registry) { - return () -> TestRequestContext.withObservation(Observation.start("name", registry)); - } - - @Bean - MappingMongoConverter mongoConverter(MongoMappingContext mappingContext, MongoDatabaseFactory factory) { - return new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext); - } - - @Bean - MongoMappingContext mappingContext() { - return new MongoMappingContext(); - } - - @Bean - MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory, MongoConverter mongoConverter) { - - MongoTemplate template = new MongoTemplate(mongoDatabaseFactory, mongoConverter); - template.setWriteConcern(WriteConcern.JOURNALED); - return template; - } - - @Bean - public PropertiesFactoryBean namedQueriesProperties() { - - PropertiesFactoryBean bean = new PropertiesFactoryBean(); - bean.setLocation(new ClassPathResource("META-INF/mongo-named-queries.properties")); - return bean; - } - - @Bean - MongoRepositoryFactoryBean repositoryFactoryBean(MongoOperations operations, - PropertiesFactoryBean namedQueriesProperties) throws IOException { - - MongoRepositoryFactoryBean factoryBean = new MongoRepositoryFactoryBean<>( - PersonRepository.class); - factoryBean.setMongoOperations(operations); - factoryBean.setNamedQueries(new PropertiesBasedNamedQueries(namedQueriesProperties.getObject())); - factoryBean.setCreateIndexesForQueryMethods(true); - return factoryBean; - } - - @Bean - SampleEvaluationContextExtension contextExtension() { - return new SampleEvaluationContextExtension(); - } - - @Bean - ObservationRegistry registry() { - return OBSERVATION_REGISTRY; - } - - @Bean - Tracer tracer() { - return new SimpleTracer(); - } - - } -}