From e9ac77c058a45a94b98bc0b75c41d7c9877ddc83 Mon Sep 17 00:00:00 2001 From: "Greg L. Turnquist" Date: Wed, 19 Oct 2022 10:46:54 -0500 Subject: [PATCH] Improve configuration support for Observability integration. Closes: #4216 --- .../EnableMongoObservability.java | 16 ++++ .../MongoMetricsConfiguration.java | 21 +++++ .../MongoMetricsConfigurationHelper.java | 21 +++++ ...ngoMetricsReactiveConfigurationHelper.java | 24 ++++++ .../MongoTracingObservationHandler.java | 5 ++ .../ReactiveTraceRequestContext.java | 20 +++++ .../SynchronousTraceRequestContext.java | 38 +++++++++ .../observability/TraceRequestContext.java | 77 +++++++++++++++++ ...rvationCommandListenerForTracingTests.java | 28 +++---- .../MongoObservationCommandListenerTests.java | 26 +++--- .../observability/TestRequestContext.java | 82 ++++-------------- .../observability/ZipkinIntegrationTests.java | 40 ++++++--- .../asciidoc/reference/observability.adoc | 84 +++++++++++++++++++ 13 files changed, 375 insertions(+), 107 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/EnableMongoObservability.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfiguration.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfigurationHelper.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsReactiveConfigurationHelper.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/ReactiveTraceRequestContext.java create 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/TraceRequestContext.java 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 new file mode 100644 index 000000000..6d6b8dde2 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/EnableMongoObservability.java @@ -0,0 +1,16 @@ +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/MongoMetricsConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfiguration.java new file mode 100644 index 000000000..75e41fd3d --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfiguration.java @@ -0,0 +1,21 @@ +package org.springframework.data.mongodb.observability; + +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.Tracer; +import org.springframework.context.annotation.Bean; + +/** + * Class to configure needed beans for MongoDB + Micrometer. + */ +public class MongoMetricsConfiguration { + + @Bean + 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 new file mode 100644 index 000000000..3ec203207 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfigurationHelper.java @@ -0,0 +1,21 @@ +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 new file mode 100644 index 000000000..6e690b405 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsReactiveConfigurationHelper.java @@ -0,0 +1,24 @@ +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/MongoTracingObservationHandler.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoTracingObservationHandler.java index aae9d9624..d3ef97d05 100644 --- 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 @@ -16,6 +16,7 @@ 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; @@ -49,6 +50,10 @@ public class MongoTracingObservationHandler implements TracingObservationHandler this.tracer = tracer; } + public void register(ObservationRegistry observationRegistry) { + observationRegistry.observationConfig().observationHandler(this); + } + @Override public Tracer getTracer() { return this.tracer; 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 new file mode 100644 index 000000000..b53ea0c56 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/ReactiveTraceRequestContext.java @@ -0,0 +1,20 @@ +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 new file mode 100644 index 000000000..d06175534 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/SynchronousTraceRequestContext.java @@ -0,0 +1,38 @@ +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/TraceRequestContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/TraceRequestContext.java new file mode 100644 index 000000000..7c35764ca --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/TraceRequestContext.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 java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import com.mongodb.RequestContext; + +/** + * A {@link Map}-based {@link RequestContext}. + * + * @author Marcin Grzejszczak + * @author Greg Turnquist + * @since 4.0.0 + */ +class TraceRequestContext implements RequestContext { + + private final Map map; + + public TraceRequestContext() { + this(new HashMap<>()); + } + + public TraceRequestContext(Map context) { + this.map = context; + } + + @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-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 1705281fd..5a9133e40 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 @@ -77,10 +77,10 @@ class MongoObservationCommandListenerForTracingTests { void successfullyCompletedCommandShouldCreateSpanWhenParentSampleInRequestContext() { // given - TestRequestContext testRequestContext = createTestRequestContextWithParentObservationAndStartIt(); + TraceRequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt(); // when - commandStartedAndSucceeded(testRequestContext); + commandStartedAndSucceeded(traceRequestContext); // then assertThatMongoSpanIsClientWithTags().hasIpThatIsBlank().hasPortThatIsNotSet(); @@ -91,10 +91,10 @@ class MongoObservationCommandListenerForTracingTests { // given handler.setSetRemoteIpAndPortEnabled(true); - TestRequestContext testRequestContext = createTestRequestContextWithParentObservationAndStartIt(); + TraceRequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt(); // when - commandStartedAndSucceeded(testRequestContext); + commandStartedAndSucceeded(traceRequestContext); // then assertThatMongoSpanIsClientWithTags().hasIpThatIsNotBlank().hasPortThatIsSet(); @@ -104,10 +104,10 @@ class MongoObservationCommandListenerForTracingTests { void commandWithErrorShouldCreateTimerWhenParentSampleInRequestContext() { // given - TestRequestContext testRequestContext = createTestRequestContextWithParentObservationAndStartIt(); + TraceRequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt(); // when - listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, // + listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, // new ConnectionDescription( // new ServerId( // new ClusterId("description"), // @@ -115,17 +115,17 @@ class MongoObservationCommandListenerForTracingTests { "database", "insert", // new BsonDocument("collection", new BsonString("user")))); listener.commandFailed( // - new CommandFailedEvent(testRequestContext, 0, null, "insert", 0, new IllegalAccessException())); + new CommandFailedEvent(traceRequestContext, 0, null, "insert", 0, new IllegalAccessException())); // then assertThatMongoSpanIsClientWithTags().assertThatThrowable().isInstanceOf(IllegalAccessException.class); } /** - * Create a parent {@link Observation} then wrap it inside a {@link TestRequestContext}. + * Create a parent {@link Observation} then wrap it inside a {@link TraceRequestContext}. */ @NotNull - private TestRequestContext createTestRequestContextWithParentObservationAndStartIt() { + private TraceRequestContext createTestRequestContextWithParentObservationAndStartIt() { Observation parent = Observation.start("name", observationRegistry); return TestRequestContext.withObservation(parent); @@ -134,13 +134,13 @@ class MongoObservationCommandListenerForTracingTests { /** * Execute MongoDB's {@link com.mongodb.event.CommandListener#commandStarted(CommandStartedEvent)} and * {@link com.mongodb.event.CommandListener#commandSucceeded(CommandSucceededEvent)} operations against the - * {@link TestRequestContext} in order to inject some test data. + * {@link TraceRequestContext} in order to inject some test data. * - * @param testRequestContext + * @param traceRequestContext */ - private void commandStartedAndSucceeded(TestRequestContext testRequestContext) { + private void commandStartedAndSucceeded(TraceRequestContext traceRequestContext) { - listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, // + listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, // new ConnectionDescription( // new ServerId( // new ClusterId("description"), // @@ -148,7 +148,7 @@ class MongoObservationCommandListenerForTracingTests { "database", "insert", // new BsonDocument("collection", new BsonString("user")))); - listener.commandSucceeded(new CommandSucceededEvent(testRequestContext, 0, null, "insert", null, 0)); + listener.commandSucceeded(new CommandSucceededEvent(traceRequestContext, 0, null, "insert", null, 0)); } /** 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 ab47f1ca3..94b454c79 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 @@ -87,7 +87,7 @@ class MongoObservationCommandListenerTests { void commandStartedShouldNotInstrumentWhenNoParentSampleInRequestContext() { // when - listener.commandStarted(new CommandStartedEvent(new TestRequestContext(), 0, null, "some name", "", null)); + listener.commandStarted(new CommandStartedEvent(new TraceRequestContext(), 0, null, "some name", "", null)); // then assertThat(meterRegistry).hasNoMetrics(); @@ -98,17 +98,17 @@ class MongoObservationCommandListenerTests { // given Observation parent = Observation.start("name", observationRegistry); - TestRequestContext testRequestContext = TestRequestContext.withObservation(parent); + TraceRequestContext traceRequestContext = TestRequestContext.withObservation(parent); // when - listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, // + listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, // new ConnectionDescription( // new ServerId( // new ClusterId("description"), // new ServerAddress("localhost", 1234))), "database", "insert", // new BsonDocument("collection", new BsonString("user")))); - listener.commandSucceeded(new CommandSucceededEvent(testRequestContext, 0, null, "insert", null, 0)); + listener.commandSucceeded(new CommandSucceededEvent(traceRequestContext, 0, null, "insert", null, 0)); // then assertThatTimerRegisteredWithTags(); @@ -119,17 +119,17 @@ class MongoObservationCommandListenerTests { // given Observation parent = Observation.start("name", observationRegistry); - TestRequestContext testRequestContext = TestRequestContext.withObservation(parent); + TraceRequestContext traceRequestContext = TestRequestContext.withObservation(parent); // when - listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, // + listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, // new ConnectionDescription( // new ServerId( // new ClusterId("description"), // new ServerAddress("localhost", 1234))), // "database", "aggregate", // new BsonDocument("aggregate", new BsonString("user")))); - listener.commandSucceeded(new CommandSucceededEvent(testRequestContext, 0, null, "aggregate", null, 0)); + listener.commandSucceeded(new CommandSucceededEvent(traceRequestContext, 0, null, "aggregate", null, 0)); // then assertThatTimerRegisteredWithTags(); @@ -140,12 +140,12 @@ class MongoObservationCommandListenerTests { // given Observation parent = Observation.start("name", observationRegistry); - TestRequestContext testRequestContext = TestRequestContext.withObservation(parent); + TraceRequestContext traceRequestContext = TestRequestContext.withObservation(parent); // when - listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, null, "database", "insert", + listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, null, "database", "insert", new BsonDocument("collection", new BsonString("user")))); - listener.commandSucceeded(new CommandSucceededEvent(testRequestContext, 0, null, "insert", null, 0)); + listener.commandSucceeded(new CommandSucceededEvent(traceRequestContext, 0, null, "insert", null, 0)); // then assertThat(meterRegistry).hasTimerWithNameAndTags(HighCardinalityCommandKeyNames.MONGODB_COMMAND.asString(), @@ -157,10 +157,10 @@ class MongoObservationCommandListenerTests { // given Observation parent = Observation.start("name", observationRegistry); - TestRequestContext testRequestContext = TestRequestContext.withObservation(parent); + TraceRequestContext traceRequestContext = TestRequestContext.withObservation(parent); // when - listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, // + listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, // new ConnectionDescription( // new ServerId( // new ClusterId("description"), // @@ -168,7 +168,7 @@ class MongoObservationCommandListenerTests { "database", "insert", // new BsonDocument("collection", new BsonString("user")))); listener.commandFailed( // - new CommandFailedEvent(testRequestContext, 0, null, "insert", 0, new IllegalAccessException())); + new CommandFailedEvent(traceRequestContext, 0, null, "insert", 0, new IllegalAccessException())); // then assertThatTimerRegisteredWithTags(); 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 index 6f82e5678..4ff0721d3 100644 --- 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 @@ -1,78 +1,26 @@ -/* - * 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 java.util.HashMap; import java.util.Map; -import java.util.stream.Stream; +import java.util.concurrent.ConcurrentHashMap; -import com.mongodb.RequestContext; - -/** - * A {@link Map}-based {@link RequestContext}. (For test purposes only). - * - * @author Marcin Grzejszczak - * @author Greg Turnquist - * @since 4.0.0 - */ -class TestRequestContext implements RequestContext { - - private final Map map = new HashMap<>(); - - @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(); - } +class TestRequestContext extends TraceRequestContext { static TestRequestContext withObservation(Observation value) { + return new TestRequestContext(value); + } - TestRequestContext testRequestContext = new TestRequestContext(); - testRequestContext.put(Observation.class, value); - return testRequestContext; + 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 index 308e880a0..7e66a0908 100644 --- 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 @@ -23,15 +23,16 @@ 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.Disabled; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.PropertiesFactoryBean; @@ -56,8 +57,8 @@ 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.RequestContext; import com.mongodb.WriteConcern; import com.mongodb.client.MongoClients; import com.mongodb.client.SynchronousContextProvider; @@ -71,7 +72,6 @@ import com.mongodb.client.SynchronousContextProvider; * @author Greg Turnquist * @since 4.0.0 */ -@Disabled("Run this manually to visually test spans in Zipkin") @ExtendWith(SpringExtension.class) @ContextConfiguration public class ZipkinIntegrationTests extends SampleTestRunner { @@ -139,33 +139,41 @@ public class ZipkinIntegrationTests extends SampleTestRunner { } @Bean - MongoDatabaseFactory mongoDatabaseFactory(MongoObservationCommandListener commandListener, - ObservationRegistry registry) { + 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)); - RequestContext requestContext = TestRequestContext.withObservation(Observation.start("name", registry)); - SynchronousContextProvider contextProvider = () -> requestContext; - MongoClientSettings settings = MongoClientSettings.builder() // .addCommandListener(commandListener) // .contextProvider(contextProvider) // .applyConnectionString(connectionString) // .build(); - return new SimpleMongoClientDatabaseFactory(MongoClients.create(settings), "observable"); + return settings; } @Bean - MappingMongoConverter mongoConverter(MongoDatabaseFactory factory) { - - MongoMappingContext mappingContext = new MongoMappingContext(); - mappingContext.afterPropertiesSet(); + 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) { @@ -203,5 +211,11 @@ public class ZipkinIntegrationTests extends SampleTestRunner { ObservationRegistry registry() { return OBSERVATION_REGISTRY; } + + @Bean + Tracer tracer() { + return new SimpleTracer(); + } + } } diff --git a/src/main/asciidoc/reference/observability.adoc b/src/main/asciidoc/reference/observability.adoc index ebdbb6050..37d4fdbbd 100644 --- a/src/main/asciidoc/reference/observability.adoc +++ b/src/main/asciidoc/reference/observability.adoc @@ -8,3 +8,87 @@ include::{root-target}_conventions.adoc[] include::{root-target}_metrics.adoc[] include::{root-target}_spans.adoc[] + +[[observability.registration]] +== Observability Registration + +Spring Data MongoDB currently has the most up-to-date code to support Observability in your MongoDB application. +These changes, however, haven't been picked up by Spring Boot (yet). +Until those changes are applied, if you wish to use Spring Data MongoDB's flavor of Observability, you must carry out the following steps. + +. First of all, you must opt into Spring Data MongoDB's configuration settings by adding the `@EnableMongoObservability` to either your `@SpringBootApplication` class or one of your configuration classes. +. Your project must include *Spring Boot Actuator*. +. Next you must add one of the following bean definitions based on whether you're using non-reactive or reactive Spring Data MongoDB. ++ +.Registering a synchronous (non-reactive) MongoDB Micrometer setup +==== +[source,java] +---- +@Bean +MongoClientSettingsBuilderCustomizer mongoMetricsSynchronousContextProvider(Tracer tracer, + ObservationRegistry registry) { + return (clientSettingsBuilder) -> { + clientSettingsBuilder.contextProvider( // + MongoMetricsConfigurationHelper.synchronousContextProvider(tracer, registry)); + }; +} +---- +==== ++ +.Registering a reactive MongoDB Micrometer setup +==== +[source,java] +---- +@Bean +MongoClientSettingsBuilderCustomizer mongoMetricsReactiveContextProvider(ObservationRegistry registry) { + return (clientSettingsBuilder) -> { + clientSettingsBuilder.contextProvider( // + MongoMetricsReactiveConfigurationHelper.reactiveContextProvider(registry)); + }; +} +---- +==== ++ +IMPORTANT: ONLY add one of these two bean definitions! +. Add the following bean definition to listen for MongoDB command events and record them with Micrometer. ++ +.Registering to listen for MongoDB commands. +==== +[source,java] +---- +@Bean +MongoClientSettingsBuilderCustomizer mongoObservationCommandListenerCustomizer(MongoDBContainer mongoDBContainer, + MongoObservationCommandListener commandListener) { + return (clientSettingsBuilder) -> clientSettingsBuilder // + .addCommandListener(commandListener); +} +---- +==== +. Add the following bean definition to register Spring Data MongoDB's trace observation handler ++ +.Registering +==== +[source,java] +---- +@Bean +ObservationRegistryCustomizer mongoTracingHandlerCustomizer( + MongoTracingObservationHandler handler) { + return handler::register; +} +---- +==== +. Disable Spring Boot's autoconfigured MongoDB command listener and enable tracing manually by adding the following properties to your `application.properties` ++ +.Custom settings to apply +==== +[source] +---- +# Disable Spring Boot's autoconfigured tracing +management.metrics.mongo.command.enabled=false +# Enable it manually +management.tracing.enabled=true +---- +Be sure to add any other relevant settings needed to configure the tracer you are using based upon Micrometer's reference documentation. +==== + +This should do it! You are now running with Spring Data MongoDB's usage of Spring Observability's `Observation` API.