diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml index f3e6d3ef2ff..b2552959cdf 100644 --- a/config/checkstyle/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -60,7 +60,7 @@ - + diff --git a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java index 7e454debedd..98b9ab800de 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java +++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java @@ -49,6 +49,7 @@ import com.mongodb.internal.diagnostics.logging.Logger; import com.mongodb.internal.diagnostics.logging.Loggers; import com.mongodb.internal.logging.StructuredLogger; +import com.mongodb.internal.observability.micrometer.MongodbContext; import com.mongodb.internal.observability.micrometer.Span; import com.mongodb.internal.session.SessionContext; import com.mongodb.internal.time.Timeout; @@ -94,8 +95,7 @@ import static com.mongodb.internal.connection.ProtocolHelper.getSnapshotTimestamp; import static com.mongodb.internal.connection.ProtocolHelper.isCommandOk; import static com.mongodb.internal.logging.LogMessage.Level.DEBUG; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.HighCardinalityKeyNames.QUERY_TEXT; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.RESPONSE_STATUS_CODE; + import static com.mongodb.internal.thread.InterruptionUtil.translateInterruptedException; import static java.util.Arrays.asList; @@ -454,6 +454,9 @@ private T sendAndReceiveInternal(final CommandMessage message, final Decoder () -> getDescription().getServerAddress(), () -> getDescription().getConnectionId() ); + if (tracingSpan != null) { + tracingSpan.openScope(); + } boolean isLoggingCommandNeeded = isLoggingCommandNeeded(); boolean isTracingCommandPayloadNeeded = tracingSpan != null && operationContext.getTracingManager().isCommandPayloadEnabled(); @@ -473,7 +476,7 @@ private T sendAndReceiveInternal(final CommandMessage message, final Decoder commandEventSender = new NoOpCommandEventSender(); } if (isTracingCommandPayloadNeeded) { - tracingSpan.tagHighCardinality(QUERY_TEXT.asString(), commandDocument); + tracingSpan.setQueryText(commandDocument); } try { @@ -481,6 +484,8 @@ private T sendAndReceiveInternal(final CommandMessage message, final Decoder } catch (Exception e) { if (tracingSpan != null) { tracingSpan.error(e); + tracingSpan.closeScope(); + tracingSpan.end(); } commandEventSender.sendFailedEvent(e); throw e; @@ -492,6 +497,7 @@ private T sendAndReceiveInternal(final CommandMessage message, final Decoder } else { commandEventSender.sendSucceededEventForOneWayCommand(); if (tracingSpan != null) { + tracingSpan.closeScope(); tracingSpan.end(); } return null; @@ -585,13 +591,17 @@ private T receiveCommandMessageResponse(final Decoder decoder, final Comm } if (tracingSpan != null) { if (e instanceof MongoCommandException) { - tracingSpan.tagLowCardinality(RESPONSE_STATUS_CODE.withValue(String.valueOf(((MongoCommandException) e).getErrorCode()))); + MongodbContext ctx = tracingSpan.getMongodbContext(); + if (ctx != null) { + ctx.setResponseStatusCode(String.valueOf(((MongoCommandException) e).getErrorCode())); + } } tracingSpan.error(e); } throw e; } finally { if (tracingSpan != null) { + tracingSpan.closeScope(); tracingSpan.end(); } } @@ -639,7 +649,7 @@ private void sendAndReceiveAsyncInternal(final CommandMessage message, final commandEventSender = new NoOpCommandEventSender(); } if (isTracingCommandPayloadNeeded) { - tracingSpan.tagHighCardinality(QUERY_TEXT.asString(), commandDocument); + tracingSpan.setQueryText(commandDocument); } final Span commandSpan = tracingSpan; @@ -647,8 +657,10 @@ private void sendAndReceiveAsyncInternal(final CommandMessage message, final try { if (t != null) { if (t instanceof MongoCommandException) { - commandSpan.tagLowCardinality( - RESPONSE_STATUS_CODE.withValue(String.valueOf(((MongoCommandException) t).getErrorCode()))); + MongodbContext ctx = commandSpan.getMongodbContext(); + if (ctx != null) { + ctx.setResponseStatusCode(String.valueOf(((MongoCommandException) t).getErrorCode())); + } } commandSpan.error(t); } diff --git a/driver-core/src/main/com/mongodb/internal/observability/micrometer/DefaultMongodbObservationConvention.java b/driver-core/src/main/com/mongodb/internal/observability/micrometer/DefaultMongodbObservationConvention.java new file mode 100644 index 00000000000..b7b13ea5f1f --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/observability/micrometer/DefaultMongodbObservationConvention.java @@ -0,0 +1,166 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * 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 + * + * http://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 com.mongodb.internal.observability.micrometer; + +import io.micrometer.common.KeyValues; +import io.micrometer.observation.GlobalObservationConvention; +import io.micrometer.observation.Observation; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Default {@link ObservationConvention} for MongoDB observations. + *

+ * Reads domain fields from {@link MongodbContext} and produces the standard MongoDB + * low-cardinality and high-cardinality key-values. Users can override this by registering + * a {@code GlobalObservationConvention} on their {@code ObservationRegistry}. + *

+ * + * @since 5.7 + */ +public class DefaultMongodbObservationConvention implements GlobalObservationConvention { + + @Override + public boolean supportsContext(final Observation.Context context) { + return context instanceof MongodbContext; + } + + @Override + public KeyValues getLowCardinalityKeyValues(final MongodbContext context) { + if (context.getObservationType() == MongodbObservation.MONGODB_OPERATION) { + return getOperationLowCardinalityKeyValues(context); + } else { + return getCommandLowCardinalityKeyValues(context); + } + } + + @Override + public KeyValues getHighCardinalityKeyValues(final MongodbContext context) { + if (context.getObservationType() == MongodbObservation.MONGODB_COMMAND) { + return getCommandHighCardinalityKeyValues(context); + } + return KeyValues.empty(); + } + + private KeyValues getOperationLowCardinalityKeyValues(final MongodbContext context) { + String commandName = context.getCommandName(); + String databaseName = context.getDatabaseName(); + String collectionName = context.getCollectionName(); + + KeyValues kv = KeyValues.of( + MongodbObservation.OperationLowCardinalityKeyNames.SYSTEM.withValue("mongodb")); + + if (databaseName != null) { + kv = kv.and(MongodbObservation.OperationLowCardinalityKeyNames.NAMESPACE.withValue(databaseName)); + } + if (collectionName != null) { + kv = kv.and(MongodbObservation.OperationLowCardinalityKeyNames.COLLECTION.withValue(collectionName)); + } + if (commandName != null) { + String dbName = databaseName != null ? databaseName : ""; + String summary = commandName + " " + dbName + + (collectionName != null ? "." + collectionName : ""); + kv = kv.and( + MongodbObservation.OperationLowCardinalityKeyNames.OPERATION_NAME.withValue(commandName), + MongodbObservation.OperationLowCardinalityKeyNames.OPERATION_SUMMARY.withValue(summary)); + } + return kv; + } + + private KeyValues getCommandLowCardinalityKeyValues(final MongodbContext context) { + String commandName = context.getCommandName(); + String databaseName = context.getDatabaseName(); + String collectionName = context.getCollectionName(); + String cmdName = commandName != null ? commandName : ""; + String dbName = databaseName != null ? databaseName : ""; + String summary = cmdName + " " + dbName + + (collectionName != null ? "." + collectionName : ""); + + KeyValues kv = KeyValues.of( + MongodbObservation.CommandLowCardinalityKeyNames.SYSTEM.withValue("mongodb"), + MongodbObservation.CommandLowCardinalityKeyNames.NAMESPACE.withValue(dbName), + MongodbObservation.CommandLowCardinalityKeyNames.QUERY_SUMMARY.withValue(summary), + MongodbObservation.CommandLowCardinalityKeyNames.COMMAND_NAME.withValue(cmdName)); + if (collectionName != null) { + kv = kv.and(MongodbObservation.CommandLowCardinalityKeyNames.COLLECTION.withValue(collectionName)); + } + com.mongodb.ServerAddress serverAddress = context.getServerAddress(); + if (serverAddress != null) { + kv = kv.and( + MongodbObservation.CommandLowCardinalityKeyNames.SERVER_ADDRESS.withValue(serverAddress.getHost()), + MongodbObservation.CommandLowCardinalityKeyNames.SERVER_PORT.withValue( + String.valueOf(serverAddress.getPort())), + MongodbObservation.CommandLowCardinalityKeyNames.NETWORK_TRANSPORT.withValue( + context.isUnixSocket() ? "unix" : "tcp")); + } + String responseStatusCode = context.getResponseStatusCode(); + if (responseStatusCode != null) { + kv = kv.and(MongodbObservation.CommandLowCardinalityKeyNames.RESPONSE_STATUS_CODE.withValue(responseStatusCode)); + } + return kv; + } + + private KeyValues getCommandHighCardinalityKeyValues(final MongodbContext context) { + KeyValues kv = KeyValues.empty(); + + String queryText = context.getQueryText(); + if (queryText != null) { + kv = kv.and(MongodbObservation.HighCardinalityKeyNames.QUERY_TEXT.withValue(queryText)); + } + com.mongodb.connection.ConnectionId connectionId = context.getConnectionId(); + if (connectionId != null) { + kv = kv.and( + MongodbObservation.HighCardinalityKeyNames.CLIENT_CONNECTION_ID.withValue( + String.valueOf(connectionId.getLocalValue())), + MongodbObservation.HighCardinalityKeyNames.SERVER_CONNECTION_ID.withValue( + String.valueOf(connectionId.getServerValue()))); + } + Long cursorId = context.getCursorId(); + if (cursorId != null) { + kv = kv.and(MongodbObservation.HighCardinalityKeyNames.CURSOR_ID.withValue( + String.valueOf(cursorId))); + } + Long transactionNumber = context.getTransactionNumber(); + if (transactionNumber != null) { + kv = kv.and(MongodbObservation.HighCardinalityKeyNames.TRANSACTION_NUMBER.withValue( + String.valueOf(transactionNumber))); + } + String sessionId = context.getSessionId(); + if (sessionId != null) { + kv = kv.and(MongodbObservation.HighCardinalityKeyNames.SESSION_ID.withValue(sessionId)); + } + + // Exception tags from observation error + Throwable error = context.getError(); + if (error != null) { + kv = kv.and( + MongodbObservation.HighCardinalityKeyNames.EXCEPTION_MESSAGE.withValue(error.getMessage()), + MongodbObservation.HighCardinalityKeyNames.EXCEPTION_TYPE.withValue(error.getClass().getName()), + MongodbObservation.HighCardinalityKeyNames.EXCEPTION_STACKTRACE.withValue(getStackTraceAsString(error))); + } + + return kv; + } + + private static String getStackTraceAsString(final Throwable throwable) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + return sw.toString(); + } +} diff --git a/driver-core/src/main/com/mongodb/internal/observability/micrometer/MicrometerTracer.java b/driver-core/src/main/com/mongodb/internal/observability/micrometer/MicrometerTracer.java index a7204a01a71..4ea9c7e159e 100644 --- a/driver-core/src/main/com/mongodb/internal/observability/micrometer/MicrometerTracer.java +++ b/driver-core/src/main/com/mongodb/internal/observability/micrometer/MicrometerTracer.java @@ -18,25 +18,16 @@ import com.mongodb.MongoNamespace; import com.mongodb.lang.Nullable; -import io.micrometer.common.KeyValue; -import io.micrometer.common.KeyValues; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; -import io.micrometer.observation.transport.Kind; -import io.micrometer.observation.transport.SenderContext; import org.bson.BsonDocument; import org.bson.BsonReader; import org.bson.json.JsonMode; import org.bson.json.JsonWriter; import org.bson.json.JsonWriterSettings; -import java.io.PrintWriter; import java.io.StringWriter; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_MESSAGE; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_STACKTRACE; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_TYPE; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.MONGODB_OBSERVATION; import static com.mongodb.internal.observability.micrometer.TracingManager.ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH; import static java.lang.System.getenv; import static java.util.Optional.ofNullable; @@ -55,22 +46,13 @@ public class MicrometerTracer implements Tracer { private final ObservationRegistry observationRegistry; private final boolean allowCommandPayload; private final int textMaxLength; - private static final String QUERY_TEXT_LENGTH_CONTEXT_KEY = "QUERY_TEXT_MAX_LENGTH"; /** * Constructs a new {@link MicrometerTracer} instance. * * @param observationRegistry The Micrometer {@link ObservationRegistry} to delegate tracing operations to. - */ - public MicrometerTracer(final ObservationRegistry observationRegistry) { - this(observationRegistry, false, 0); - } - - /** - * Constructs a new {@link MicrometerTracer} instance with an option to allow command payloads. - * - * @param observationRegistry The Micrometer {@link ObservationRegistry} to delegate tracing operations to. * @param allowCommandPayload Whether to allow command payloads in the trace context. + * @param textMaxLength The maximum length for query text truncation. */ public MicrometerTracer(final ObservationRegistry observationRegistry, final boolean allowCommandPayload, final int textMaxLength) { this.allowCommandPayload = allowCommandPayload; @@ -78,11 +60,16 @@ public MicrometerTracer(final ObservationRegistry observationRegistry, final boo this.textMaxLength = ofNullable(getenv(ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH)) .map(Integer::parseInt) .orElse(textMaxLength); + // Register default convention. Users can override by registering their own GlobalObservationConvention + // after the MongoClient is created — the last matching convention wins. + DefaultMongodbObservationConvention defaultConvention = new DefaultMongodbObservationConvention(); + observationRegistry.observationConfig().observationConvention(defaultConvention); } @Override - public Span nextSpan(final String name, @Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) { - Observation observation = getObservation(name); + public Span nextSpan(final MongodbObservation observationType, final String name, + @Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) { + Observation observation = getObservation(observationType, name); if (parent instanceof MicrometerTraceContext) { Observation parentObservation = ((MicrometerTraceContext) parent).observation; @@ -91,7 +78,7 @@ public Span nextSpan(final String name, @Nullable final TraceContext parent, @Nu } } - return new MicrometerSpan(observation.start(), namespace); + return new MicrometerSpan(observation.start(), namespace, textMaxLength); } @Override @@ -104,12 +91,12 @@ public boolean includeCommandPayload() { return allowCommandPayload; } - private Observation getObservation(final String name) { - Observation observation = MONGODB_OBSERVATION.observation(observationRegistry, - () -> new SenderContext<>((carrier, key, value) -> {}, Kind.CLIENT)) - .contextualName(name); - observation.getContext().put(QUERY_TEXT_LENGTH_CONTEXT_KEY, textMaxLength); - return observation; + private Observation getObservation(final MongodbObservation observationType, final String name) { + return observationType.observation(observationRegistry, () -> { + MongodbContext ctx = new MongodbContext(); + ctx.setObservationType(observationType); + return ctx; + }).contextualName(name); } /** * Represents a Micrometer-based trace context. @@ -135,38 +122,43 @@ private static class MicrometerSpan implements Span { @Nullable private final MongoNamespace namespace; private final int queryTextLength; + @Nullable + private Observation.Scope scope; /** * Constructs a new {@link MicrometerSpan} instance with an associated Observation and MongoDB namespace. * - * @param observation The Micrometer {@link Observation}, or null if none exists. - * @param namespace The MongoDB namespace associated with the span. + * @param observation The Micrometer {@link Observation}, or null if none exists. + * @param namespace The MongoDB namespace associated with the span. + * @param queryTextLength The maximum length for query text truncation. */ - MicrometerSpan(final Observation observation, @Nullable final MongoNamespace namespace) { + MicrometerSpan(final Observation observation, @Nullable final MongoNamespace namespace, final int queryTextLength) { this.namespace = namespace; this.observation = observation; - this.queryTextLength = ofNullable(observation.getContext().get(QUERY_TEXT_LENGTH_CONTEXT_KEY)) - .filter(Integer.class::isInstance) - .map(Integer.class::cast) - .orElse(Integer.MAX_VALUE); + this.queryTextLength = queryTextLength; } @Override - public void tagLowCardinality(final KeyValue keyValue) { - observation.lowCardinalityKeyValue(keyValue); + public void openScope() { + this.scope = observation.openScope(); } @Override - public void tagLowCardinality(final KeyValues keyValues) { - observation.lowCardinalityKeyValues(keyValues); + public void closeScope() { + if (scope != null) { + scope.close(); + scope = null; + } } @Override - public void tagHighCardinality(final String keyName, final BsonDocument value) { - observation.highCardinalityKeyValue(keyName, - (queryTextLength < Integer.MAX_VALUE) // truncate values that are too long - ? getTruncatedBsonDocument(value) - : value.toString()); + public void setQueryText(final BsonDocument commandDocument) { + MongodbContext ctx = getMongodbContext(); + if (ctx != null) { + ctx.setQueryText((queryTextLength < Integer.MAX_VALUE) + ? getTruncatedBsonDocument(commandDocument) + : commandDocument.toString()); + } } @Override @@ -176,11 +168,6 @@ public void event(final String event) { @Override public void error(final Throwable throwable) { - observation.lowCardinalityKeyValues(KeyValues.of( - EXCEPTION_MESSAGE.withValue(throwable.getMessage()), - EXCEPTION_TYPE.withValue(throwable.getClass().getName()), - EXCEPTION_STACKTRACE.withValue(getStackTraceAsString(throwable)) - )); observation.error(throwable); } @@ -196,15 +183,17 @@ public TraceContext context() { @Override @Nullable - public MongoNamespace getNamespace() { - return namespace; + public MongodbContext getMongodbContext() { + if (observation.getContext() instanceof MongodbContext) { + return (MongodbContext) observation.getContext(); + } + return null; } - private String getStackTraceAsString(final Throwable throwable) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - throwable.printStackTrace(pw); - return sw.toString(); + @Override + @Nullable + public MongoNamespace getNamespace() { + return namespace; } private String getTruncatedBsonDocument(final BsonDocument commandDocument) { diff --git a/driver-core/src/main/com/mongodb/internal/observability/micrometer/MongodbContext.java b/driver-core/src/main/com/mongodb/internal/observability/micrometer/MongodbContext.java new file mode 100644 index 00000000000..db7c54977fc --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/observability/micrometer/MongodbContext.java @@ -0,0 +1,174 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * 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 + * + * http://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 com.mongodb.internal.observability.micrometer; + +import com.mongodb.ServerAddress; +import com.mongodb.connection.ConnectionId; +import com.mongodb.lang.Nullable; +import io.micrometer.observation.transport.Kind; +import io.micrometer.observation.transport.SenderContext; + +/** + * A MongoDB-specific {@link SenderContext} for Micrometer observations. + *

+ * Extends {@link SenderContext} with {@link Kind#CLIENT} to preserve the client span kind + * in the tracing bridge. Provides a MongoDB-specific type that users can filter on + * when registering {@code ObservationHandler} or {@code ObservationConvention} instances. + *

+ *

+ * Domain fields (commandName, databaseName, etc.) are populated by the driver after + * the observation is started and before it is stopped. The {@code ObservationConvention} + * reads these fields at stop time to produce the final tag key-values. + *

+ * + * @since 5.7 + */ +public class MongodbContext extends SenderContext { + + private MongodbObservation observationType; + @Nullable + private String commandName; + @Nullable + private String databaseName; + @Nullable + private String collectionName; + @Nullable + private ServerAddress serverAddress; + @Nullable + private ConnectionId connectionId; + @Nullable + private Long cursorId; + @Nullable + private Long transactionNumber; + @Nullable + private String sessionId; + @Nullable + private String queryText; + @Nullable + private String responseStatusCode; + private boolean isUnixSocket; + + public MongodbContext() { + super((carrier, key, value) -> { }, Kind.CLIENT); + } + + @Nullable + public String getCommandName() { + return commandName; + } + + public void setCommandName(@Nullable final String commandName) { + this.commandName = commandName; + } + + @Nullable + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(@Nullable final String databaseName) { + this.databaseName = databaseName; + } + + @Nullable + public String getCollectionName() { + return collectionName; + } + + public void setCollectionName(@Nullable final String collectionName) { + this.collectionName = collectionName; + } + + @Nullable + public ServerAddress getServerAddress() { + return serverAddress; + } + + public void setServerAddress(@Nullable final ServerAddress serverAddress) { + this.serverAddress = serverAddress; + } + + @Nullable + public ConnectionId getConnectionId() { + return connectionId; + } + + public void setConnectionId(@Nullable final ConnectionId connectionId) { + this.connectionId = connectionId; + } + + public MongodbObservation getObservationType() { + return observationType; + } + + public void setObservationType(final MongodbObservation observationType) { + this.observationType = observationType; + } + + @Nullable + public Long getCursorId() { + return cursorId; + } + + public void setCursorId(@Nullable final Long cursorId) { + this.cursorId = cursorId; + } + + @Nullable + public Long getTransactionNumber() { + return transactionNumber; + } + + public void setTransactionNumber(@Nullable final Long transactionNumber) { + this.transactionNumber = transactionNumber; + } + + @Nullable + public String getSessionId() { + return sessionId; + } + + public void setSessionId(@Nullable final String sessionId) { + this.sessionId = sessionId; + } + + public boolean isUnixSocket() { + return isUnixSocket; + } + + public void setUnixSocket(final boolean unixSocket) { + isUnixSocket = unixSocket; + } + + @Nullable + public String getQueryText() { + return queryText; + } + + public void setQueryText(@Nullable final String queryText) { + this.queryText = queryText; + } + + @Nullable + public String getResponseStatusCode() { + return responseStatusCode; + } + + public void setResponseStatusCode(@Nullable final String responseStatusCode) { + this.responseStatusCode = responseStatusCode; + } +} diff --git a/driver-core/src/main/com/mongodb/internal/observability/micrometer/MongodbObservation.java b/driver-core/src/main/com/mongodb/internal/observability/micrometer/MongodbObservation.java index 0fbfe165f50..0824d28c37f 100644 --- a/driver-core/src/main/com/mongodb/internal/observability/micrometer/MongodbObservation.java +++ b/driver-core/src/main/com/mongodb/internal/observability/micrometer/MongodbObservation.java @@ -17,43 +17,66 @@ package com.mongodb.internal.observability.micrometer; import io.micrometer.common.docs.KeyName; -import io.micrometer.observation.Observation; import io.micrometer.observation.docs.ObservationDocumentation; /** - * A MongoDB-based {@link Observation}. + * MongoDB {@link ObservationDocumentation} definitions for operation-level and command-level observations. + *

+ * These are split into two separate observation types so that each has a distinct name and a fixed set + * of low-cardinality tag keys. This is required by Prometheus which rejects meters that share a name + * but have different tag key sets. + *

* * @since 5.7 */ public enum MongodbObservation implements ObservationDocumentation { - MONGODB_OBSERVATION { + /** + * Observation for high-level MongoDB operations (e.g. find, insert, update). + * Created per user-initiated operation, may contain multiple command spans. + */ + MONGODB_OPERATION { + @Override + public String getName() { + return "mongodb.operation"; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return OperationLowCardinalityKeyNames.values(); + } + }, + + /** + * Observation for wire-protocol MongoDB commands sent to the server. + * Created per actual command (nested under an operation span). + */ + MONGODB_COMMAND { @Override public String getName() { - return "mongodb"; + return "mongodb.command"; } @Override public KeyName[] getLowCardinalityKeyNames() { - return LowCardinalityKeyNames.values(); + return CommandLowCardinalityKeyNames.values(); } @Override public KeyName[] getHighCardinalityKeyNames() { return HighCardinalityKeyNames.values(); } - }; /** - * Enums related to low cardinality key names for MongoDB tags. + * Low cardinality key names for operation-level observations. */ - public enum LowCardinalityKeyNames implements KeyName { + public enum OperationLowCardinalityKeyNames implements KeyName { SYSTEM { @Override public String asString() { - return "db.system"; + return "db.system.name"; } }, NAMESPACE { @@ -74,22 +97,41 @@ public String asString() { return "db.operation.name"; } }, - COMMAND_NAME { + OPERATION_SUMMARY { @Override public String asString() { - return "db.command.name"; + return "db.operation.summary"; + } + } + } + + /** + * Low cardinality key names for command-level observations. + */ + public enum CommandLowCardinalityKeyNames implements KeyName { + + SYSTEM { + @Override + public String asString() { + return "db.system.name"; } }, - NETWORK_TRANSPORT { + NAMESPACE { @Override public String asString() { - return "network.transport"; + return "db.namespace"; } }, - OPERATION_SUMMARY { + COLLECTION { @Override public String asString() { - return "db.operation.summary"; + return "db.collection.name"; + } + }, + COMMAND_NAME { + @Override + public String asString() { + return "db.command.name"; } }, QUERY_SUMMARY { @@ -98,10 +140,10 @@ public String asString() { return "db.query.summary"; } }, - CURSOR_ID { + NETWORK_TRANSPORT { @Override public String asString() { - return "db.mongodb.cursor_id"; + return "network.transport"; } }, SERVER_ADDRESS { @@ -116,6 +158,25 @@ public String asString() { return "server.port"; } }, + RESPONSE_STATUS_CODE { + @Override + public String asString() { + return "db.response.status_code"; + } + } + } + + /** + * High cardinality (highly variable values) key names for command-level observations. + */ + public enum HighCardinalityKeyNames implements KeyName { + + QUERY_TEXT { + @Override + public String asString() { + return "db.query.text"; + } + }, CLIENT_CONNECTION_ID { @Override public String asString() { @@ -128,6 +189,12 @@ public String asString() { return "db.mongodb.server_connection_id"; } }, + CURSOR_ID { + @Override + public String asString() { + return "db.mongodb.cursor_id"; + } + }, TRANSACTION_NUMBER { @Override public String asString() { @@ -140,10 +207,10 @@ public String asString() { return "db.mongodb.lsid"; } }, - EXCEPTION_STACKTRACE { + EXCEPTION_MESSAGE { @Override public String asString() { - return "exception.stacktrace"; + return "exception.message"; } }, EXCEPTION_TYPE { @@ -152,29 +219,10 @@ public String asString() { return "exception.type"; } }, - EXCEPTION_MESSAGE { - @Override - public String asString() { - return "exception.message"; - } - }, - RESPONSE_STATUS_CODE { - @Override - public String asString() { - return "db.response.status_code"; - } - } - } - - /** - * Enums related to high cardinality (highly variable values) key names for MongoDB tags. - */ - public enum HighCardinalityKeyNames implements KeyName { - - QUERY_TEXT { + EXCEPTION_STACKTRACE { @Override public String asString() { - return "db.query.text"; + return "exception.stacktrace"; } } } diff --git a/driver-core/src/main/com/mongodb/internal/observability/micrometer/Span.java b/driver-core/src/main/com/mongodb/internal/observability/micrometer/Span.java index 84bdbb41672..372a54c7da6 100644 --- a/driver-core/src/main/com/mongodb/internal/observability/micrometer/Span.java +++ b/driver-core/src/main/com/mongodb/internal/observability/micrometer/Span.java @@ -18,8 +18,6 @@ import com.mongodb.MongoNamespace; import com.mongodb.lang.Nullable; -import io.micrometer.common.KeyValue; -import io.micrometer.common.KeyValues; import org.bson.BsonDocument; /** @@ -48,15 +46,15 @@ public interface Span { */ Span EMPTY = new Span() { @Override - public void tagLowCardinality(final KeyValue tag) { + public void openScope() { } @Override - public void tagLowCardinality(final KeyValues keyValues) { + public void closeScope() { } @Override - public void tagHighCardinality(final String keyName, final BsonDocument value) { + public void setQueryText(final BsonDocument commandDocument) { } @Override @@ -81,29 +79,32 @@ public TraceContext context() { public MongoNamespace getNamespace() { return null; } + + @Override + @Nullable + public MongodbContext getMongodbContext() { + return null; + } }; /** - * Adds a low-cardinality tag to the span. - * - * @param keyValue The key-value pair representing the tag. + * Opens a scope for this span, making it the current observation on the thread. + * Must be paired with {@link #closeScope()} in a try-finally block. */ - void tagLowCardinality(KeyValue keyValue); + void openScope(); /** - * Adds multiple low-cardinality tags to the span. - * - * @param keyValues The key-value pairs representing the tags. + * Closes the scope previously opened by {@link #openScope()}, restoring the previous observation. */ - void tagLowCardinality(KeyValues keyValues); + void closeScope(); /** - * Adds a high-cardinality (highly variable values) tag to the span with a BSON document value. + * Sets the query text on the observation context from the given command document. + * The document is converted to a JSON string and may be truncated based on configuration. * - * @param keyName The name of the tag. - * @param value The BSON document representing the value of the tag. + * @param commandDocument The BSON command document. */ - void tagHighCardinality(String keyName, BsonDocument value); + void setQueryText(BsonDocument commandDocument); /** * Records an event in the span. @@ -131,6 +132,15 @@ public MongoNamespace getNamespace() { */ TraceContext context(); + /** + * Retrieves the {@link MongodbContext} associated with the span, if any. + * Returns null for no-op spans or non-Micrometer implementations. + * + * @return The MongoDB observation context, or null. + */ + @Nullable + MongodbContext getMongodbContext(); + /** * Retrieves the MongoDB namespace associated with the span, if any. * diff --git a/driver-core/src/main/com/mongodb/internal/observability/micrometer/Tracer.java b/driver-core/src/main/com/mongodb/internal/observability/micrometer/Tracer.java index 632580ab40e..fc1ddca68d0 100644 --- a/driver-core/src/main/com/mongodb/internal/observability/micrometer/Tracer.java +++ b/driver-core/src/main/com/mongodb/internal/observability/micrometer/Tracer.java @@ -30,7 +30,8 @@ public interface Tracer { Tracer NO_OP = new Tracer() { @Override - public Span nextSpan(final String name, @Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) { + public Span nextSpan(final MongodbObservation observationType, final String name, + @Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) { return Span.EMPTY; } @@ -46,14 +47,15 @@ public boolean includeCommandPayload() { }; /** - * Creates a new span with the specified name and optional parent trace context. + * Creates a new span with the specified observation type, name and optional parent trace context. * + * @param observationType The {@link MongodbObservation} type (operation or command). * @param name The name of the span. * @param parent The parent {@link TraceContext}, or null if no parent context is provided. * @param namespace The {@link MongoNamespace} associated with the span, or null if none is provided. * @return A {@link Span} representing the newly created span. */ - Span nextSpan(String name, @Nullable TraceContext parent, @Nullable MongoNamespace namespace); + Span nextSpan(MongodbObservation observationType, String name, @Nullable TraceContext parent, @Nullable MongoNamespace namespace); /** * Indicates whether tracing is enabled. diff --git a/driver-core/src/main/com/mongodb/internal/observability/micrometer/TracingManager.java b/driver-core/src/main/com/mongodb/internal/observability/micrometer/TracingManager.java index 4247ed1c3dd..51b0bec614b 100644 --- a/driver-core/src/main/com/mongodb/internal/observability/micrometer/TracingManager.java +++ b/driver-core/src/main/com/mongodb/internal/observability/micrometer/TracingManager.java @@ -27,28 +27,14 @@ import com.mongodb.lang.Nullable; import com.mongodb.observability.ObservabilitySettings; import com.mongodb.observability.micrometer.MicrometerObservabilitySettings; -import io.micrometer.common.KeyValues; import io.micrometer.observation.ObservationRegistry; import org.bson.BsonDocument; import java.util.function.Predicate; import java.util.function.Supplier; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.CLIENT_CONNECTION_ID; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.COLLECTION; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.COMMAND_NAME; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.NAMESPACE; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.OPERATION_NAME; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.OPERATION_SUMMARY; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.NETWORK_TRANSPORT; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.QUERY_SUMMARY; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SERVER_ADDRESS; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SERVER_CONNECTION_ID; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SERVER_PORT; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SESSION_ID; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SYSTEM; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.TRANSACTION_NUMBER; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.CURSOR_ID; +import static com.mongodb.internal.observability.micrometer.MongodbObservation.MONGODB_COMMAND; +import static com.mongodb.internal.observability.micrometer.MongodbObservation.MONGODB_OPERATION; import static java.lang.System.getenv; /** @@ -109,35 +95,31 @@ public TracingManager(@Nullable final ObservabilitySettings observabilitySetting } /** - * Creates a new span with the specified name and parent trace context. - *

- * This method is used to create a span that is linked to a parent context, - * enabling hierarchical tracing of operations. - *

+ * Creates a new span with the specified observation type, name and parent trace context. * - * @param name The name of the span. - * @param parentContext The parent trace context to associate with the span. + * @param observationType The observation type (operation or command). + * @param name The name of the span. + * @param parentContext The parent trace context to associate with the span. * @return The created span. */ - public Span addSpan(final String name, @Nullable final TraceContext parentContext) { - return tracer.nextSpan(name, parentContext, null); + public Span addSpan(final MongodbObservation observationType, final String name, + @Nullable final TraceContext parentContext) { + return tracer.nextSpan(observationType, name, parentContext, null); } /** - * Creates a new span with the specified name, parent trace context, and MongoDB namespace. - *

- * This method is used to create a span that is linked to a parent context, - * enabling hierarchical tracing of operations. The MongoDB namespace can be used - * by nested spans to access the database and collection name (which might not be easily accessible at connection layer). - *

+ * Creates a new span with the specified observation type, name, parent trace context, + * and MongoDB namespace. * - * @param name The name of the span. - * @param parentContext The parent trace context to associate with the span. - * @param namespace The MongoDB namespace associated with the operation. + * @param observationType The observation type (operation or command). + * @param name The name of the span. + * @param parentContext The parent trace context to associate with the span. + * @param namespace The MongoDB namespace associated with the operation. * @return The created span. */ - public Span addSpan(final String name, @Nullable final TraceContext parentContext, final MongoNamespace namespace) { - return tracer.nextSpan(name, parentContext, namespace); + public Span addSpan(final MongodbObservation observationType, final String name, + @Nullable final TraceContext parentContext, final MongoNamespace namespace) { + return tracer.nextSpan(observationType, name, parentContext, namespace); } /** @@ -146,9 +128,7 @@ public Span addSpan(final String name, @Nullable final TraceContext parentContex * @return The created transaction span. */ public Span addTransactionSpan() { - Span span = tracer.nextSpan("transaction", null, null); - span.tagLowCardinality(SYSTEM.withValue("mongodb")); - return span; + return tracer.nextSpan(MONGODB_OPERATION, "transaction", null, null); } /** @@ -205,17 +185,9 @@ public Span createTracingSpan(final CommandMessage message, } Span operationSpan = operationContext.getTracingSpan(); - Span span = addSpan(commandName, operationSpan != null ? operationSpan.context() : null); - - if (command.containsKey("getMore")) { - long cursorId = command.getInt64("getMore").longValue(); - span.tagLowCardinality(CURSOR_ID.withValue(String.valueOf(cursorId))); - if (operationSpan != null) { - operationSpan.tagLowCardinality(CURSOR_ID.withValue(String.valueOf(cursorId))); - } - } + Span span = addSpan(MONGODB_COMMAND, commandName, operationSpan != null ? operationSpan.context() : null); - // Tag namespace + // Resolve namespace from parent operation span or message String namespace; String collection = ""; if (operationSpan != null) { @@ -231,39 +203,40 @@ public Span createTracingSpan(final CommandMessage message, } else { namespace = message.getDatabase(); } - String summary = commandName + " " + namespace + (collection.isEmpty() ? "" : "." + collection); - KeyValues keyValues = KeyValues.of( - SYSTEM.withValue("mongodb"), - NAMESPACE.withValue(namespace), - QUERY_SUMMARY.withValue(summary), - COMMAND_NAME.withValue(commandName)); + // Populate domain fields on MongodbContext — the convention reads these to produce tags + MongodbContext mongodbContext = span.getMongodbContext(); + if (mongodbContext != null) { + mongodbContext.setCommandName(commandName); + mongodbContext.setDatabaseName(namespace); + if (!collection.isEmpty()) { + mongodbContext.setCollectionName(collection); + } - if (!collection.isEmpty()) { - keyValues = keyValues.and(COLLECTION.withValue(collection)); - } - span.tagLowCardinality(keyValues); - - // tag server and connection info - ServerAddress serverAddress = serverAddressSupplier.get(); - ConnectionId connectionId = connectionIdSupplier.get(); - span.tagLowCardinality(KeyValues.of( - SERVER_ADDRESS.withValue(serverAddress.getHost()), - SERVER_PORT.withValue(String.valueOf(serverAddress.getPort())), - CLIENT_CONNECTION_ID.withValue(String.valueOf(connectionId.getLocalValue())), - SERVER_CONNECTION_ID.withValue(String.valueOf(connectionId.getServerValue())), - NETWORK_TRANSPORT.withValue(serverAddress instanceof UnixServerAddress ? "unix" : "tcp") - )); - - // tag session and transaction info - SessionContext sessionContext = operationContext.getSessionContext(); - if (sessionContext.hasSession() && !sessionContext.isImplicitSession()) { - span.tagLowCardinality(KeyValues.of( - TRANSACTION_NUMBER.withValue(String.valueOf(sessionContext.getTransactionNumber())), - SESSION_ID.withValue(String.valueOf(sessionContext.getSessionId() - .get(sessionContext.getSessionId().getFirstKey()) - .asBinary().asUuid())) - )); + ServerAddress serverAddress = serverAddressSupplier.get(); + mongodbContext.setServerAddress(serverAddress); + mongodbContext.setUnixSocket(serverAddress instanceof UnixServerAddress); + + ConnectionId connectionId = connectionIdSupplier.get(); + mongodbContext.setConnectionId(connectionId); + + if (command.containsKey("getMore")) { + long cursorId = command.getInt64("getMore").longValue(); + mongodbContext.setCursorId(cursorId); + // Also set on parent operation span context for correlation + MongodbContext parentContext = operationSpan != null ? operationSpan.getMongodbContext() : null; + if (parentContext != null) { + parentContext.setCursorId(cursorId); + } + } + + SessionContext sessionContext = operationContext.getSessionContext(); + if (sessionContext.hasSession() && !sessionContext.isImplicitSession()) { + mongodbContext.setTransactionNumber(sessionContext.getTransactionNumber()); + mongodbContext.setSessionId(String.valueOf(sessionContext.getSessionId() + .get(sessionContext.getSessionId().getFirstKey()) + .asBinary().asUuid())); + } } return span; @@ -297,17 +270,18 @@ public Span createOperationSpan(@Nullable final TransactionSpan transactionSpan, ? "" : "." + namespace.getCollectionName()); - KeyValues keyValues = KeyValues.of( - SYSTEM.withValue("mongodb"), - NAMESPACE.withValue(namespace.getDatabaseName())); - if (!MongoNamespaceHelper.COMMAND_COLLECTION_NAME.equalsIgnoreCase(namespace.getCollectionName())) { - keyValues = keyValues.and(COLLECTION.withValue(namespace.getCollectionName())); + Span span = addSpan(MONGODB_OPERATION, name, parentContext, namespace); + + // Populate domain fields on MongodbContext — the convention reads these to produce tags + MongodbContext mongodbContext = span.getMongodbContext(); + if (mongodbContext != null) { + mongodbContext.setCommandName(commandName); + mongodbContext.setDatabaseName(namespace.getDatabaseName()); + if (!MongoNamespaceHelper.COMMAND_COLLECTION_NAME.equalsIgnoreCase(namespace.getCollectionName())) { + mongodbContext.setCollectionName(namespace.getCollectionName()); + } } - keyValues = keyValues.and(OPERATION_NAME.withValue(commandName), - OPERATION_SUMMARY.withValue(name)); - Span span = addSpan(name, parentContext, namespace); - span.tagLowCardinality(keyValues); operationContext.setTracingSpan(span); return span; } diff --git a/driver-core/src/main/com/mongodb/internal/observability/micrometer/TransactionSpan.java b/driver-core/src/main/com/mongodb/internal/observability/micrometer/TransactionSpan.java index 3d16d18a976..ad25ba902d3 100644 --- a/driver-core/src/main/com/mongodb/internal/observability/micrometer/TransactionSpan.java +++ b/driver-core/src/main/com/mongodb/internal/observability/micrometer/TransactionSpan.java @@ -31,6 +31,7 @@ public class TransactionSpan { public TransactionSpan(final TracingManager tracingManager) { this.span = tracingManager.addTransactionSpan(); + this.span.openScope(); } /** @@ -54,6 +55,7 @@ public void handleTransactionSpanError(final Throwable e) { } if (!isConvenientTransaction) { + span.closeScope(); span.end(); } } @@ -67,6 +69,7 @@ public void finalizeTransactionSpan(final String status) { span.event(status); // clear previous commit error if any if (!isConvenientTransaction) { + span.closeScope(); span.end(); } reportedError = null; // clear previous commit error if any @@ -82,6 +85,7 @@ public void spanFinalizing(final boolean cleanupTransactionContext) { if (reportedError != null) { span.error(reportedError); } + span.closeScope(); span.end(); reportedError = null; // Don't clean up transaction context if we're still retrying (we want the retries to fold under the original transaction span) diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java index eb36678761a..0f83fd4e70e 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java @@ -426,9 +426,11 @@ public T execute(final ReadOperation operation, final ReadPreference r .withSessionContext(new ClientSessionBinding.SyncClientSessionContext(actualClientSession, readConcern, implicitSession)); Span span = operationContext.getTracingManager().createOperationSpan( actualClientSession.getTransactionSpan(), operationContext, operation.getCommandName(), operation.getNamespace()); + if (span != null) { + span.openScope(); + } ReadBinding binding = getReadBinding(readPreference, actualClientSession, implicitSession); - try { if (actualClientSession.hasActiveTransaction() && !binding.getReadPreference().equals(primary())) { throw new MongoClientException("Read preference in a transaction must be primary"); @@ -445,6 +447,7 @@ public T execute(final ReadOperation operation, final ReadPreference r } finally { binding.release(); if (span != null) { + span.closeScope(); span.end(); } } @@ -462,6 +465,9 @@ public T execute(final WriteOperation operation, final ReadConcern readCo .withSessionContext(new ClientSessionBinding.SyncClientSessionContext(actualClientSession, readConcern, isImplicitSession(session))); Span span = operationContext.getTracingManager().createOperationSpan( actualClientSession.getTransactionSpan(), operationContext, operation.getCommandName(), operation.getNamespace()); + if (span != null) { + span.openScope(); + } WriteBinding binding = getWriteBinding(actualClientSession, isImplicitSession(session)); try { @@ -477,6 +483,7 @@ public T execute(final WriteOperation operation, final ReadConcern readCo } finally { binding.release(); if (span != null) { + span.closeScope(); span.end(); } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/observability/SpanTree.java b/driver-sync/src/test/functional/com/mongodb/client/observability/SpanTree.java index 7d3bff3224d..547b4541607 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/observability/SpanTree.java +++ b/driver-sync/src/test/functional/com/mongodb/client/observability/SpanTree.java @@ -34,12 +34,12 @@ import java.util.UUID; import java.util.function.BiConsumer; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.CLIENT_CONNECTION_ID; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.CURSOR_ID; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SERVER_CONNECTION_ID; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SERVER_PORT; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SESSION_ID; -import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.TRANSACTION_NUMBER; +import static com.mongodb.internal.observability.micrometer.MongodbObservation.CommandLowCardinalityKeyNames.SERVER_PORT; +import static com.mongodb.internal.observability.micrometer.MongodbObservation.HighCardinalityKeyNames.CLIENT_CONNECTION_ID; +import static com.mongodb.internal.observability.micrometer.MongodbObservation.HighCardinalityKeyNames.CURSOR_ID; +import static com.mongodb.internal.observability.micrometer.MongodbObservation.HighCardinalityKeyNames.SERVER_CONNECTION_ID; +import static com.mongodb.internal.observability.micrometer.MongodbObservation.HighCardinalityKeyNames.SESSION_ID; +import static com.mongodb.internal.observability.micrometer.MongodbObservation.HighCardinalityKeyNames.TRANSACTION_NUMBER; import static org.bson.assertions.Assertions.notNull; import static org.junit.jupiter.api.Assertions.fail; diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java b/driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java index 12a4cb5db56..478f84e74df 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java @@ -49,6 +49,7 @@ import com.mongodb.lang.Nullable; import com.mongodb.logging.TestLoggingInterceptor; import com.mongodb.observability.ObservabilitySettings; +import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; import io.micrometer.tracing.test.reporter.inmemory.InMemoryOtelSetup; import org.bson.BsonArray; @@ -113,6 +114,7 @@ public final class Entities { private final Map clientLoggingInterceptors = new HashMap<>(); private final Map clientTracing = new HashMap<>(); private final Set inMemoryOTelInstances = new HashSet<>(); + private final Set observationScopes = new HashSet<>(); private final Map clientConnectionPoolListeners = new HashMap<>(); private final Map clientServerListeners = new HashMap<>(); private final Map clientClusterListeners = new HashMap<>(); @@ -593,6 +595,12 @@ private void initClient(final BsonDocument entity, final String id, .observabilitySettings(ObservabilitySettings.micrometerBuilder() .observationRegistry(observationRegistry) .enableCommandPayloadTracing(enableCommandPayload).build()); + + // Simulate what Spring Boot's observation does + // open a parent observation's scope before running the MongoDB operation + Observation parentObservation = Observation.createNotStarted("http.request", observationRegistry) + .start(); + observationScopes.add(parentObservation.openScope()); } MongoClientSettings clientSettings = clientSettingsBuilder.build(); @@ -816,5 +824,6 @@ public void close() { clientLoggingInterceptors.values().forEach(TestLoggingInterceptor::close); threads.values().forEach(ExecutorService::shutdownNow); inMemoryOTelInstances.forEach(InMemoryOtelSetup::close); + observationScopes.forEach(Observation.Scope::close); } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java index 02d097688e6..fcfb0fc6c7d 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java @@ -197,26 +197,6 @@ public static void applyCustomizations(final TestDef def) { .test("client-side-operations-timeout", "timeoutMS can be configured on a MongoClient", "timeoutMS can be set to 0 on a MongoClient - dropIndexes on collection"); - // OpenTelemetry - def.skipJira("https://jira.mongodb.org/browse/JAVA-5991") - .file("open-telemetry/tests", "operation find") - .file("open-telemetry/tests", "operation find_one_and_update") - .file("open-telemetry/tests", "operation update") - .file("open-telemetry/tests", "operation bulk_write") - .file("open-telemetry/tests", "operation drop collection") - .file("open-telemetry/tests", "transaction spans") - .file("open-telemetry/tests", "convenient transactions") - .file("open-telemetry/tests", "operation atlas_search") - .file("open-telemetry/tests", "operation insert") - .file("open-telemetry/tests", "operation map_reduce") - .file("open-telemetry/tests", "operation find without db.query.text") - .file("open-telemetry/tests", "operation find_retries"); - def.skipAccordingToSpec("Micrometer tests expect the network transport to be tcp") - .when(ClusterFixture::isUnixSocket) - .directory("open-telemetry/tests"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-6094 TODO-JAVA-6094") - .directory("open-telemetry/tests"); - // TODO-JAVA-5712 // collection-management