Browse Source

Align conventions with OpenTelemetry spec.

See: #4216
pull/4218/head
Mark Paluch 3 years ago
parent
commit
2d63d6006d
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/ContextProviderFactory.java
  2. 91
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/DefaultMongoHandlerObservationConvention.java
  3. 33
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoHandlerContext.java
  4. 21
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfiguration.java
  5. 109
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservation.java
  6. 37
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservationCommandListener.java
  7. 12
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ImperativeIntegrationTests.java
  8. 152
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerForTracingTests.java
  9. 18
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerTests.java
  10. 15
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ReactiveIntegrationTests.java
  11. 10
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestConfig.java

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/ContextProviderFactory.java

@ -31,7 +31,6 @@ import com.mongodb.reactivestreams.client.ReactiveContextProvider; @@ -31,7 +31,6 @@ 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;
/**
@ -105,9 +104,6 @@ public class ContextProviderFactory { @@ -105,9 +104,6 @@ public class ContextProviderFactory {
Map<Object, Object> 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);
}

91
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/DefaultMongoHandlerObservationConvention.java

@ -15,39 +15,84 @@ @@ -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 @@ class DefaultMongoHandlerObservationConvention implements MongoHandlerObservatio @@ -55,36 +100,20 @@ class DefaultMongoHandlerObservationConvention implements MongoHandlerObservatio
@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();
}
}

33
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoHandlerContext.java

@ -23,6 +23,7 @@ import org.bson.BsonDocument; @@ -23,6 +23,7 @@ import org.bson.BsonDocument;
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 @@ import io.micrometer.observation.transport.SenderContext; @@ -37,9 +38,10 @@ import io.micrometer.observation.transport.SenderContext;
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
* @author Mark Paluch
* @since 4.0
*/
public class MongoHandlerContext extends SenderContext<Object> {
class MongoHandlerContext extends SenderContext<Object> {
/**
* @see <a href=
@ -51,6 +53,7 @@ public class MongoHandlerContext extends SenderContext<Object> { @@ -51,6 +53,7 @@ public class MongoHandlerContext extends SenderContext<Object> {
"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<Object> { @@ -58,8 +61,11 @@ public class MongoHandlerContext extends SenderContext<Object> {
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 class MongoHandlerContext extends SenderContext<Object> { @@ -73,17 +79,21 @@ public class MongoHandlerContext extends SenderContext<Object> {
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 @@ public class MongoHandlerContext extends SenderContext<Object> { @@ -116,7 +126,7 @@ public class MongoHandlerContext extends SenderContext<Object> {
}
// 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 @@ public class MongoHandlerContext extends SenderContext<Object> { @@ -125,7 +135,7 @@ public class MongoHandlerContext extends SenderContext<Object> {
* @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 @@ public class MongoHandlerContext extends SenderContext<Object> { @@ -135,4 +145,5 @@ public class MongoHandlerContext extends SenderContext<Object> {
return stringValue.isEmpty() ? null : stringValue;
}
}

21
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoMetricsConfiguration.java

@ -1,21 +0,0 @@ @@ -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);
}
}

109
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservation.java

@ -44,13 +44,9 @@ enum MongoObservation implements ObservationDocumentation { @@ -44,13 +44,9 @@ enum MongoObservation implements ObservationDocumentation {
@Override
public KeyName[] getHighCardinalityKeyNames() {
return HighCardinalityCommandKeyNames.values();
return new KeyName[0];
}
@Override
public String getPrefix() {
return "spring.data.mongodb";
}
};
/**
@ -58,13 +54,103 @@ enum MongoObservation implements ObservationDocumentation { @@ -58,13 +54,103 @@ enum MongoObservation implements ObservationDocumentation {
*/
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 @@ enum MongoObservation implements ObservationDocumentation { @@ -76,13 +162,7 @@ enum MongoObservation implements ObservationDocumentation {
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 MongoObservation implements ObservationDocumentation { @@ -90,8 +170,9 @@ enum MongoObservation implements ObservationDocumentation {
MONGODB_COMMAND {
@Override
public String asString() {
return "spring.data.mongodb.command";
return "db.operation";
}
}
}
}

37
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservationCommandListener.java

@ -20,6 +20,7 @@ import org.apache.commons.logging.LogFactory; @@ -20,6 +20,7 @@ import org.apache.commons.logging.LogFactory;
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 { @@ -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 class MongoObservationCommandListener implements CommandListener { @@ -55,6 +57,23 @@ public class MongoObservationCommandListener implements CommandListener {
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 class MongoObservationCommandListener implements CommandListener { @@ -82,7 +101,7 @@ public class MongoObservationCommandListener implements CommandListener {
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 class MongoObservationCommandListener implements CommandListener { @@ -107,16 +126,18 @@ public class MongoObservationCommandListener implements CommandListener {
@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 class MongoObservationCommandListener implements CommandListener { @@ -129,16 +150,18 @@ public class MongoObservationCommandListener implements CommandListener {
@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()) {

12
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ImperativeIntegrationTests.java

@ -29,6 +29,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -29,6 +29,7 @@ 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.exporter.FinishedSpan;
import io.micrometer.tracing.test.SampleTestRunner;
/**
@ -72,6 +73,17 @@ public class ImperativeIntegrationTests extends SampleTestRunner { @@ -72,6 +73,17 @@ public class ImperativeIntegrationTests extends SampleTestRunner {
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");
}
};
}
}

152
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerForTracingTests.java

@ -1,152 +0,0 @@ @@ -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());
}
}

18
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerTests.java

@ -21,7 +21,6 @@ import org.bson.BsonDocument; @@ -21,7 +21,6 @@ import org.bson.BsonDocument;
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 @@ class MongoObservationCommandListenerTests { @@ -91,7 +90,7 @@ class MongoObservationCommandListenerTests {
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 @@ class MongoObservationCommandListenerTests { @@ -136,7 +135,6 @@ class MongoObservationCommandListenerTests {
assertThatTimerRegisteredWithTags();
}
@Test
void successfullyCompletedCommandWithoutClusterInformationShouldCreateTimerWhenParentSampleInRequestContext() {
@ -149,9 +147,11 @@ class MongoObservationCommandListenerTests { @@ -149,9 +147,11 @@ class MongoObservationCommandListenerTests {
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 @@ class MongoObservationCommandListenerTests { @@ -183,10 +183,8 @@ class MongoObservationCommandListenerTests {
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")));
}
}

15
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/ReactiveIntegrationTests.java

@ -28,6 +28,8 @@ import io.micrometer.core.instrument.MeterRegistry; @@ -28,6 +28,8 @@ 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.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 class ReactiveIntegrationTests extends SampleTestRunner { @@ -64,17 +66,24 @@ public class ReactiveIntegrationTests extends SampleTestRunner {
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");
};
}
}

10
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestConfig.java

@ -65,11 +65,6 @@ class TestConfig { @@ -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 @@ class TestConfig { @@ -82,14 +77,13 @@ class TestConfig {
}
@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();

Loading…
Cancel
Save