From 2d63d6006d789d13003b93100943db8ed8077595 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 21 Oct 2022 09:00:47 +0200 Subject: [PATCH] Align conventions with OpenTelemetry spec. See: #4216 --- .../observability/ContextProviderFactory.java | 4 - ...aultMongoHandlerObservationConvention.java | 91 +++++++---- .../observability/MongoHandlerContext.java | 33 ++-- .../MongoMetricsConfiguration.java | 21 --- .../observability/MongoObservation.java | 109 +++++++++++-- .../MongoObservationCommandListener.java | 37 ++++- .../ImperativeIntegrationTests.java | 12 ++ ...rvationCommandListenerForTracingTests.java | 152 ------------------ .../MongoObservationCommandListenerTests.java | 18 +-- .../ReactiveIntegrationTests.java | 15 +- .../mongodb/observability/TestConfig.java | 10 +- 11 files changed, 241 insertions(+), 261 deletions(-) delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfiguration.java delete mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerForTracingTests.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 index 2357b334a2..87ee50483f 100644 --- 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 @@ -31,7 +31,6 @@ import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; -import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; import reactor.core.CoreSubscriber; /** @@ -105,9 +104,6 @@ public RequestContext getContext(Subscriber subscriber) { 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); } 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 9f80c0535b..4e9315a194 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 @@ -15,39 +15,84 @@ */ package org.springframework.data.mongodb.observability; -import org.springframework.data.mongodb.observability.MongoObservation.HighCardinalityCommandKeyNames; +import java.net.InetSocketAddress; + import org.springframework.data.mongodb.observability.MongoObservation.LowCardinalityCommandKeyNames; -import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; +import com.mongodb.ConnectionString; +import com.mongodb.ServerAddress; 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 + * @author Mark Paluch + * @since 4.0 */ class DefaultMongoHandlerObservationConvention implements MongoHandlerObservationConvention { @Override public KeyValues getLowCardinalityKeyValues(MongoHandlerContext context) { - KeyValues keyValues = KeyValues.empty(); + KeyValues keyValues = KeyValues.of(LowCardinalityCommandKeyNames.DB_SYSTEM.withValue("mongodb"), + LowCardinalityCommandKeyNames.MONGODB_COMMAND.withValue(context.getCommandName())); + + ConnectionString connectionString = context.getConnectionString(); + if (connectionString != null) { + + keyValues = keyValues + .and(LowCardinalityCommandKeyNames.DB_CONNECTION_STRING.withValue(connectionString.getConnectionString())); + + String user = connectionString.getUsername(); + + if (!ObjectUtils.isEmpty(user)) { + keyValues = keyValues.and(LowCardinalityCommandKeyNames.DB_USER.withValue(user)); + } + + } + + if (!ObjectUtils.isEmpty(context.getDatabaseName())) { + keyValues = keyValues.and(LowCardinalityCommandKeyNames.DB_NAME.withValue(context.getDatabaseName())); + } if (!ObjectUtils.isEmpty(context.getCollectionName())) { keyValues = keyValues .and(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.withValue(context.getCollectionName())); } - KeyValue connectionTag = connectionTag(context.getCommandStartedEvent()); - if (connectionTag != null) { - keyValues = keyValues.and(connectionTag); + ConnectionDescription connectionDescription = context.getCommandStartedEvent().getConnectionDescription(); + + if (connectionDescription != null) { + + ServerAddress serverAddress = connectionDescription.getServerAddress(); + + if (serverAddress != null) { + + keyValues = keyValues.and(LowCardinalityCommandKeyNames.NET_TRANSPORT.withValue("IP.TCP"), + LowCardinalityCommandKeyNames.NET_PEER_NAME.withValue(serverAddress.getHost()), + LowCardinalityCommandKeyNames.NET_PEER_PORT.withValue("" + serverAddress.getPort())); + + InetSocketAddress socketAddress = serverAddress.getSocketAddress(); + + if (socketAddress != null) { + + keyValues = keyValues.and( + LowCardinalityCommandKeyNames.NET_SOCK_PEER_ADDR.withValue(socketAddress.getHostName()), + LowCardinalityCommandKeyNames.NET_SOCK_PEER_PORT.withValue("" + socketAddress.getPort())); + } + } + + ConnectionId connectionId = connectionDescription.getConnectionId(); + if (connectionId != null) { + keyValues = keyValues.and(LowCardinalityCommandKeyNames.MONGODB_CLUSTER_ID + .withValue(connectionId.getServerId().getClusterId().getValue())); + } } return keyValues; @@ -55,36 +100,20 @@ public KeyValues getLowCardinalityKeyValues(MongoHandlerContext context) { @Override public KeyValues getHighCardinalityKeyValues(MongoHandlerContext context) { - - return KeyValues.of( - HighCardinalityCommandKeyNames.MONGODB_COMMAND.withValue(context.getCommandStartedEvent().getCommandName())); + return KeyValues.empty(); } @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(); - - if (connectionDescription != null) { + String collectionName = context.getCollectionName(); + CommandStartedEvent commandStartedEvent = context.getCommandStartedEvent(); - ConnectionId connectionId = connectionDescription.getConnectionId(); - if (connectionId != null) { - return LowCardinalityCommandKeyNames.MONGODB_CLUSTER_ID - .withValue(connectionId.getServerId().getClusterId().getValue()); - } + if (ObjectUtils.isEmpty(collectionName)) { + return commandStartedEvent.getCommandName(); } - return null; + return collectionName + "." + commandStartedEvent.getCommandName(); } + } 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 847f0f5c91..e85061a5b1 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 @@ -23,6 +23,7 @@ import org.bson.BsonValue; import org.springframework.lang.Nullable; +import com.mongodb.ConnectionString; import com.mongodb.RequestContext; import com.mongodb.event.CommandFailedEvent; import com.mongodb.event.CommandStartedEvent; @@ -37,9 +38,10 @@ * * @author Marcin Grzejszczak * @author Greg Turnquist - * @since 4.0.0 + * @author Mark Paluch + * @since 4.0 */ -public class MongoHandlerContext extends SenderContext { +class MongoHandlerContext extends SenderContext { /** * @see { "insert", "update", "collMod", "compact", "convertToCapped", "create", "createIndexes", "drop", "dropIndexes", "killCursors", "listIndexes", "reIndex")); + private final @Nullable ConnectionString connectionString; private final CommandStartedEvent commandStartedEvent; private final RequestContext requestContext; private final String collectionName; @@ -58,8 +61,11 @@ public class MongoHandlerContext extends SenderContext { private CommandSucceededEvent commandSucceededEvent; private CommandFailedEvent commandFailedEvent; - public MongoHandlerContext(CommandStartedEvent commandStartedEvent, RequestContext requestContext) { + public MongoHandlerContext(@Nullable ConnectionString connectionString, CommandStartedEvent commandStartedEvent, + RequestContext requestContext) { + super((carrier, key, value) -> {}, Kind.CLIENT); + this.connectionString = connectionString; this.commandStartedEvent = commandStartedEvent; this.requestContext = requestContext; this.collectionName = getCollectionName(commandStartedEvent); @@ -73,17 +79,21 @@ public RequestContext getRequestContext() { return this.requestContext; } + public String getDatabaseName() { + return commandStartedEvent.getDatabaseName(); + } + public String getCollectionName() { return this.collectionName; } - public String getContextualName() { - - if (this.collectionName == null) { - return this.commandStartedEvent.getCommandName(); - } + public String getCommandName() { + return commandStartedEvent.getCommandName(); + } - return this.commandStartedEvent.getCommandName() + " " + this.collectionName; + @Nullable + public ConnectionString getConnectionString() { + return connectionString; } public void setCommandSucceededEvent(CommandSucceededEvent commandSucceededEvent) { @@ -116,7 +126,7 @@ private static String getCollectionName(CommandStartedEvent event) { } // Some other commands, like getMore, have a field like {"collection": collectionName}. - return getNonEmptyBsonString(command.get("collection")); + return command == null ? "" : getNonEmptyBsonString(command.get("collection")); } /** @@ -125,7 +135,7 @@ private static String getCollectionName(CommandStartedEvent event) { * @return trimmed string from {@code bsonValue} or null if the trimmed string was empty or the value wasn't a string */ @Nullable - private static String getNonEmptyBsonString(BsonValue bsonValue) { + private static String getNonEmptyBsonString(@Nullable BsonValue bsonValue) { if (bsonValue == null || !bsonValue.isString()) { return null; @@ -135,4 +145,5 @@ private static String getNonEmptyBsonString(BsonValue bsonValue) { return stringValue.isEmpty() ? null : stringValue; } + } 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 deleted file mode 100644 index 5917fdb864..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfiguration.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.springframework.data.mongodb.observability; - -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 - public MongoObservationCommandListener mongoObservationCommandListener(ObservationRegistry registry) { - return new MongoObservationCommandListener(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 f25ef081f4..7419ce54ba 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 @@ -44,13 +44,9 @@ public KeyName[] getLowCardinalityKeyNames() { @Override public KeyName[] getHighCardinalityKeyNames() { - return HighCardinalityCommandKeyNames.values(); + return new KeyName[0]; } - @Override - public String getPrefix() { - return "spring.data.mongodb"; - } }; /** @@ -58,13 +54,103 @@ public String getPrefix() { */ enum LowCardinalityCommandKeyNames implements KeyName { + /** + * MongoDB database system. + */ + DB_SYSTEM { + @Override + public String asString() { + return "db.system"; + } + }, + + /** + * MongoDB connection string. + */ + DB_CONNECTION_STRING { + @Override + public String asString() { + return "db.connection_string"; + } + }, + + /** + * Network transport. + */ + NET_TRANSPORT { + @Override + public String asString() { + return "net.transport"; + } + }, + + /** + * Name of the database host. + */ + NET_PEER_NAME { + @Override + public String asString() { + return "net.peer.name"; + } + }, + + /** + * Logical remote port number. + */ + NET_PEER_PORT { + @Override + public String asString() { + return "net.peer.port"; + } + }, + + /** + * Mongo peer address. + */ + NET_SOCK_PEER_ADDR { + @Override + public String asString() { + return "net.sock.peer.addr"; + } + }, + + /** + * Mongo peer port. + */ + NET_SOCK_PEER_PORT { + @Override + public String asString() { + return "net.sock.peer.port"; + } + }, + + /** + * MongoDB user. + */ + DB_USER { + @Override + public String asString() { + return "db.user"; + } + }, + + /** + * MongoDB database name. + */ + DB_NAME { + @Override + public String asString() { + return "db.name"; + } + }, + /** * MongoDB collection name. */ MONGODB_COLLECTION { @Override public String asString() { - return "spring.data.mongodb.collection"; + return "db.mongodb.collection"; } }, @@ -76,13 +162,7 @@ public String asString() { public String asString() { return "spring.data.mongodb.cluster_id"; } - } - } - - /** - * Enums related to high cardinality key names for MongoDB commands. - */ - enum HighCardinalityCommandKeyNames implements KeyName { + }, /** * MongoDB command value. @@ -90,8 +170,9 @@ enum HighCardinalityCommandKeyNames implements KeyName { MONGODB_COMMAND { @Override public String asString() { - return "spring.data.mongodb.command"; + return "db.operation"; } } } + } 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 e849a7f8d7..0a344570b1 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 @@ -20,6 +20,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import com.mongodb.ConnectionString; import com.mongodb.RequestContext; import com.mongodb.event.CommandFailedEvent; import com.mongodb.event.CommandListener; @@ -42,6 +43,7 @@ public class MongoObservationCommandListener implements CommandListener { private static final Log log = LogFactory.getLog(MongoObservationCommandListener.class); private final ObservationRegistry observationRegistry; + private final @Nullable ConnectionString connectionString; private final MongoHandlerObservationConvention observationConvention = new DefaultMongoHandlerObservationConvention(); @@ -55,6 +57,23 @@ public MongoObservationCommandListener(ObservationRegistry observationRegistry) Assert.notNull(observationRegistry, "ObservationRegistry must not be null"); this.observationRegistry = observationRegistry; + this.connectionString = null; + } + + /** + * Create a new {@link MongoObservationCommandListener} to record {@link Observation}s. This constructor attaches the + * {@link ConnectionString} to every {@link Observation}. + * + * @param observationRegistry must not be {@literal null} + * @param connectionString must not be {@literal null} + */ + public MongoObservationCommandListener(ObservationRegistry observationRegistry, ConnectionString connectionString) { + + Assert.notNull(observationRegistry, "ObservationRegistry must not be null"); + Assert.notNull(connectionString, "ConnectionString must not be null"); + + this.observationRegistry = observationRegistry; + this.connectionString = connectionString; } @Override @@ -82,7 +101,7 @@ public void commandStarted(CommandStartedEvent event) { log.debug("Found the following observation passed from the mongo context [" + parent + "]"); } - MongoHandlerContext observationContext = new MongoHandlerContext(event, requestContext); + MongoHandlerContext observationContext = new MongoHandlerContext(connectionString, event, requestContext); observationContext.setRemoteServiceName("mongo"); Observation observation = MongoObservation.MONGODB_COMMAND_OBSERVATION @@ -107,16 +126,18 @@ public void commandStarted(CommandStartedEvent event) { @Override public void commandSucceeded(CommandSucceededEvent event) { - if (event.getRequestContext() == null) { + RequestContext requestContext = event.getRequestContext(); + + if (requestContext == null) { return; } - Observation observation = event.getRequestContext().getOrDefault(Observation.class, null); + Observation observation = requestContext.getOrDefault(Observation.class, null); if (observation == null) { return; } - MongoHandlerContext context = event.getRequestContext().get(MongoHandlerContext.class); + MongoHandlerContext context = requestContext.get(MongoHandlerContext.class); context.setCommandSucceededEvent(event); if (log.isDebugEnabled()) { @@ -129,16 +150,18 @@ public void commandSucceeded(CommandSucceededEvent event) { @Override public void commandFailed(CommandFailedEvent event) { - if (event.getRequestContext() == null) { + RequestContext requestContext = event.getRequestContext(); + + if (requestContext == null) { return; } - Observation observation = event.getRequestContext().getOrDefault(Observation.class, null); + Observation observation = requestContext.getOrDefault(Observation.class, null); if (observation == null) { return; } - MongoHandlerContext context = event.getRequestContext().get(MongoHandlerContext.class); + MongoHandlerContext context = requestContext.get(MongoHandlerContext.class); context.setCommandFailedEvent(event); if (log.isDebugEnabled()) { 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 index b0335e74a7..72faa9ffa3 100644 --- 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 @@ -29,6 +29,7 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.exporter.FinishedSpan; import io.micrometer.tracing.test.SampleTestRunner; /** @@ -72,6 +73,17 @@ public SampleTestRunnerConsumer yourCode() { repository.deleteAll(); System.out.println(((SimpleMeterRegistry) meterRegistry).getMetersAsString()); + + assertThat(tracer.getFinishedSpans()).hasSize(5).extracting(FinishedSpan::getName).contains("person.delete", + "person.update", "person.find"); + + for (FinishedSpan span : tracer.getFinishedSpans()) { + + assertThat(span.getTags()).containsEntry("db.system", "mongodb").containsEntry("net.transport", "IP.TCP"); + + assertThat(span.getTags()).containsKeys("db.connection_string", "db.name", "db.operation", + "db.mongodb.collection", "net.peer.name", "net.peer.port", "net.sock.peer.addr", "net.sock.peer.port"); + } }; } } 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 deleted file mode 100644 index 3b41fe80c4..0000000000 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerForTracingTests.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2002-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 org.bson.BsonDocument; -import org.bson.BsonString; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -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; -import com.mongodb.event.CommandFailedEvent; -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 - */ -class MongoObservationCommandListenerForTracingTests { - - SimpleTracer simpleTracer; - - MeterRegistry meterRegistry; - ObservationRegistry observationRegistry; - - MongoObservationCommandListener listener; - - @BeforeEach - void setup() { - - this.simpleTracer = new SimpleTracer(); - - this.meterRegistry = new SimpleMeterRegistry(); - this.observationRegistry = ObservationRegistry.create(); - this.observationRegistry.observationConfig().observationHandler(new DefaultMeterObservationHandler(meterRegistry)); - - this.listener = new MongoObservationCommandListener(observationRegistry); - } - - @Test - void successfullyCompletedCommandShouldCreateSpanWhenParentSampleInRequestContext() { - - // given - RequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt(); - - // when - commandStartedAndSucceeded(traceRequestContext); - - // then - assertThatMongoSpanIsClientWithTags().hasIpThatIsBlank().hasPortThatIsNotSet(); - } - - - @Test - void commandWithErrorShouldCreateTimerWhenParentSampleInRequestContext() { - - // given - RequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt(); - - // when - 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.commandFailed( // - 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 MapRequestContext}. - */ - @NotNull - 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 MapRequestContext} in order to inject some test data. - * - * @param traceRequestContext - */ - private void commandStartedAndSucceeded(RequestContext traceRequestContext) { - - 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(traceRequestContext, 0, null, "insert", null, 0)); - } - - /** - * Create a base MongoDB-based {@link SpanAssert} using Micrometer Tracing's fluent API. Other test methods can apply - * additional assertions. - * - * @return - */ - private SpanAssert assertThatMongoSpanIsClientWithTags() { - - return TracerAssert.assertThat(simpleTracer).onlySpan() // - .hasNameEqualTo("insert user") // - .hasKindEqualTo(Span.Kind.CLIENT) // - .hasRemoteServiceNameEqualTo("mongodb-database") // - .hasTag(HighCardinalityCommandKeyNames.MONGODB_COMMAND.asString(), "insert") // - .hasTag(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.asString(), "user") // - .hasTagWithKey(LowCardinalityCommandKeyNames.MONGODB_CLUSTER_ID.asString()); - } -} 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 09818da7d2..9b2477503b 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 @@ -21,7 +21,6 @@ import org.bson.BsonString; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.data.mongodb.observability.MongoObservation.HighCardinalityCommandKeyNames; import org.springframework.data.mongodb.observability.MongoObservation.LowCardinalityCommandKeyNames; import com.mongodb.RequestContext; @@ -91,7 +90,7 @@ void commandStartedShouldNotInstrumentWhenNoParentSampleInRequestContext() { listener.commandStarted(new CommandStartedEvent(new MapRequestContext(), 0, null, "some name", "", null)); // then - assertThat(meterRegistry).hasNoMetrics(); + assertThat(meterRegistry).hasMeterWithName("spring.data.mongodb.command.active"); } @Test @@ -136,7 +135,6 @@ void successfullyCompletedCommandWithCollectionHavingCommandNameShouldCreateTime assertThatTimerRegisteredWithTags(); } - @Test void successfullyCompletedCommandWithoutClusterInformationShouldCreateTimerWhenParentSampleInRequestContext() { @@ -149,9 +147,11 @@ void successfullyCompletedCommandWithoutClusterInformationShouldCreateTimerWhenP new BsonDocument("collection", new BsonString("user")))); listener.commandSucceeded(new CommandSucceededEvent(traceRequestContext, 0, null, "insert", null, 0)); - // then - assertThat(meterRegistry).hasTimerWithNameAndTags(HighCardinalityCommandKeyNames.MONGODB_COMMAND.asString(), - KeyValues.of(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.withValue("user"))); + assertThat(meterRegistry).hasTimerWithNameAndTags(MongoObservation.MONGODB_COMMAND_OBSERVATION.getName(), + KeyValues.of(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.withValue("user"), + LowCardinalityCommandKeyNames.DB_NAME.withValue("database"), + LowCardinalityCommandKeyNames.MONGODB_COMMAND.withValue("insert"), + LowCardinalityCommandKeyNames.DB_SYSTEM.withValue("mongodb")).and("error", "none")); } @Test @@ -183,10 +183,8 @@ private RequestContext getContext() { private void assertThatTimerRegisteredWithTags() { assertThat(meterRegistry) // - .hasTimerWithNameAndTags(HighCardinalityCommandKeyNames.MONGODB_COMMAND.asString(), - KeyValues.of(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.withValue("user"))) // - .hasTimerWithNameAndTagKeys(HighCardinalityCommandKeyNames.MONGODB_COMMAND.asString(), - LowCardinalityCommandKeyNames.MONGODB_CLUSTER_ID.asString()); + .hasTimerWithNameAndTags(MongoObservation.MONGODB_COMMAND_OBSERVATION.getName(), + KeyValues.of(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.withValue("user"))); } } 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 index c969458a87..980c975549 100644 --- 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 @@ -28,6 +28,8 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; +import io.micrometer.tracing.exporter.FinishedSpan; import io.micrometer.tracing.test.SampleTestRunner; import reactor.test.StepVerifier; import reactor.util.context.Context; @@ -64,17 +66,24 @@ public SampleTestRunnerConsumer yourCode() { 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) + repository.deleteAll() // + .then(repository.save(new Person("Dave", "Matthews", 42))) // + .contextWrite(Context.of(ObservationThreadLocalAccessor.KEY, intermediate)) // + .as(StepVerifier::create).expectNextCount(1)// .verifyComplete(); - repository.findByLastname("Matthews").contextWrite(Context.of(Observation.class, intermediate)) + repository.findByLastname("Matthews") // + .contextWrite(Context.of(Observation.class, intermediate)) // .as(StepVerifier::create).assertNext(actual -> { assertThat(actual).extracting("firstname", "lastname").containsExactly("Dave", "Matthews"); }).verifyComplete(); + intermediate.stop(); System.out.println(((SimpleMeterRegistry) meterRegistry).getMetersAsString()); + + assertThat(tracer.getFinishedSpans()).hasSize(5).extracting(FinishedSpan::getName).contains("person.delete", + "person.update", "person.find"); }; } } 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 index 13500b5d23..76d3ee99ce 100644 --- 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 @@ -65,11 +65,6 @@ class TestConfig { 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"); @@ -82,14 +77,13 @@ ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory(MongoClientSettings se } @Bean - MongoClientSettings mongoClientSettings(MongoObservationCommandListener commandListener, - ObservationRegistry observationRegistry) { + MongoClientSettings mongoClientSettings(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) // + .addCommandListener(new MongoObservationCommandListener(observationRegistry, connectionString)) // .contextProvider(ContextProviderFactory.create(observationRegistry)) // .applyConnectionString(connectionString) // .build();