From f650d436cb8b872383e980870b41d6cf59b2f986 Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Tue, 29 Apr 2025 14:30:04 +0100
Subject: [PATCH 01/14] Add tracing support using Micrometer
---
driver-core/build.gradle.kts | 1 +
.../main/com/mongodb/MongoClientSettings.java | 31 ++++++
.../internal/connection/CommandMessage.java | 4 +
.../connection/InternalStreamConnection.java | 51 +++++++++
.../internal/connection/OperationContext.java | 57 ++++++----
.../operation/CommandBatchCursor.java | 7 +-
.../internal/operation/FindOperation.java | 40 +++++--
.../internal/session/ServerSessionPool.java | 3 +-
.../com/mongodb/internal/tracing/Span.java | 52 +++++++++
.../internal/tracing/TraceContext.java | 23 ++++
.../internal/tracing/TracingManager.java | 80 ++++++++++++++
.../internal/tracing/package-info.java | 23 ++++
.../com/mongodb/tracing/MicrometerTracer.java | 102 ++++++++++++++++++
.../src/main/com/mongodb/tracing/Tracer.java | 53 +++++++++
.../com/mongodb/tracing/package-info.java | 23 ++++
.../com/mongodb/ClusterFixture.java | 1 +
driver-core/src/test/resources/specifications | 2 +-
.../connection/CommandHelperTest.java | 4 +-
.../connection/CommandMessageTest.java | 3 +-
.../src/main/com/mongodb/MongoClient.java | 1 +
.../internal/OperationExecutorImpl.java | 2 +
.../client/internal/MongoClientImpl.java | 3 +-
.../client/internal/MongoClusterImpl.java | 17 +--
gradle/libs.versions.toml | 2 +
24 files changed, 546 insertions(+), 39 deletions(-)
create mode 100644 driver-core/src/main/com/mongodb/internal/tracing/Span.java
create mode 100644 driver-core/src/main/com/mongodb/internal/tracing/TraceContext.java
create mode 100644 driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
create mode 100644 driver-core/src/main/com/mongodb/internal/tracing/package-info.java
create mode 100644 driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
create mode 100644 driver-core/src/main/com/mongodb/tracing/Tracer.java
create mode 100644 driver-core/src/main/com/mongodb/tracing/package-info.java
diff --git a/driver-core/build.gradle.kts b/driver-core/build.gradle.kts
index 4f06805a6ea..7e260b18d23 100644
--- a/driver-core/build.gradle.kts
+++ b/driver-core/build.gradle.kts
@@ -54,6 +54,7 @@ dependencies {
optionalImplementation(libs.snappy.java)
optionalImplementation(libs.zstd.jni)
+ optionalImplementation(libs.micrometer)
testImplementation(project(path = ":bson", configuration = "testArtifacts"))
testImplementation(libs.reflections)
diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java
index 31206e56029..642682d1b3a 100644
--- a/driver-core/src/main/com/mongodb/MongoClientSettings.java
+++ b/driver-core/src/main/com/mongodb/MongoClientSettings.java
@@ -30,9 +30,11 @@
import com.mongodb.connection.SslSettings;
import com.mongodb.connection.TransportSettings;
import com.mongodb.event.CommandListener;
+import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import com.mongodb.spi.dns.DnsClient;
import com.mongodb.spi.dns.InetAddressResolver;
+import com.mongodb.tracing.Tracer;
import org.bson.UuidRepresentation;
import org.bson.codecs.BsonCodecProvider;
import org.bson.codecs.BsonValueCodecProvider;
@@ -118,6 +120,7 @@ public final class MongoClientSettings {
private final InetAddressResolver inetAddressResolver;
@Nullable
private final Long timeoutMS;
+ private final TracingManager tracingManager;
/**
* Gets the default codec registry. It includes the following providers:
@@ -238,6 +241,7 @@ public static final class Builder {
private ContextProvider contextProvider;
private DnsClient dnsClient;
private InetAddressResolver inetAddressResolver;
+ private TracingManager tracingManager;
private Builder() {
}
@@ -275,6 +279,7 @@ private Builder(final MongoClientSettings settings) {
if (settings.heartbeatSocketTimeoutSetExplicitly) {
heartbeatSocketTimeoutMS = settings.heartbeatSocketSettings.getReadTimeout(MILLISECONDS);
}
+ tracingManager = settings.tracingManager;
}
/**
@@ -723,6 +728,20 @@ Builder heartbeatSocketTimeoutMS(final int heartbeatSocketTimeoutMS) {
return this;
}
+ /**
+ * Sets the tracer to use for creating Spans for operations and commands.
+ *
+ * @param tracer the tracer
+ * @see com.mongodb.tracing.MicrometerTracer
+ * @return this
+ * @since 5.5
+ */
+ @Alpha(Reason.CLIENT)
+ public Builder tracer(final Tracer tracer) {
+ this.tracingManager = new TracingManager(tracer);
+ return this;
+ }
+
/**
* Build an instance of {@code MongoClientSettings}.
*
@@ -1040,6 +1059,17 @@ public ContextProvider getContextProvider() {
return contextProvider;
}
+ /**
+ * Get the tracer to create Spans for operations and commands.
+ *
+ * @return this
+ * @since 5.5
+ */
+ @Alpha(Reason.CLIENT)
+ public TracingManager getTracingManager() {
+ return tracingManager;
+ }
+
@Override
public boolean equals(final Object o) {
if (this == o) {
@@ -1156,5 +1186,6 @@ private MongoClientSettings(final Builder builder) {
heartbeatConnectTimeoutSetExplicitly = builder.heartbeatConnectTimeoutMS != 0;
contextProvider = builder.contextProvider;
timeoutMS = builder.timeoutMS;
+ tracingManager = builder.tracingManager;
}
}
diff --git a/driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java b/driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java
index 12543e92ccb..0660938e4f2 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java
@@ -186,6 +186,10 @@ BsonDocument getCommandDocument(final ByteBufferBsonOutput bsonOutput) {
}
}
+ BsonDocument getCommand() {
+ return command;
+ }
+
/**
* Get the field name from a buffer positioned at the start of the document sequence identifier of an OP_MSG Section of type
* `PAYLOAD_TYPE_1_DOCUMENT_SEQUENCE`.
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 bf009aa1b07..e477239e6dc 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
@@ -51,6 +51,9 @@
import com.mongodb.internal.logging.StructuredLogger;
import com.mongodb.internal.session.SessionContext;
import com.mongodb.internal.time.Timeout;
+import com.mongodb.internal.tracing.Span;
+import com.mongodb.internal.tracing.TraceContext;
+import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import org.bson.BsonBinaryReader;
import org.bson.BsonDocument;
@@ -374,13 +377,24 @@ public boolean isClosed() {
public T sendAndReceive(final CommandMessage message, final Decoder decoder, final OperationContext operationContext) {
Supplier sendAndReceiveInternal = () -> sendAndReceiveInternal(
message, decoder, operationContext);
+
+ Span tracingSpan = createTracingSpan(message, operationContext);
+
try {
return sendAndReceiveInternal.get();
} catch (MongoCommandException e) {
+ if (tracingSpan != null) {
+ tracingSpan.error(e);
+ }
+
if (reauthenticationIsTriggered(e)) {
return reauthenticateAndRetry(sendAndReceiveInternal, operationContext);
}
throw e;
+ } finally {
+ if (tracingSpan != null) {
+ tracingSpan.end();
+ }
}
}
@@ -391,6 +405,7 @@ public void sendAndReceiveAsync(final CommandMessage message, final Decoder<
AsyncSupplier sendAndReceiveAsyncInternal = c -> sendAndReceiveAsyncInternal(
message, decoder, operationContext, c);
+
beginAsync().thenSupply(c -> {
sendAndReceiveAsyncInternal.getAsync(c);
}).onErrorIf(e -> reauthenticationIsTriggered(e), (t, c) -> {
@@ -872,6 +887,42 @@ public ByteBuf getBuffer(final int size) {
return stream.getBuffer(size);
}
+ @Nullable
+ private Span createTracingSpan(final CommandMessage message, final OperationContext operationContext) {
+ TracingManager tracingManager = operationContext.getTracingManager();
+ Span span;
+ if (tracingManager.isEnabled()) {
+ BsonDocument command = message.getCommand();
+ TraceContext parentContext = null;
+ long cursorId = -1;
+ if (command.containsKey("getMore")) {
+ cursorId = command.getInt64("getMore").longValue();
+ parentContext = tracingManager.getCursorParentContext(cursorId);
+ } else {
+ parentContext = tracingManager.getParentContext(operationContext.getId());
+ }
+
+ span = tracingManager.addSpan("Command " + command.getFirstKey(), parentContext);
+ span.tag("db.system", "mongodb");
+ span.tag("db.namespace", message.getNamespace().getFullName());
+ span.tag("db.query.summary", command.getFirstKey());
+ span.tag("db.query.opcode", String.valueOf(message.getOpCode()));
+ span.tag("db.query.text", command.toString());
+ if (cursorId != -1) {
+ span.tag("db.mongodb.cursor_id", String.valueOf(cursorId));
+ }
+ span.tag("server.address", serverId.getAddress().getHost());
+ span.tag("server.port", String.valueOf(serverId.getAddress().getPort()));
+ span.tag("server.type", message.getSettings().getServerType().name());
+
+ span.tag("db.mongodb.server_connection_id", this.description.getConnectionId().toString());
+ } else {
+ span = null;
+ }
+
+ return span;
+ }
+
private class MessageHeaderCallback implements SingleResultCallback {
private final OperationContext operationContext;
private final SingleResultCallback callback;
diff --git a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
index 7e0de92da1d..c74f4116a3f 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
@@ -27,6 +27,7 @@
import com.mongodb.internal.TimeoutSettings;
import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.session.SessionContext;
+import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import com.mongodb.selector.ServerSelector;
@@ -47,6 +48,7 @@ public class OperationContext {
private final SessionContext sessionContext;
private final RequestContext requestContext;
private final TimeoutContext timeoutContext;
+ private final TracingManager tracingManager;
@Nullable
private final ServerApi serverApi;
@Nullable
@@ -54,12 +56,17 @@ public class OperationContext {
public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext,
@Nullable final ServerApi serverApi) {
- this(requestContext, sessionContext, timeoutContext, serverApi, null);
+ this(requestContext, sessionContext, timeoutContext, TracingManager.NO_OP, serverApi, null);
}
public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext,
- @Nullable final ServerApi serverApi, @Nullable final String operationName) {
- this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, new ServerDeprioritization(), serverApi, operationName);
+ final TracingManager tracingManager,
+ @Nullable final ServerApi serverApi,
+ @Nullable final String operationName) {
+ this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, new ServerDeprioritization(),
+ tracingManager,
+ serverApi,
+ operationName);
}
public static OperationContext simpleOperationContext(
@@ -68,8 +75,10 @@ public static OperationContext simpleOperationContext(
IgnorableRequestContext.INSTANCE,
NoOpSessionContext.INSTANCE,
new TimeoutContext(timeoutSettings),
+ TracingManager.NO_OP,
serverApi,
- null);
+ null
+ );
}
public static OperationContext simpleOperationContext(final TimeoutContext timeoutContext) {
@@ -77,26 +86,34 @@ public static OperationContext simpleOperationContext(final TimeoutContext timeo
IgnorableRequestContext.INSTANCE,
NoOpSessionContext.INSTANCE,
timeoutContext,
+ TracingManager.NO_OP,
null,
null);
}
public OperationContext withSessionContext(final SessionContext sessionContext) {
- return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, serverApi, operationName);
+ return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi,
+ operationName);
}
public OperationContext withTimeoutContext(final TimeoutContext timeoutContext) {
- return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, serverApi, operationName);
+ return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi,
+ operationName);
}
public OperationContext withOperationName(final String operationName) {
- return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, serverApi, operationName);
+ return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi,
+ operationName);
}
public long getId() {
return id;
}
+ public TracingManager getTracingManager() {
+ return tracingManager;
+ }
+
public SessionContext getSessionContext() {
return sessionContext;
}
@@ -121,33 +138,37 @@ public String getOperationName() {
@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
public OperationContext(final long id,
- final RequestContext requestContext,
- final SessionContext sessionContext,
- final TimeoutContext timeoutContext,
- final ServerDeprioritization serverDeprioritization,
- @Nullable final ServerApi serverApi,
- @Nullable final String operationName) {
+ final RequestContext requestContext,
+ final SessionContext sessionContext,
+ final TimeoutContext timeoutContext,
+ final ServerDeprioritization serverDeprioritization,
+ final TracingManager tracingManager,
+ @Nullable final ServerApi serverApi,
+ @Nullable final String operationName) {
this.id = id;
this.serverDeprioritization = serverDeprioritization;
this.requestContext = requestContext;
this.sessionContext = sessionContext;
this.timeoutContext = timeoutContext;
+ this.tracingManager = tracingManager;
this.serverApi = serverApi;
this.operationName = operationName;
}
@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
public OperationContext(final long id,
- final RequestContext requestContext,
- final SessionContext sessionContext,
- final TimeoutContext timeoutContext,
- @Nullable final ServerApi serverApi,
- @Nullable final String operationName) {
+ final RequestContext requestContext,
+ final SessionContext sessionContext,
+ final TimeoutContext timeoutContext,
+ final TracingManager tracingManager,
+ @Nullable final ServerApi serverApi,
+ @Nullable final String operationName) {
this.id = id;
this.serverDeprioritization = new ServerDeprioritization();
this.requestContext = requestContext;
this.sessionContext = sessionContext;
this.timeoutContext = timeoutContext;
+ this.tracingManager = tracingManager;
this.serverApi = serverApi;
this.operationName = operationName;
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/CommandBatchCursor.java b/driver-core/src/main/com/mongodb/internal/operation/CommandBatchCursor.java
index d201976e5ed..a061abafbe9 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/CommandBatchCursor.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/CommandBatchCursor.java
@@ -75,6 +75,7 @@ class CommandBatchCursor implements AggregateResponseBatchCursor {
@Nullable
private List nextBatch;
private boolean resetTimeoutWhenClosing;
+ private final long cursorId;
CommandBatchCursor(
final TimeoutMode timeoutMode,
@@ -95,10 +96,13 @@ class CommandBatchCursor implements AggregateResponseBatchCursor {
operationContext = connectionSource.getOperationContext();
this.timeoutMode = timeoutMode;
+ ServerCursor serverCursor = commandCursorResult.getServerCursor();
+ this.cursorId = serverCursor != null ? serverCursor.getId() : -1;
+
operationContext.getTimeoutContext().setMaxTimeOverride(maxTimeMS);
Connection connectionToPin = connectionSource.getServerDescription().getType() == ServerType.LOAD_BALANCER ? connection : null;
- resourceManager = new ResourceManager(namespace, connectionSource, connectionToPin, commandCursorResult.getServerCursor());
+ resourceManager = new ResourceManager(namespace, connectionSource, connectionToPin, serverCursor);
resetTimeoutWhenClosing = true;
}
@@ -169,6 +173,7 @@ public void remove() {
@Override
public void close() {
+ operationContext.getTracingManager().removeCursorParentContext(cursorId);
resourceManager.close();
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/FindOperation.java b/driver-core/src/main/com/mongodb/internal/operation/FindOperation.java
index ab37613db13..9d21f41d876 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/FindOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/FindOperation.java
@@ -30,6 +30,8 @@
import com.mongodb.internal.binding.AsyncReadBinding;
import com.mongodb.internal.binding.ReadBinding;
import com.mongodb.internal.connection.OperationContext;
+import com.mongodb.internal.tracing.Span;
+import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import org.bson.BsonBoolean;
import org.bson.BsonDocument;
@@ -296,13 +298,18 @@ public BatchCursor execute(final ReadBinding binding) {
if (invalidTimeoutModeException != null) {
throw invalidTimeoutModeException;
}
+ OperationContext operationContext = binding.getOperationContext();
- RetryState retryState = initialRetryState(retryReads, binding.getOperationContext().getTimeoutContext());
- Supplier> read = decorateReadWithRetries(retryState, binding.getOperationContext(), () ->
+ // Adds a Tracing Span for 'find' operation
+ TracingManager tracingManager = operationContext.getTracingManager();
+ Span tracingSpan = tracingManager.addSpan("find", operationContext.getId());
+
+ RetryState retryState = initialRetryState(retryReads, operationContext.getTimeoutContext());
+ Supplier> read = decorateReadWithRetries(retryState, operationContext, () ->
withSourceAndConnection(binding::getReadConnectionSource, false, (source, connection) -> {
- retryState.breakAndThrowIfRetryAnd(() -> !canRetryRead(source.getServerDescription(), binding.getOperationContext()));
+ retryState.breakAndThrowIfRetryAnd(() -> !canRetryRead(source.getServerDescription(), operationContext));
try {
- return createReadCommandAndExecute(retryState, binding.getOperationContext(), source, namespace.getDatabaseName(),
+ return createReadCommandAndExecute(retryState, operationContext, source, namespace.getDatabaseName(),
getCommandCreator(), CommandResultDocumentCodec.create(decoder, FIRST_BATCH),
transformer(), connection);
} catch (MongoCommandException e) {
@@ -310,7 +317,16 @@ public BatchCursor execute(final ReadBinding binding) {
}
})
);
- return read.get();
+ try {
+ return read.get();
+ } catch (MongoQueryException e) {
+ tracingSpan.error(e);
+ throw e;
+ } finally {
+ tracingSpan.end();
+ // Clean up the tracing span after the operation is complete
+ tracingManager.cleanContexts(operationContext.getId());
+ }
}
@Override
@@ -473,9 +489,17 @@ private TimeoutMode getTimeoutMode() {
}
private CommandReadTransformer> transformer() {
- return (result, source, connection) ->
- new CommandBatchCursor<>(getTimeoutMode(), result, batchSize, getMaxTimeForCursor(source.getOperationContext()), decoder,
- comment, source, connection);
+ return (result, source, connection) -> {
+ OperationContext operationContext = source.getOperationContext();
+
+ // register cursor id with the operation context, so 'getMore' commands can be folded under the 'find' operation
+ long cursorId = result.getDocument("cursor").getInt64("id").longValue();
+ TracingManager tracingManager = operationContext.getTracingManager();
+ tracingManager.addCursorParentContext(cursorId, operationContext.getId());
+
+ return new CommandBatchCursor<>(getTimeoutMode(), result, batchSize, getMaxTimeForCursor(operationContext), decoder,
+ comment, source, connection);
+ };
}
private CommandReadTransformerAsync> asyncTransformer() {
diff --git a/driver-core/src/main/com/mongodb/internal/session/ServerSessionPool.java b/driver-core/src/main/com/mongodb/internal/session/ServerSessionPool.java
index 9111eaed3a9..00da3adb822 100644
--- a/driver-core/src/main/com/mongodb/internal/session/ServerSessionPool.java
+++ b/driver-core/src/main/com/mongodb/internal/session/ServerSessionPool.java
@@ -29,6 +29,7 @@
import com.mongodb.internal.connection.NoOpSessionContext;
import com.mongodb.internal.connection.OperationContext;
import com.mongodb.internal.selector.ReadPreferenceServerSelector;
+import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.internal.validator.NoOpFieldNameValidator;
import com.mongodb.lang.Nullable;
import com.mongodb.selector.ServerSelector;
@@ -70,7 +71,7 @@ interface Clock {
public ServerSessionPool(final Cluster cluster, final TimeoutSettings timeoutSettings, @Nullable final ServerApi serverApi) {
this(cluster,
new OperationContext(IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE,
- new TimeoutContext(timeoutSettings.connectionOnly()), serverApi));
+ new TimeoutContext(timeoutSettings.connectionOnly()), serverApi, TracingManager.NO_OP));
}
public ServerSessionPool(final Cluster cluster, final OperationContext operationContext) {
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Span.java b/driver-core/src/main/com/mongodb/internal/tracing/Span.java
new file mode 100644
index 00000000000..10b95bed5af
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/internal/tracing/Span.java
@@ -0,0 +1,52 @@
+/*
+ * 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.tracing;
+
+public interface Span {
+ Span EMPTY = new Span() {
+ @Override
+ public void tag(final String key, final String value) {
+ }
+
+ @Override
+ public void event(final String event) {
+ }
+
+ @Override
+ public void error(final Throwable throwable) {
+ }
+
+ @Override
+ public void end() {
+ }
+
+ @Override
+ public TraceContext context() {
+ return TraceContext.EMPTY;
+ }
+ };
+
+ void tag(String key, String value);
+
+ void event(String event);
+
+ void error(Throwable throwable);
+
+ void end();
+
+ TraceContext context();
+}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/TraceContext.java b/driver-core/src/main/com/mongodb/internal/tracing/TraceContext.java
new file mode 100644
index 00000000000..cb2f6ef1020
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/internal/tracing/TraceContext.java
@@ -0,0 +1,23 @@
+/*
+ * 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.tracing;
+
+@SuppressWarnings("InterfaceIsType")
+public interface TraceContext {
+ TraceContext EMPTY = new TraceContext() {
+ };
+}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
new file mode 100644
index 00000000000..f6bc5d14d81
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
@@ -0,0 +1,80 @@
+/*
+ * 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.tracing;
+
+import com.mongodb.tracing.Tracer;
+
+import java.util.HashMap;
+
+public class TracingManager {
+ public static final TracingManager NO_OP = new TracingManager(Tracer.NO_OP);
+
+ private final Tracer tracer;
+ private final TraceContext parentContext;
+
+ // Map a cursor id to its parent context (useful for folding getMore commands under the parent operation)
+ private final HashMap cursors = new HashMap<>();
+
+ // Map an operation's span context so the subsequent commands spans can fold under the parent operation
+ private final HashMap operationContexts = new HashMap<>();
+
+ public TracingManager(final Tracer tracer) {
+ this.tracer = tracer;
+ this.parentContext = tracer.currentContext();
+ }
+
+ public TracingManager(final Tracer tracer, final TraceContext parentContext) {
+ this.tracer = tracer;
+ this.parentContext = parentContext;
+ }
+
+ public Span addSpan(final String name, final Long operationId) {
+ Span span = tracer.nextSpan(name);
+ operationContexts.put(operationId, span.context());
+ return span;
+ }
+
+ public Span addSpan(final String name, final TraceContext parentContext) {
+ return tracer.nextSpan(name, parentContext);
+ }
+
+ public void cleanContexts(final Long operationId) {
+ operationContexts.remove(operationId);
+ }
+
+ public TraceContext getParentContext(final Long operationId) {
+ assert operationContexts.containsKey(operationId);
+ return operationContexts.get(operationId);
+ }
+
+ public void addCursorParentContext(final long cursorId, final long operationId) {
+ assert operationContexts.containsKey(operationId);
+ cursors.put(cursorId, operationContexts.get(operationId));
+ }
+
+ public TraceContext getCursorParentContext(final long cursorId) {
+ return cursors.get(cursorId);
+ }
+
+ public void removeCursorParentContext(final long cursorId) {
+ cursors.remove(cursorId);
+ }
+
+ public boolean isEnabled() {
+ return tracer.enabled();
+ }
+}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/package-info.java b/driver-core/src/main/com/mongodb/internal/tracing/package-info.java
new file mode 100644
index 00000000000..6b1f711c20b
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/internal/tracing/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Contains classes related to sessions
+ */
+@NonNullApi
+package com.mongodb.internal.tracing;
+
+import com.mongodb.lang.NonNullApi;
diff --git a/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java b/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
new file mode 100644
index 00000000000..b34fa2247b4
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
@@ -0,0 +1,102 @@
+/*
+ * 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.tracing;
+
+import com.mongodb.internal.tracing.Span;
+import com.mongodb.internal.tracing.TraceContext;
+
+public class MicrometerTracer implements Tracer {
+ private final io.micrometer.tracing.Tracer tracer;
+
+ public MicrometerTracer(final io.micrometer.tracing.Tracer tracer) {
+ this.tracer = tracer;
+ }
+
+ @Override
+ public TraceContext currentContext() {
+ return new MicrometerTraceContext(tracer.currentTraceContext().context());
+ }
+
+ @Override
+ public Span nextSpan(final String name) {
+ return new MicrometerSpan(tracer.nextSpan().name(name).start());
+ }
+
+ @Override
+ public Span nextSpan(final String name, final TraceContext parent) {
+ if (parent != null) {
+ io.micrometer.tracing.Span span = tracer.spanBuilder()
+ .name(name)
+ .setParent(((MicrometerTraceContext) parent).getTraceContext())
+ .start();
+ return new MicrometerSpan(span);
+ } else {
+ return nextSpan(name);
+ }
+ }
+
+ @Override
+ public boolean enabled() {
+ return true;
+ }
+
+ private static class MicrometerTraceContext implements TraceContext {
+ private final io.micrometer.tracing.TraceContext traceContext;
+
+ MicrometerTraceContext(final io.micrometer.tracing.TraceContext traceContext) {
+ this.traceContext = traceContext;
+ }
+
+ public io.micrometer.tracing.TraceContext getTraceContext() {
+ return traceContext;
+ }
+ }
+
+ private static class MicrometerSpan implements Span {
+ private final io.micrometer.tracing.Span span;
+
+ MicrometerSpan(final io.micrometer.tracing.Span span) {
+ this.span = span;
+ }
+
+ @Override
+ public void tag(final String key, final String value) {
+ span.tag(key, value);
+ }
+
+ // TODO add variant with TimeUnit
+ @Override
+ public void event(final String event) {
+ span.event(event);
+ }
+
+ @Override
+ public void error(final Throwable throwable) {
+ span.error(throwable);
+ }
+
+ @Override
+ public void end() {
+ span.end();
+ }
+
+ @Override
+ public TraceContext context() {
+ return new MicrometerTraceContext(span.context());
+ }
+ }
+}
diff --git a/driver-core/src/main/com/mongodb/tracing/Tracer.java b/driver-core/src/main/com/mongodb/tracing/Tracer.java
new file mode 100644
index 00000000000..14d7093d4cb
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/tracing/Tracer.java
@@ -0,0 +1,53 @@
+/*
+ * 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.tracing;
+
+import com.mongodb.internal.tracing.Span;
+import com.mongodb.internal.tracing.TraceContext;
+
+public interface Tracer {
+ Tracer NO_OP = new Tracer() {
+
+ @Override
+ public TraceContext currentContext() {
+ return TraceContext.EMPTY;
+ }
+
+ @Override
+ public Span nextSpan(final String name) {
+ return Span.EMPTY;
+ }
+
+ @Override
+ public Span nextSpan(final String name, final TraceContext parent) {
+ return Span.EMPTY;
+ }
+
+ @Override
+ public boolean enabled() {
+ return false;
+ }
+ };
+
+ TraceContext currentContext();
+
+ Span nextSpan(String name); // uses current active span
+
+ Span nextSpan(String name, TraceContext parent); // manually attach the next span to the provided parent
+
+ boolean enabled();
+}
diff --git a/driver-core/src/main/com/mongodb/tracing/package-info.java b/driver-core/src/main/com/mongodb/tracing/package-info.java
new file mode 100644
index 00000000000..2ec7551d300
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/tracing/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Contains classes related to sessions
+ */
+@NonNullApi
+package com.mongodb.tracing;
+
+import com.mongodb.lang.NonNullApi;
diff --git a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java
index 30792bf0487..d2916206a7d 100644
--- a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java
+++ b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java
@@ -71,6 +71,7 @@
import com.mongodb.internal.operation.DropDatabaseOperation;
import com.mongodb.internal.operation.ReadOperation;
import com.mongodb.internal.operation.WriteOperation;
+import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
diff --git a/driver-core/src/test/resources/specifications b/driver-core/src/test/resources/specifications
index c13d23b91b4..61270d61656 160000
--- a/driver-core/src/test/resources/specifications
+++ b/driver-core/src/test/resources/specifications
@@ -1 +1 @@
-Subproject commit c13d23b91b422b348c54195fe1c49406fc457559
+Subproject commit 61270d61656709944e6b75e160453e3bfa658483
diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java
index f7873379c3b..f40c7d17f20 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java
@@ -26,6 +26,7 @@
import com.mongodb.internal.IgnorableRequestContext;
import com.mongodb.internal.TimeoutContext;
import com.mongodb.internal.TimeoutSettings;
+import com.mongodb.internal.tracing.TracingManager;
import org.bson.BsonDocument;
import org.bson.codecs.Decoder;
import org.junit.jupiter.api.Test;
@@ -118,9 +119,8 @@ void testIsCommandOk() {
assertFalse(CommandHelper.isCommandOk(new BsonDocument()));
}
-
OperationContext createOperationContext() {
return new OperationContext(IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE,
- new TimeoutContext(TimeoutSettings.DEFAULT), ServerApi.builder().version(ServerApiVersion.V1).build());
+ new TimeoutContext(TimeoutSettings.DEFAULT), ServerApi.builder().version(ServerApiVersion.V1).build(), TracingManager.NO_OP);
}
}
diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageTest.java
index 533e74f0d23..3ceb7567eef 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageTest.java
@@ -32,6 +32,7 @@
import com.mongodb.internal.operation.ClientBulkWriteOperation;
import com.mongodb.internal.operation.ClientBulkWriteOperation.ClientBulkWriteCommand.OpsAndNsInfo;
import com.mongodb.internal.session.SessionContext;
+import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.internal.validator.NoOpFieldNameValidator;
import org.bson.BsonArray;
import org.bson.BsonBoolean;
@@ -163,7 +164,7 @@ void getCommandDocumentFromClientBulkWrite() {
output,
new OperationContext(
IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE,
- new TimeoutContext(TimeoutSettings.DEFAULT), null));
+ new TimeoutContext(TimeoutSettings.DEFAULT), null, TracingManager.NO_OP));
BsonDocument actualCommandDocument = commandMessage.getCommandDocument(output);
assertEquals(expectedCommandDocument, actualCommandDocument);
}
diff --git a/driver-legacy/src/main/com/mongodb/MongoClient.java b/driver-legacy/src/main/com/mongodb/MongoClient.java
index 09d58e1b493..9478dc76177 100644
--- a/driver-legacy/src/main/com/mongodb/MongoClient.java
+++ b/driver-legacy/src/main/com/mongodb/MongoClient.java
@@ -43,6 +43,7 @@
import com.mongodb.internal.diagnostics.logging.Loggers;
import com.mongodb.internal.session.ServerSessionPool;
import com.mongodb.internal.thread.DaemonThreadFactory;
+import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.internal.validator.NoOpFieldNameValidator;
import com.mongodb.lang.Nullable;
import org.bson.BsonArray;
diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java
index dacf0c9b82e..1689c9b31e9 100644
--- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java
+++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java
@@ -34,6 +34,7 @@
import com.mongodb.internal.operation.AsyncReadOperation;
import com.mongodb.internal.operation.AsyncWriteOperation;
import com.mongodb.internal.operation.OperationHelper;
+import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import com.mongodb.reactivestreams.client.ClientSession;
import com.mongodb.reactivestreams.client.ReactiveContextProvider;
@@ -204,6 +205,7 @@ private OperationContext getOperationContext(final RequestContext requestContext
requestContext,
new ReadConcernAwareNoOpSessionContext(readConcern),
createTimeoutContext(session, timeoutSettings),
+ mongoClient.getSettings().getTracingManager(),
mongoClient.getSettings().getServerApi(),
commandName);
}
diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java
index 6870277b1c6..acb19fe12d9 100644
--- a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java
+++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java
@@ -106,7 +106,8 @@ public MongoClientImpl(final Cluster cluster,
operationExecutor, settings.getReadConcern(), settings.getReadPreference(), settings.getRetryReads(),
settings.getRetryWrites(), settings.getServerApi(),
new ServerSessionPool(cluster, TimeoutSettings.create(settings), settings.getServerApi()),
- TimeoutSettings.create(settings), settings.getUuidRepresentation(), settings.getWriteConcern());
+ TimeoutSettings.create(settings), settings.getUuidRepresentation(),
+ settings.getWriteConcern(), settings.getTracingManager());
this.closed = new AtomicBoolean();
BsonDocument clientMetadataDocument = delegate.getCluster().getClientMetadata().getBsonDocument();
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 0430d9407c1..5944edae4e2 100644
--- a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java
+++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java
@@ -57,6 +57,7 @@
import com.mongodb.internal.operation.SyncOperations;
import com.mongodb.internal.operation.WriteOperation;
import com.mongodb.internal.session.ServerSessionPool;
+import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import org.bson.BsonDocument;
import org.bson.Document;
@@ -99,6 +100,7 @@ final class MongoClusterImpl implements MongoCluster {
private final UuidRepresentation uuidRepresentation;
private final WriteConcern writeConcern;
private final SyncOperations operations;
+ private final TracingManager tracingManager;
MongoClusterImpl(
@Nullable final AutoEncryptionSettings autoEncryptionSettings, final Cluster cluster, final CodecRegistry codecRegistry,
@@ -106,7 +108,8 @@ final class MongoClusterImpl implements MongoCluster {
@Nullable final OperationExecutor operationExecutor, final ReadConcern readConcern, final ReadPreference readPreference,
final boolean retryReads, final boolean retryWrites, @Nullable final ServerApi serverApi,
final ServerSessionPool serverSessionPool, final TimeoutSettings timeoutSettings, final UuidRepresentation uuidRepresentation,
- final WriteConcern writeConcern) {
+ final WriteConcern writeConcern,
+ final TracingManager tracingManager) {
this.autoEncryptionSettings = autoEncryptionSettings;
this.cluster = cluster;
this.codecRegistry = codecRegistry;
@@ -123,6 +126,8 @@ final class MongoClusterImpl implements MongoCluster {
this.timeoutSettings = timeoutSettings;
this.uuidRepresentation = uuidRepresentation;
this.writeConcern = writeConcern;
+ this.tracingManager = tracingManager;
+
operations = new SyncOperations<>(
null,
BsonDocument.class,
@@ -165,35 +170,35 @@ public Long getTimeout(final TimeUnit timeUnit) {
public MongoCluster withCodecRegistry(final CodecRegistry codecRegistry) {
return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator,
operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, timeoutSettings,
- uuidRepresentation, writeConcern);
+ uuidRepresentation, writeConcern, tracingManager);
}
@Override
public MongoCluster withReadPreference(final ReadPreference readPreference) {
return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator,
operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, timeoutSettings,
- uuidRepresentation, writeConcern);
+ uuidRepresentation, writeConcern, tracingManager);
}
@Override
public MongoCluster withWriteConcern(final WriteConcern writeConcern) {
return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator,
operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, timeoutSettings,
- uuidRepresentation, writeConcern);
+ uuidRepresentation, writeConcern, tracingManager);
}
@Override
public MongoCluster withReadConcern(final ReadConcern readConcern) {
return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator,
operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, timeoutSettings,
- uuidRepresentation, writeConcern);
+ uuidRepresentation, writeConcern, tracingManager);
}
@Override
public MongoCluster withTimeout(final long timeout, final TimeUnit timeUnit) {
return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator,
operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool,
- timeoutSettings.withTimeout(timeout, timeUnit), uuidRepresentation, writeConcern);
+ timeoutSettings.withTimeout(timeout, timeUnit), uuidRepresentation, writeConcern, tracingManager);
}
@Override
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8b8222d66e5..90aec4c1c38 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -24,6 +24,7 @@ reactive-streams = "1.0.4"
snappy = "1.1.10.3"
zstd = "1.5.5-3"
jetbrains-annotations = "26.0.2"
+micrometer = "1.4.5"
kotlin = "1.8.10"
kotlinx-coroutines-bom = "1.6.4"
@@ -93,6 +94,7 @@ reactive-streams = { module = " org.reactivestreams:reactive-streams", version.r
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
snappy-java = { module = "org.xerial.snappy:snappy-java", version.ref = "snappy" }
zstd-jni = { module = "com.github.luben:zstd-jni", version.ref = "zstd" }
+micrometer = { module = "io.micrometer:micrometer-tracing", version.ref = "micrometer" }
graal-sdk = { module = "org.graalvm.sdk:graal-sdk", version.ref = "graal-sdk" }
graal-sdk-nativeimage = { module = "org.graalvm.sdk:nativeimage", version.ref = "graal-sdk" }
From 4a16c29f00fe1c2a251fea0712243a05a9d9ebe3 Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Mon, 4 Aug 2025 00:29:29 +0100
Subject: [PATCH 02/14] Simplifying bookkeeping logic for Span grouping
---
.../main/com/mongodb/MongoClientSettings.java | 28 +-
.../internal/connection/CommandMessage.java | 4 -
.../connection/InternalStreamConnection.java | 186 +++++++----
.../connection/LoggingCommandEventSender.java | 4 +-
.../internal/connection/OperationContext.java | 27 +-
.../operation/CommandBatchCursor.java | 7 +-
.../internal/operation/FindOperation.java | 31 +-
.../internal/session/ServerSessionPool.java | 3 +-
.../com/mongodb/internal/tracing/Span.java | 69 +++-
.../com/mongodb/internal/tracing/Tags.java | 42 +++
.../com/mongodb/internal/tracing/Tracer.java | 97 ++++++
.../internal/tracing/TracingManager.java | 106 ++++---
.../internal/tracing/TransactionSpan.java | 110 +++++++
.../internal/tracing/package-info.java | 2 +-
.../com/mongodb/tracing/MicrometerTracer.java | 88 +++++-
.../src/main/com/mongodb/tracing/Tracer.java | 53 ----
.../com/mongodb/tracing/package-info.java | 4 +-
.../com/mongodb/ClusterFixture.java | 1 -
.../connection/CommandHelperTest.java | 3 +-
.../connection/CommandMessageTest.java | 3 +-
.../syncadapter/SyncClientSession.kt | 3 +
.../client/syncadapter/SyncClientSession.kt | 3 +
.../src/main/com/mongodb/MongoClient.java | 1 -
.../internal/OperationExecutorImpl.java | 2 +-
.../client/syncadapter/SyncClientSession.java | 7 +
.../scala/syncadapter/SyncClientSession.scala | 3 +
driver-sync/build.gradle.kts | 3 +
.../com/mongodb/client/ClientSession.java | 10 +
.../client/internal/ClientSessionImpl.java | 114 ++++---
.../client/internal/MongoClientImpl.java | 3 +-
.../client/internal/MongoClusterImpl.java | 53 +++-
.../com/mongodb/client/tracing/SpanTree.java | 295 ++++++++++++++++++
.../mongodb/client/tracing/ZipkinTracer.java | 95 ++++++
gradle/libs.versions.toml | 11 +
34 files changed, 1195 insertions(+), 276 deletions(-)
create mode 100644 driver-core/src/main/com/mongodb/internal/tracing/Tags.java
create mode 100644 driver-core/src/main/com/mongodb/internal/tracing/Tracer.java
create mode 100644 driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java
delete mode 100644 driver-core/src/main/com/mongodb/tracing/Tracer.java
create mode 100644 driver-sync/src/test/functional/com/mongodb/client/tracing/SpanTree.java
create mode 100644 driver-sync/src/test/functional/com/mongodb/client/tracing/ZipkinTracer.java
diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java
index 642682d1b3a..938b9c76068 100644
--- a/driver-core/src/main/com/mongodb/MongoClientSettings.java
+++ b/driver-core/src/main/com/mongodb/MongoClientSettings.java
@@ -30,11 +30,10 @@
import com.mongodb.connection.SslSettings;
import com.mongodb.connection.TransportSettings;
import com.mongodb.event.CommandListener;
-import com.mongodb.internal.tracing.TracingManager;
+import com.mongodb.internal.tracing.Tracer;
import com.mongodb.lang.Nullable;
import com.mongodb.spi.dns.DnsClient;
import com.mongodb.spi.dns.InetAddressResolver;
-import com.mongodb.tracing.Tracer;
import org.bson.UuidRepresentation;
import org.bson.codecs.BsonCodecProvider;
import org.bson.codecs.BsonValueCodecProvider;
@@ -120,7 +119,7 @@ public final class MongoClientSettings {
private final InetAddressResolver inetAddressResolver;
@Nullable
private final Long timeoutMS;
- private final TracingManager tracingManager;
+ private final Tracer tracer;
/**
* Gets the default codec registry. It includes the following providers:
@@ -241,7 +240,7 @@ public static final class Builder {
private ContextProvider contextProvider;
private DnsClient dnsClient;
private InetAddressResolver inetAddressResolver;
- private TracingManager tracingManager;
+ private Tracer tracer;
private Builder() {
}
@@ -279,7 +278,7 @@ private Builder(final MongoClientSettings settings) {
if (settings.heartbeatSocketTimeoutSetExplicitly) {
heartbeatSocketTimeoutMS = settings.heartbeatSocketSettings.getReadTimeout(MILLISECONDS);
}
- tracingManager = settings.tracingManager;
+ tracer = settings.tracer;
}
/**
@@ -729,16 +728,16 @@ Builder heartbeatSocketTimeoutMS(final int heartbeatSocketTimeoutMS) {
}
/**
- * Sets the tracer to use for creating Spans for operations and commands.
+ * Sets the tracer to use for creating Spans for operations, commands and transactions.
*
* @param tracer the tracer
* @see com.mongodb.tracing.MicrometerTracer
* @return this
- * @since 5.5
+ * @since 5.6
*/
@Alpha(Reason.CLIENT)
public Builder tracer(final Tracer tracer) {
- this.tracingManager = new TracingManager(tracer);
+ this.tracer = tracer;
return this;
}
@@ -1060,14 +1059,13 @@ public ContextProvider getContextProvider() {
}
/**
- * Get the tracer to create Spans for operations and commands.
+ * Get the tracer to create Spans for operations, commands and transactions.
*
- * @return this
- * @since 5.5
+ * @return the configured Tracer
+ * @since 5.6
*/
- @Alpha(Reason.CLIENT)
- public TracingManager getTracingManager() {
- return tracingManager;
+ public Tracer getTracer() {
+ return tracer;
}
@Override
@@ -1186,6 +1184,6 @@ private MongoClientSettings(final Builder builder) {
heartbeatConnectTimeoutSetExplicitly = builder.heartbeatConnectTimeoutMS != 0;
contextProvider = builder.contextProvider;
timeoutMS = builder.timeoutMS;
- tracingManager = builder.tracingManager;
+ tracer = (builder.tracer == null) ? Tracer.NO_OP : builder.tracer;
}
}
diff --git a/driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java b/driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java
index 0660938e4f2..12543e92ccb 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java
@@ -186,10 +186,6 @@ BsonDocument getCommandDocument(final ByteBufferBsonOutput bsonOutput) {
}
}
- BsonDocument getCommand() {
- return command;
- }
-
/**
* Get the field name from a buffer positioned at the start of the document sequence identifier of an OP_MSG Section of type
* `PAYLOAD_TYPE_1_DOCUMENT_SEQUENCE`.
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 e477239e6dc..bd3008cf927 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
@@ -52,7 +52,6 @@
import com.mongodb.internal.session.SessionContext;
import com.mongodb.internal.time.Timeout;
import com.mongodb.internal.tracing.Span;
-import com.mongodb.internal.tracing.TraceContext;
import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import org.bson.BsonBinaryReader;
@@ -97,6 +96,18 @@
import static com.mongodb.internal.connection.ProtocolHelper.isCommandOk;
import static com.mongodb.internal.logging.LogMessage.Level.DEBUG;
import static com.mongodb.internal.thread.InterruptionUtil.translateInterruptedException;
+import static com.mongodb.internal.tracing.Tags.CLIENT_CONNECTION_ID;
+import static com.mongodb.internal.tracing.Tags.CURSOR_ID;
+import static com.mongodb.internal.tracing.Tags.NAMESPACE;
+import static com.mongodb.internal.tracing.Tags.QUERY_SUMMARY;
+import static com.mongodb.internal.tracing.Tags.QUERY_TEXT;
+import static com.mongodb.internal.tracing.Tags.SERVER_ADDRESS;
+import static com.mongodb.internal.tracing.Tags.SERVER_CONNECTION_ID;
+import static com.mongodb.internal.tracing.Tags.SERVER_PORT;
+import static com.mongodb.internal.tracing.Tags.SERVER_TYPE;
+import static com.mongodb.internal.tracing.Tags.SESSION_ID;
+import static com.mongodb.internal.tracing.Tags.SYSTEM;
+import static com.mongodb.internal.tracing.Tags.TRANSACTION_NUMBER;
import static java.util.Arrays.asList;
/**
@@ -377,24 +388,13 @@ public boolean isClosed() {
public T sendAndReceive(final CommandMessage message, final Decoder decoder, final OperationContext operationContext) {
Supplier sendAndReceiveInternal = () -> sendAndReceiveInternal(
message, decoder, operationContext);
-
- Span tracingSpan = createTracingSpan(message, operationContext);
-
try {
return sendAndReceiveInternal.get();
- } catch (MongoCommandException e) {
- if (tracingSpan != null) {
- tracingSpan.error(e);
- }
-
+ } catch (Throwable e) {
if (reauthenticationIsTriggered(e)) {
return reauthenticateAndRetry(sendAndReceiveInternal, operationContext);
}
throw e;
- } finally {
- if (tracingSpan != null) {
- tracingSpan.end();
- }
}
}
@@ -406,9 +406,7 @@ public void sendAndReceiveAsync(final CommandMessage message, final Decoder<
AsyncSupplier sendAndReceiveAsyncInternal = c -> sendAndReceiveAsyncInternal(
message, decoder, operationContext, c);
- beginAsync().thenSupply(c -> {
- sendAndReceiveAsyncInternal.getAsync(c);
- }).onErrorIf(e -> reauthenticationIsTriggered(e), (t, c) -> {
+ beginAsync().thenSupply(sendAndReceiveAsyncInternal::getAsync).onErrorIf(this::reauthenticationIsTriggered, (t, c) -> {
reauthenticateAndRetryAsync(sendAndReceiveAsyncInternal, operationContext, c);
}).finish(callback);
}
@@ -447,15 +445,44 @@ public boolean reauthenticationIsTriggered(@Nullable final Throwable t) {
private T sendAndReceiveInternal(final CommandMessage message, final Decoder decoder,
final OperationContext operationContext) {
CommandEventSender commandEventSender;
+
try (ByteBufferBsonOutput bsonOutput = new ByteBufferBsonOutput(this)) {
message.encode(bsonOutput, operationContext);
- commandEventSender = createCommandEventSender(message, bsonOutput, operationContext);
- commandEventSender.sendStartedEvent();
+ Span tracingSpan = createTracingSpan(message, operationContext, bsonOutput);
+
+ boolean isLoggingCommandNeeded = isLoggingCommandNeeded();
+ boolean isTracingCommandPayloadNeeded = tracingSpan != null && operationContext.getTracingManager().isCommandPayloadEnabled();
+
+ // Only hydrate the command document if necessary
+ BsonDocument commandDocument = null;
+ if (isLoggingCommandNeeded || isTracingCommandPayloadNeeded) {
+ commandDocument = message.getCommandDocument(bsonOutput);
+ }
+ if (isLoggingCommandNeeded) {
+ commandEventSender = new LoggingCommandEventSender(
+ SECURITY_SENSITIVE_COMMANDS, SECURITY_SENSITIVE_HELLO_COMMANDS, description, commandListener,
+ operationContext, message, commandDocument,
+ COMMAND_PROTOCOL_LOGGER, loggerSettings);
+ commandEventSender.sendStartedEvent();
+ } else {
+ commandEventSender = new NoOpCommandEventSender();
+ }
+ if (isTracingCommandPayloadNeeded) {
+ tracingSpan.tag(QUERY_TEXT, commandDocument.toJson());
+ }
+
try {
sendCommandMessage(message, bsonOutput, operationContext);
} catch (Exception e) {
+ if (tracingSpan != null) {
+ tracingSpan.error(e);
+ }
commandEventSender.sendFailedEvent(e);
throw e;
+ } finally {
+ if (tracingSpan != null) {
+ tracingSpan.end();
+ }
}
}
@@ -568,7 +595,18 @@ private void sendAndReceiveAsyncInternal(final CommandMessage message, final
try {
message.encode(bsonOutput, operationContext);
- CommandEventSender commandEventSender = createCommandEventSender(message, bsonOutput, operationContext);
+
+ CommandEventSender commandEventSender;
+ if (isLoggingCommandNeeded()) {
+ BsonDocument commandDocument = message.getCommandDocument(bsonOutput);
+ commandEventSender = new LoggingCommandEventSender(
+ SECURITY_SENSITIVE_COMMANDS, SECURITY_SENSITIVE_HELLO_COMMANDS, description, commandListener,
+ operationContext, message, commandDocument,
+ COMMAND_PROTOCOL_LOGGER, loggerSettings);
+ } else {
+ commandEventSender = new NoOpCommandEventSender();
+ }
+
commandEventSender.sendStartedEvent();
Compressor localSendCompressor = sendCompressor;
if (localSendCompressor == null || SECURITY_SENSITIVE_COMMANDS.contains(message.getCommandDocument(bsonOutput).getFirstKey())) {
@@ -887,42 +925,6 @@ public ByteBuf getBuffer(final int size) {
return stream.getBuffer(size);
}
- @Nullable
- private Span createTracingSpan(final CommandMessage message, final OperationContext operationContext) {
- TracingManager tracingManager = operationContext.getTracingManager();
- Span span;
- if (tracingManager.isEnabled()) {
- BsonDocument command = message.getCommand();
- TraceContext parentContext = null;
- long cursorId = -1;
- if (command.containsKey("getMore")) {
- cursorId = command.getInt64("getMore").longValue();
- parentContext = tracingManager.getCursorParentContext(cursorId);
- } else {
- parentContext = tracingManager.getParentContext(operationContext.getId());
- }
-
- span = tracingManager.addSpan("Command " + command.getFirstKey(), parentContext);
- span.tag("db.system", "mongodb");
- span.tag("db.namespace", message.getNamespace().getFullName());
- span.tag("db.query.summary", command.getFirstKey());
- span.tag("db.query.opcode", String.valueOf(message.getOpCode()));
- span.tag("db.query.text", command.toString());
- if (cursorId != -1) {
- span.tag("db.mongodb.cursor_id", String.valueOf(cursorId));
- }
- span.tag("server.address", serverId.getAddress().getHost());
- span.tag("server.port", String.valueOf(serverId.getAddress().getPort()));
- span.tag("server.type", message.getSettings().getServerType().name());
-
- span.tag("db.mongodb.server_connection_id", this.description.getConnectionId().toString());
- } else {
- span = null;
- }
-
- return span;
- }
-
private class MessageHeaderCallback implements SingleResultCallback {
private final OperationContext operationContext;
private final SingleResultCallback callback;
@@ -1003,19 +1005,75 @@ public void onResult(@Nullable final ByteBuf result, @Nullable final Throwable t
private static final StructuredLogger COMMAND_PROTOCOL_LOGGER = new StructuredLogger("protocol.command");
- private CommandEventSender createCommandEventSender(final CommandMessage message, final ByteBufferBsonOutput bsonOutput,
- final OperationContext operationContext) {
+ private boolean isLoggingCommandNeeded() {
boolean listensOrLogs = commandListener != null || COMMAND_PROTOCOL_LOGGER.isRequired(DEBUG, getClusterId());
- if (!recordEverything && (isMonitoringConnection || !opened() || !authenticated.get() || !listensOrLogs)) {
- return new NoOpCommandEventSender();
- }
- return new LoggingCommandEventSender(
- SECURITY_SENSITIVE_COMMANDS, SECURITY_SENSITIVE_HELLO_COMMANDS, description, commandListener,
- operationContext, message, bsonOutput,
- COMMAND_PROTOCOL_LOGGER, loggerSettings);
+ return recordEverything || (!isMonitoringConnection && opened() && authenticated.get() && listensOrLogs);
}
private ClusterId getClusterId() {
return description.getConnectionId().getServerId().getClusterId();
}
+
+ /**
+ * Creates a tracing span for the given command message.
+ *
+ * The span is only created if tracing is enabled and the command is not security-sensitive.
+ * It attaches various tags to the span, such as database system, namespace, query summary, opcode,
+ * server address, port, server type, client and server connection IDs, and, if applicable,
+ * transaction number and session ID. For cursor fetching commands, the parent context is retrieved using the cursor ID.
+ * If command payload tracing is enabled, the command document is also attached as a tag.
+ *
+ * @param message the command message to trace
+ * @param operationContext the operation context containing tracing and session information
+ * @param bsonOutput the BSON output used to serialize the command
+ * @return the created {@link Span}, or {@code null} if tracing is not enabled or the command is security-sensitive
+ */
+ @Nullable
+ private Span createTracingSpan(final CommandMessage message, final OperationContext operationContext, final ByteBufferBsonOutput bsonOutput) {
+
+ TracingManager tracingManager = operationContext.getTracingManager();
+ BsonDocument command = message.getCommandDocument(bsonOutput);
+
+// BsonDocument command = message.getCommand();
+ String commandName = command.getFirstKey();
+// Span newSpan = tracingManager.addSpan("_____Command_____[ " + commandName + " ]", myparentContext);
+ if (!tracingManager.isEnabled()
+ || SECURITY_SENSITIVE_COMMANDS.contains(commandName)
+ || SECURITY_SENSITIVE_HELLO_COMMANDS.contains(commandName)) {
+ return null;
+ }
+
+ Span span = tracingManager
+ .addSpan("Command " + commandName, operationContext.getTracingSpanContext())
+ .tag(SYSTEM, "mongodb")
+ .tag(NAMESPACE, message.getNamespace().getDatabaseName())
+ .tag(QUERY_SUMMARY, commandName);
+
+ if (command.containsKey("getMore")) {
+ span.tag(CURSOR_ID, command.getInt64("getMore").longValue());
+ }
+
+ tagServerAndConnectionInfo(span, message);
+ tagSessionAndTransactionInfo(span, operationContext);
+
+ return span;
+ }
+
+ private void tagServerAndConnectionInfo(final Span span, final CommandMessage message) {
+ span.tag(SERVER_ADDRESS, serverId.getAddress().getHost())
+ .tag(SERVER_PORT, String.valueOf(serverId.getAddress().getPort()))
+ .tag(SERVER_TYPE, message.getSettings().getServerType().name())
+ .tag(CLIENT_CONNECTION_ID, this.description.getConnectionId().toString())
+ .tag(SERVER_CONNECTION_ID, String.valueOf(this.description.getConnectionId().getServerValue()));
+ }
+
+ private void tagSessionAndTransactionInfo(final Span span, final OperationContext operationContext) {
+ SessionContext sessionContext = operationContext.getSessionContext();
+ if (sessionContext.hasSession() && !sessionContext.isImplicitSession()) {
+ span.tag(TRANSACTION_NUMBER, String.valueOf(sessionContext.getTransactionNumber()))
+ .tag(SESSION_ID, String.valueOf(sessionContext.getSessionId()
+ .get(sessionContext.getSessionId().getFirstKey())
+ .asBinary().asUuid()));
+ }
+ }
}
diff --git a/driver-core/src/main/com/mongodb/internal/connection/LoggingCommandEventSender.java b/driver-core/src/main/com/mongodb/internal/connection/LoggingCommandEventSender.java
index 3821ca947c6..136ec10a1dd 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/LoggingCommandEventSender.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/LoggingCommandEventSender.java
@@ -78,7 +78,7 @@ class LoggingCommandEventSender implements CommandEventSender {
@Nullable final CommandListener commandListener,
final OperationContext operationContext,
final CommandMessage message,
- final ByteBufferBsonOutput bsonOutput,
+ final BsonDocument commandDocument,
final StructuredLogger logger,
final LoggerSettings loggerSettings) {
this.description = description;
@@ -88,7 +88,7 @@ class LoggingCommandEventSender implements CommandEventSender {
this.loggerSettings = loggerSettings;
this.startTimeNanos = System.nanoTime();
this.message = message;
- this.commandDocument = message.getCommandDocument(bsonOutput);
+ this.commandDocument = commandDocument;
this.commandName = commandDocument.getFirstKey();
this.redactionRequired = securitySensitiveCommands.contains(commandName)
|| (securitySensitiveHelloCommands.contains(commandName) && commandDocument.containsKey("speculativeAuthenticate"));
diff --git a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
index c74f4116a3f..9f1c232b274 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
@@ -27,6 +27,7 @@
import com.mongodb.internal.TimeoutSettings;
import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.session.SessionContext;
+import com.mongodb.internal.tracing.TraceContext;
import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import com.mongodb.selector.ServerSelector;
@@ -53,6 +54,8 @@ public class OperationContext {
private final ServerApi serverApi;
@Nullable
private final String operationName;
+ @Nullable
+ private TraceContext tracingContext;
public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext,
@Nullable final ServerApi serverApi) {
@@ -66,7 +69,8 @@ public OperationContext(final RequestContext requestContext, final SessionContex
this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, new ServerDeprioritization(),
tracingManager,
serverApi,
- operationName);
+ operationName,
+ null);
}
public static OperationContext simpleOperationContext(
@@ -93,17 +97,17 @@ public static OperationContext simpleOperationContext(final TimeoutContext timeo
public OperationContext withSessionContext(final SessionContext sessionContext) {
return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi,
- operationName);
+ operationName, tracingContext);
}
public OperationContext withTimeoutContext(final TimeoutContext timeoutContext) {
return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi,
- operationName);
+ operationName, tracingContext);
}
public OperationContext withOperationName(final String operationName) {
return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi,
- operationName);
+ operationName, tracingContext);
}
public long getId() {
@@ -136,6 +140,15 @@ public String getOperationName() {
return operationName;
}
+ @Nullable
+ public TraceContext getTracingSpanContext() {
+ return tracingContext != null ? tracingContext : null;
+ }
+
+ public void setTracingContext(final TraceContext tracingContext) {
+ this.tracingContext = tracingContext;
+ }
+
@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
public OperationContext(final long id,
final RequestContext requestContext,
@@ -144,7 +157,9 @@ public OperationContext(final long id,
final ServerDeprioritization serverDeprioritization,
final TracingManager tracingManager,
@Nullable final ServerApi serverApi,
- @Nullable final String operationName) {
+ @Nullable final String operationName,
+ @Nullable final TraceContext tracingContext) {
+
this.id = id;
this.serverDeprioritization = serverDeprioritization;
this.requestContext = requestContext;
@@ -153,6 +168,7 @@ public OperationContext(final long id,
this.tracingManager = tracingManager;
this.serverApi = serverApi;
this.operationName = operationName;
+ this.tracingContext = tracingContext;
}
@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
@@ -171,6 +187,7 @@ public OperationContext(final long id,
this.tracingManager = tracingManager;
this.serverApi = serverApi;
this.operationName = operationName;
+ this.tracingContext = null;
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/CommandBatchCursor.java b/driver-core/src/main/com/mongodb/internal/operation/CommandBatchCursor.java
index a061abafbe9..d201976e5ed 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/CommandBatchCursor.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/CommandBatchCursor.java
@@ -75,7 +75,6 @@ class CommandBatchCursor implements AggregateResponseBatchCursor {
@Nullable
private List nextBatch;
private boolean resetTimeoutWhenClosing;
- private final long cursorId;
CommandBatchCursor(
final TimeoutMode timeoutMode,
@@ -96,13 +95,10 @@ class CommandBatchCursor implements AggregateResponseBatchCursor {
operationContext = connectionSource.getOperationContext();
this.timeoutMode = timeoutMode;
- ServerCursor serverCursor = commandCursorResult.getServerCursor();
- this.cursorId = serverCursor != null ? serverCursor.getId() : -1;
-
operationContext.getTimeoutContext().setMaxTimeOverride(maxTimeMS);
Connection connectionToPin = connectionSource.getServerDescription().getType() == ServerType.LOAD_BALANCER ? connection : null;
- resourceManager = new ResourceManager(namespace, connectionSource, connectionToPin, serverCursor);
+ resourceManager = new ResourceManager(namespace, connectionSource, connectionToPin, commandCursorResult.getServerCursor());
resetTimeoutWhenClosing = true;
}
@@ -173,7 +169,6 @@ public void remove() {
@Override
public void close() {
- operationContext.getTracingManager().removeCursorParentContext(cursorId);
resourceManager.close();
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/FindOperation.java b/driver-core/src/main/com/mongodb/internal/operation/FindOperation.java
index 9d21f41d876..84039a03296 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/FindOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/FindOperation.java
@@ -30,8 +30,6 @@
import com.mongodb.internal.binding.AsyncReadBinding;
import com.mongodb.internal.binding.ReadBinding;
import com.mongodb.internal.connection.OperationContext;
-import com.mongodb.internal.tracing.Span;
-import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import org.bson.BsonBoolean;
import org.bson.BsonDocument;
@@ -298,18 +296,13 @@ public BatchCursor execute(final ReadBinding binding) {
if (invalidTimeoutModeException != null) {
throw invalidTimeoutModeException;
}
- OperationContext operationContext = binding.getOperationContext();
- // Adds a Tracing Span for 'find' operation
- TracingManager tracingManager = operationContext.getTracingManager();
- Span tracingSpan = tracingManager.addSpan("find", operationContext.getId());
-
- RetryState retryState = initialRetryState(retryReads, operationContext.getTimeoutContext());
- Supplier> read = decorateReadWithRetries(retryState, operationContext, () ->
+ RetryState retryState = initialRetryState(retryReads, binding.getOperationContext().getTimeoutContext());
+ Supplier> read = decorateReadWithRetries(retryState, binding.getOperationContext(), () ->
withSourceAndConnection(binding::getReadConnectionSource, false, (source, connection) -> {
- retryState.breakAndThrowIfRetryAnd(() -> !canRetryRead(source.getServerDescription(), operationContext));
+ retryState.breakAndThrowIfRetryAnd(() -> !canRetryRead(source.getServerDescription(), binding.getOperationContext()));
try {
- return createReadCommandAndExecute(retryState, operationContext, source, namespace.getDatabaseName(),
+ return createReadCommandAndExecute(retryState, binding.getOperationContext(), source, namespace.getDatabaseName(),
getCommandCreator(), CommandResultDocumentCodec.create(decoder, FIRST_BATCH),
transformer(), connection);
} catch (MongoCommandException e) {
@@ -317,16 +310,7 @@ public BatchCursor execute(final ReadBinding binding) {
}
})
);
- try {
- return read.get();
- } catch (MongoQueryException e) {
- tracingSpan.error(e);
- throw e;
- } finally {
- tracingSpan.end();
- // Clean up the tracing span after the operation is complete
- tracingManager.cleanContexts(operationContext.getId());
- }
+ return read.get();
}
@Override
@@ -492,11 +476,6 @@ private CommandReadTransformer> transformer(
return (result, source, connection) -> {
OperationContext operationContext = source.getOperationContext();
- // register cursor id with the operation context, so 'getMore' commands can be folded under the 'find' operation
- long cursorId = result.getDocument("cursor").getInt64("id").longValue();
- TracingManager tracingManager = operationContext.getTracingManager();
- tracingManager.addCursorParentContext(cursorId, operationContext.getId());
-
return new CommandBatchCursor<>(getTimeoutMode(), result, batchSize, getMaxTimeForCursor(operationContext), decoder,
comment, source, connection);
};
diff --git a/driver-core/src/main/com/mongodb/internal/session/ServerSessionPool.java b/driver-core/src/main/com/mongodb/internal/session/ServerSessionPool.java
index 00da3adb822..9111eaed3a9 100644
--- a/driver-core/src/main/com/mongodb/internal/session/ServerSessionPool.java
+++ b/driver-core/src/main/com/mongodb/internal/session/ServerSessionPool.java
@@ -29,7 +29,6 @@
import com.mongodb.internal.connection.NoOpSessionContext;
import com.mongodb.internal.connection.OperationContext;
import com.mongodb.internal.selector.ReadPreferenceServerSelector;
-import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.internal.validator.NoOpFieldNameValidator;
import com.mongodb.lang.Nullable;
import com.mongodb.selector.ServerSelector;
@@ -71,7 +70,7 @@ interface Clock {
public ServerSessionPool(final Cluster cluster, final TimeoutSettings timeoutSettings, @Nullable final ServerApi serverApi) {
this(cluster,
new OperationContext(IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE,
- new TimeoutContext(timeoutSettings.connectionOnly()), serverApi, TracingManager.NO_OP));
+ new TimeoutContext(timeoutSettings.connectionOnly()), serverApi));
}
public ServerSessionPool(final Cluster cluster, final OperationContext operationContext) {
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Span.java b/driver-core/src/main/com/mongodb/internal/tracing/Span.java
index 10b95bed5af..41f6ef0afdf 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/Span.java
+++ b/driver-core/src/main/com/mongodb/internal/tracing/Span.java
@@ -16,10 +16,41 @@
package com.mongodb.internal.tracing;
+
+/**
+ * Represents a tracing span for the driver internal operations.
+ *
+ * A span records information about a single operation, such as tags, events, errors, and its context.
+ * Implementations can be used to propagate tracing information and record telemetry.
+ *
+ *
+ * Spans can be used to trace different aspects of MongoDB driver activity:
+ *
+ * - Command Spans: Trace the execution of MongoDB commands (e.g., find, insert, update).
+ * - Operation Spans: Trace higher-level operations, which may include multiple commands or internal steps.
+ * - Transaction Spans: Trace the lifecycle of a transaction, including all operations and commands within it.
+ *
+ *
+ *
+ * @since 5.6
+ */
public interface Span {
+ /**
+ * An empty / no-op implementation of the Span interface.
+ *
+ * This implementation is used as a default when no actual tracing is required.
+ * All methods in this implementation perform no operations and return default values.
+ *
+ */
Span EMPTY = new Span() {
@Override
- public void tag(final String key, final String value) {
+ public Span tag(final String key, final String value) {
+ return this;
+ }
+
+ @Override
+ public Span tag(final String key, final Long value) {
+ return this;
}
@Override
@@ -40,13 +71,47 @@ public TraceContext context() {
}
};
- void tag(String key, String value);
+ /**
+ * Adds a tag to the span with a key-value pair.
+ *
+ * @param key The tag key.
+ * @param value The tag value.
+ * @return The current instance of the span.
+ */
+ Span tag(String key, String value);
+
+ /**
+ * Adds a tag to the span with a key and a numeric value.
+ *
+ * @param key The tag key.
+ * @param value The numeric tag value.
+ * @return The current instance of the span.
+ */
+ Span tag(String key, Long value);
+ /**
+ * Records an event in the span.
+ *
+ * @param event The event description.
+ */
void event(String event);
+ /**
+ * Records an error for this span.
+ *
+ * @param throwable The error to record.
+ */
void error(Throwable throwable);
+ /**
+ * Ends the span, marking it as complete.
+ */
void end();
+ /**
+ * Retrieves the context associated with the span.
+ *
+ * @return The trace context associated with the span.
+ */
TraceContext context();
}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Tags.java b/driver-core/src/main/com/mongodb/internal/tracing/Tags.java
new file mode 100644
index 00000000000..e4407f2a401
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/internal/tracing/Tags.java
@@ -0,0 +1,42 @@
+/*
+ * 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.tracing;
+
+/**
+ * Contains constant tag names used for tracing and monitoring MongoDB operations.
+ * These tags are typically used to annotate spans or events with relevant metadata.
+ *
+ * @since 5.6
+ */
+public final class Tags {
+ private Tags() {
+ }
+
+ public static final String SYSTEM = "db.system";
+ public static final String NAMESPACE = "db.namespace";
+ public static final String COLLECTION = "db.collection.name";
+ public static final String QUERY_SUMMARY = "db.query.summary";
+ public static final String QUERY_TEXT = "db.query.text";
+ public static final String CURSOR_ID = "db.mongodb.cursor_id";
+ public static final String SERVER_ADDRESS = "server.address";
+ public static final String SERVER_PORT = "server.port";
+ public static final String SERVER_TYPE = "server.type";
+ public static final String CLIENT_CONNECTION_ID = "db.mongodb.client_connection_id";
+ public static final String SERVER_CONNECTION_ID = "db.mongodb.server_connection_id";
+ public static final String TRANSACTION_NUMBER = "db.mongodb.txnNumber";
+ public static final String SESSION_ID = "db.mongodb.lsid";
+}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java b/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java
new file mode 100644
index 00000000000..c2881e9e2fb
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java
@@ -0,0 +1,97 @@
+/*
+ * 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.tracing;
+
+import com.mongodb.lang.Nullable;
+
+/**
+ * A Tracer interface that provides methods for tracing commands, operations and transactions.
+ *
+ * This interface defines methods to retrieve the current trace context, create new spans, and check if tracing is enabled.
+ * It also includes a no-operation (NO_OP) implementation for cases where tracing is not required.
+ *
+ *
+ * @since 5.6
+ */
+public interface Tracer {
+ Tracer NO_OP = new Tracer() {
+
+ @Override
+ public TraceContext currentContext() {
+ return TraceContext.EMPTY;
+ }
+
+ @Override
+ public Span nextSpan(final String name) {
+ return Span.EMPTY;
+ }
+
+ @Override
+ public Span nextSpan(final String name, @Nullable final TraceContext parent) {
+ return Span.EMPTY;
+ }
+
+ @Override
+ public boolean enabled() {
+ return false;
+ }
+
+ @Override
+ public boolean includeCommandPayload() {
+ return false;
+ }
+ };
+
+ /**
+ * Retrieves the current trace context from the Micrometer tracer.
+ *
+ * @return A {@link TraceContext} representing the underlying {@link io.micrometer.tracing.TraceContext}.
+ * exists.
+ */
+ TraceContext currentContext();
+
+ /**
+ * Creates a new span with the specified name.
+ *
+ * @param name The name of the span.
+ * @return A {@link Span} representing the newly created span.
+ */
+ Span nextSpan(String name); // uses current active span
+
+ /**
+ * Creates a new span with the specified name and optional parent trace context.
+ *
+ * @param name The name of the span.
+ * @param parent The parent {@link TraceContext}, or null if no parent context is provided.
+ * @return A {@link Span} representing the newly created span.
+ */
+ Span nextSpan(String name, @Nullable TraceContext parent); // manually attach the next span to the provided parent
+
+ /**
+ * Indicates whether tracing is enabled.
+ *
+ * @return {@code true} if tracing is enabled, {@code false} otherwise.
+ */
+ boolean enabled();
+
+ /**
+ * Indicates whether command payloads are included in the trace context.
+ *
+ * @return {@code true} if command payloads are allowed, {@code false} otherwise.
+ */
+ boolean includeCommandPayload(); // whether the tracer allows command payloads in the trace context
+}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
index f6bc5d14d81..6db92f0d75a 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
+++ b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
@@ -16,65 +16,97 @@
package com.mongodb.internal.tracing;
-import com.mongodb.tracing.Tracer;
+import com.mongodb.lang.Nullable;
-import java.util.HashMap;
+import static com.mongodb.internal.tracing.Tags.SYSTEM;
+import static java.lang.System.getenv;
+/**
+ * Manages tracing spans for MongoDB driver activities.
+ *
+ * This class provides methods to create and manage spans for commands, operations and transactions.
+ * It integrates with a {@link Tracer} to propagate tracing information and record telemetry.
+ *
+ */
public class TracingManager {
+ /**
+ * A no-op instance of the TracingManager used when tracing is disabled.
+ */
public static final TracingManager NO_OP = new TracingManager(Tracer.NO_OP);
+ private static final String ENV_ALLOW_COMMAND_PAYLOAD = "MONGODB_TRACING_ALLOW_COMMAND_PAYLOAD";
private final Tracer tracer;
private final TraceContext parentContext;
+ private final boolean enableCommandPayload;
- // Map a cursor id to its parent context (useful for folding getMore commands under the parent operation)
- private final HashMap cursors = new HashMap<>();
-
- // Map an operation's span context so the subsequent commands spans can fold under the parent operation
- private final HashMap operationContexts = new HashMap<>();
-
+ /**
+ * Constructs a new TracingManager with the specified tracer.
+ *
+ * @param tracer The tracer to use for tracing operations.
+ */
public TracingManager(final Tracer tracer) {
- this.tracer = tracer;
- this.parentContext = tracer.currentContext();
+ this(tracer, tracer.currentContext());
}
+ /**
+ * Constructs a new TracingManager with the specified tracer and parent context.
+ * Setting the environment variable {@code MONGODB_TRACING_ALLOW_COMMAND_PAYLOAD} to "true" will enable command payload tracing.
+ *
+ * @param tracer The tracer to use for tracing operations.
+ * @param parentContext The parent trace context.
+ */
public TracingManager(final Tracer tracer, final TraceContext parentContext) {
this.tracer = tracer;
this.parentContext = parentContext;
+ String envAllowCommandPayload = getenv(ENV_ALLOW_COMMAND_PAYLOAD);
+ if (envAllowCommandPayload != null) {
+ this.enableCommandPayload = Boolean.parseBoolean(envAllowCommandPayload);
+ } else {
+ this.enableCommandPayload = tracer.includeCommandPayload();
+ }
}
- public Span addSpan(final String name, final Long operationId) {
- Span span = tracer.nextSpan(name);
- operationContexts.put(operationId, span.context());
- return span;
- }
-
- public Span addSpan(final String name, final TraceContext parentContext) {
+ /**
+ * 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.
+ *
+ *
+ * @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);
}
- public void cleanContexts(final Long operationId) {
- operationContexts.remove(operationId);
- }
-
- public TraceContext getParentContext(final Long operationId) {
- assert operationContexts.containsKey(operationId);
- return operationContexts.get(operationId);
- }
-
- public void addCursorParentContext(final long cursorId, final long operationId) {
- assert operationContexts.containsKey(operationId);
- cursors.put(cursorId, operationContexts.get(operationId));
- }
-
- public TraceContext getCursorParentContext(final long cursorId) {
- return cursors.get(cursorId);
- }
-
- public void removeCursorParentContext(final long cursorId) {
- cursors.remove(cursorId);
+ /**
+ * Creates a new transaction span for the specified server session.
+ *
+ * @return The created transaction span.
+ */
+ public Span addTransactionSpan() {
+ Span span = tracer.nextSpan("transaction", parentContext);
+ span.tag(SYSTEM, "mongodb");
+ return span;
}
+ /**
+ * Checks whether tracing is enabled.
+ *
+ * @return True if tracing is enabled, false otherwise.
+ */
public boolean isEnabled() {
return tracer.enabled();
}
+
+ /**
+ * Checks whether command payload tracing is enabled.
+ *
+ * @return True if command payload tracing is enabled, false otherwise.
+ */
+ public boolean isCommandPayloadEnabled() {
+ return enableCommandPayload;
+ }
}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java b/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java
new file mode 100644
index 00000000000..d975f6931e0
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java
@@ -0,0 +1,110 @@
+/*
+ * 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.tracing;
+
+import com.mongodb.lang.Nullable;
+
+/**
+ * State class for transaction tracing.
+ */
+public class TransactionSpan {
+ private boolean isConvenientTransaction = false;
+ private final Span span;
+ @Nullable
+ private Throwable reportedError;
+
+ public TransactionSpan(final TracingManager tracingManager) {
+ this.span = tracingManager.addTransactionSpan();
+ }
+
+ /**
+ * Handles a transaction span error.
+ *
+ * If the transaction is convenient, the error is reported as an event. This is done since
+ * the error is not fatal and the transaction may be retried.
+ *
+ * If the transaction is not convenient, the error is reported as a span error and the
+ * transaction context is cleaned up.
+ *
+ * @param e The error to report.
+ */
+ public void handleTransactionSpanError(final Throwable e) {
+ if (isConvenientTransaction) {
+ // report error as event (since subsequent retries might succeed, also keep track of the last event
+ span.event(e.toString());
+ reportedError = e;
+ } else {
+ span.error(e);
+ }
+
+ if (!isConvenientTransaction) {
+ span.end();
+ }
+ }
+
+ /**
+ * Finalizes the transaction span by logging the specified status as an event and ending the span.
+ *
+ * @param status The status to log as an event.
+ */
+ public void finalizeTransactionSpan(final String status) {
+ span.event(status);
+ // clear previous commit error if any
+ if (!isConvenientTransaction) {
+ span.end();
+ }
+ reportedError = null; // clear previous commit error if any
+ }
+
+ /**
+ * Finalizes the transaction span by logging any last span event as an error and ending the span.
+ * Optionally cleans up the transaction context if specified.
+ *
+ * @param cleanupTransactionContext A boolean indicating whether to clean up the transaction context.
+ */
+ public void spanFinalizing(final boolean cleanupTransactionContext) {
+ if (reportedError != null) {
+ span.error(reportedError);
+ }
+ 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)
+ if (cleanupTransactionContext) {
+ isConvenientTransaction = false;
+ }
+ }
+
+ /**
+ * Indicates that the transaction is a convenient transaction.
+ *
+ * This has an impact on how the transaction span is handled. If the transaction is convenient, any errors that occur
+ * during the transaction are reported as events. If the transaction is not convenient, errors are reported as span
+ * errors and the transaction context is cleaned up.
+ */
+ public void setIsConvenientTransaction() {
+ this.isConvenientTransaction = true;
+ }
+
+ /**
+ * Retrieves the trace context associated with the transaction span.
+ *
+ * @return The trace context associated with the transaction span.
+ */
+ public TraceContext getContext() {
+ return span.context();
+ }
+}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/package-info.java b/driver-core/src/main/com/mongodb/internal/tracing/package-info.java
index 6b1f711c20b..e7dd3311143 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/package-info.java
+++ b/driver-core/src/main/com/mongodb/internal/tracing/package-info.java
@@ -15,7 +15,7 @@
*/
/**
- * Contains classes related to sessions
+ * Contains classes related to tracing
*/
@NonNullApi
package com.mongodb.internal.tracing;
diff --git a/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java b/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
index b34fa2247b4..ed678b93cab 100644
--- a/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
+++ b/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
@@ -18,12 +18,40 @@
import com.mongodb.internal.tracing.Span;
import com.mongodb.internal.tracing.TraceContext;
-
+import com.mongodb.internal.tracing.Tracer;
+import com.mongodb.lang.Nullable;
+
+/**
+ * A {@link Tracer} implementation that delegates tracing operations to a Micrometer {@link io.micrometer.tracing.Tracer}.
+ *
+ * This class enables integration of MongoDB driver tracing with Micrometer-based tracing systems.
+ * It provides methods to create and manage spans using the Micrometer tracing API.
+ *
+ *
+ * @since 5.6
+ */
public class MicrometerTracer implements Tracer {
private final io.micrometer.tracing.Tracer tracer;
+ private final boolean allowCommandPayload;
+ /**
+ * Constructs a new {@link MicrometerTracer} instance.
+ *
+ * @param tracer The Micrometer {@link io.micrometer.tracing.Tracer} to delegate tracing operations to.
+ */
public MicrometerTracer(final io.micrometer.tracing.Tracer tracer) {
+ this(tracer, false);
+ }
+
+ /**
+ * Constructs a new {@link MicrometerTracer} instance with an option to allow command payloads.
+ *
+ * @param tracer The Micrometer {@link io.micrometer.tracing.Tracer} to delegate tracing operations to.
+ * @param allowCommandPayload Whether to allow command payloads in the trace context.
+ */
+ public MicrometerTracer(final io.micrometer.tracing.Tracer tracer, final boolean allowCommandPayload) {
this.tracer = tracer;
+ this.allowCommandPayload = allowCommandPayload;
}
@Override
@@ -37,16 +65,17 @@ public Span nextSpan(final String name) {
}
@Override
- public Span nextSpan(final String name, final TraceContext parent) {
- if (parent != null) {
- io.micrometer.tracing.Span span = tracer.spanBuilder()
- .name(name)
- .setParent(((MicrometerTraceContext) parent).getTraceContext())
- .start();
- return new MicrometerSpan(span);
- } else {
- return nextSpan(name);
+ public Span nextSpan(final String name, @Nullable final TraceContext parent) {
+ if (parent instanceof MicrometerTraceContext) {
+ io.micrometer.tracing.TraceContext micrometerContext = ((MicrometerTraceContext) parent).getTraceContext();
+ if (micrometerContext != null) {
+ return new MicrometerSpan(tracer.spanBuilder()
+ .name(name)
+ .setParent(micrometerContext)
+ .start());
+ }
}
+ return nextSpan(name);
}
@Override
@@ -54,31 +83,64 @@ public boolean enabled() {
return true;
}
+ @Override
+ public boolean includeCommandPayload() {
+ return allowCommandPayload;
+ }
+
+ /**
+ * Represents a Micrometer-based trace context.
+ */
private static class MicrometerTraceContext implements TraceContext {
private final io.micrometer.tracing.TraceContext traceContext;
- MicrometerTraceContext(final io.micrometer.tracing.TraceContext traceContext) {
+ /**
+ * Constructs a new {@link MicrometerTraceContext} instance.
+ *
+ * @param traceContext The Micrometer {@link io.micrometer.tracing.TraceContext}, or null if none exists.
+ */
+ MicrometerTraceContext(@Nullable final io.micrometer.tracing.TraceContext traceContext) {
this.traceContext = traceContext;
}
+ /**
+ * Retrieves the underlying Micrometer trace context.
+ *
+ * @return The Micrometer {@link io.micrometer.tracing.TraceContext}, or null if none exists.
+ */
+ @Nullable
public io.micrometer.tracing.TraceContext getTraceContext() {
return traceContext;
}
}
+ /**
+ * Represents a Micrometer-based span.
+ */
private static class MicrometerSpan implements Span {
private final io.micrometer.tracing.Span span;
+ /**
+ * Constructs a new {@link MicrometerSpan} instance.
+ *
+ * @param span The Micrometer {@link io.micrometer.tracing.Span} to delegate operations to.
+ */
MicrometerSpan(final io.micrometer.tracing.Span span) {
this.span = span;
}
@Override
- public void tag(final String key, final String value) {
+ public Span tag(final String key, final String value) {
+ span.tag(key, value);
+ return this;
+ }
+
+ @Override
+ public Span tag(final String key, final Long value) {
span.tag(key, value);
+ return this;
}
- // TODO add variant with TimeUnit
@Override
public void event(final String event) {
span.event(event);
diff --git a/driver-core/src/main/com/mongodb/tracing/Tracer.java b/driver-core/src/main/com/mongodb/tracing/Tracer.java
deleted file mode 100644
index 14d7093d4cb..00000000000
--- a/driver-core/src/main/com/mongodb/tracing/Tracer.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.tracing;
-
-import com.mongodb.internal.tracing.Span;
-import com.mongodb.internal.tracing.TraceContext;
-
-public interface Tracer {
- Tracer NO_OP = new Tracer() {
-
- @Override
- public TraceContext currentContext() {
- return TraceContext.EMPTY;
- }
-
- @Override
- public Span nextSpan(final String name) {
- return Span.EMPTY;
- }
-
- @Override
- public Span nextSpan(final String name, final TraceContext parent) {
- return Span.EMPTY;
- }
-
- @Override
- public boolean enabled() {
- return false;
- }
- };
-
- TraceContext currentContext();
-
- Span nextSpan(String name); // uses current active span
-
- Span nextSpan(String name, TraceContext parent); // manually attach the next span to the provided parent
-
- boolean enabled();
-}
diff --git a/driver-core/src/main/com/mongodb/tracing/package-info.java b/driver-core/src/main/com/mongodb/tracing/package-info.java
index 2ec7551d300..43e82603a09 100644
--- a/driver-core/src/main/com/mongodb/tracing/package-info.java
+++ b/driver-core/src/main/com/mongodb/tracing/package-info.java
@@ -15,7 +15,9 @@
*/
/**
- * Contains classes related to sessions
+ * This package defines the API for MongoDB driver tracing.
+ *
+ * @since 5.6
*/
@NonNullApi
package com.mongodb.tracing;
diff --git a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java
index d2916206a7d..30792bf0487 100644
--- a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java
+++ b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java
@@ -71,7 +71,6 @@
import com.mongodb.internal.operation.DropDatabaseOperation;
import com.mongodb.internal.operation.ReadOperation;
import com.mongodb.internal.operation.WriteOperation;
-import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java
index f40c7d17f20..447a1efb5dd 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java
@@ -26,7 +26,6 @@
import com.mongodb.internal.IgnorableRequestContext;
import com.mongodb.internal.TimeoutContext;
import com.mongodb.internal.TimeoutSettings;
-import com.mongodb.internal.tracing.TracingManager;
import org.bson.BsonDocument;
import org.bson.codecs.Decoder;
import org.junit.jupiter.api.Test;
@@ -121,6 +120,6 @@ void testIsCommandOk() {
OperationContext createOperationContext() {
return new OperationContext(IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE,
- new TimeoutContext(TimeoutSettings.DEFAULT), ServerApi.builder().version(ServerApiVersion.V1).build(), TracingManager.NO_OP);
+ new TimeoutContext(TimeoutSettings.DEFAULT), ServerApi.builder().version(ServerApiVersion.V1).build());
}
}
diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageTest.java
index 3ceb7567eef..533e74f0d23 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandMessageTest.java
@@ -32,7 +32,6 @@
import com.mongodb.internal.operation.ClientBulkWriteOperation;
import com.mongodb.internal.operation.ClientBulkWriteOperation.ClientBulkWriteCommand.OpsAndNsInfo;
import com.mongodb.internal.session.SessionContext;
-import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.internal.validator.NoOpFieldNameValidator;
import org.bson.BsonArray;
import org.bson.BsonBoolean;
@@ -164,7 +163,7 @@ void getCommandDocumentFromClientBulkWrite() {
output,
new OperationContext(
IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE,
- new TimeoutContext(TimeoutSettings.DEFAULT), null, TracingManager.NO_OP));
+ new TimeoutContext(TimeoutSettings.DEFAULT), null));
BsonDocument actualCommandDocument = commandMessage.getCommandDocument(output);
assertEquals(expectedCommandDocument, actualCommandDocument);
}
diff --git a/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncClientSession.kt b/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncClientSession.kt
index 83ba91df16b..3c66babd5d8 100644
--- a/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncClientSession.kt
+++ b/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncClientSession.kt
@@ -21,6 +21,7 @@ import com.mongodb.TransactionOptions
import com.mongodb.client.ClientSession as JClientSession
import com.mongodb.client.TransactionBody
import com.mongodb.internal.TimeoutContext
+import com.mongodb.internal.tracing.TransactionSpan
import com.mongodb.kotlin.client.coroutine.ClientSession
import com.mongodb.session.ServerSession
import kotlinx.coroutines.runBlocking
@@ -89,4 +90,6 @@ class SyncClientSession(internal val wrapped: ClientSession, private val origina
throw UnsupportedOperationException()
override fun getTimeoutContext(): TimeoutContext? = wrapped.getTimeoutContext()
+
+ override fun getTransactionSpan(): TransactionSpan? = null
}
diff --git a/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncClientSession.kt b/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncClientSession.kt
index 64cd27b776f..001198dbcd0 100644
--- a/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncClientSession.kt
+++ b/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncClientSession.kt
@@ -21,6 +21,7 @@ import com.mongodb.TransactionOptions
import com.mongodb.client.ClientSession as JClientSession
import com.mongodb.client.TransactionBody
import com.mongodb.internal.TimeoutContext
+import com.mongodb.internal.tracing.TransactionSpan
import com.mongodb.kotlin.client.ClientSession
import com.mongodb.session.ServerSession
import org.bson.BsonDocument
@@ -93,4 +94,6 @@ internal class SyncClientSession(internal val wrapped: ClientSession, private va
throw UnsupportedOperationException()
override fun getTimeoutContext(): TimeoutContext = throw UnsupportedOperationException()
+
+ override fun getTransactionSpan(): TransactionSpan? = null
}
diff --git a/driver-legacy/src/main/com/mongodb/MongoClient.java b/driver-legacy/src/main/com/mongodb/MongoClient.java
index 9478dc76177..09d58e1b493 100644
--- a/driver-legacy/src/main/com/mongodb/MongoClient.java
+++ b/driver-legacy/src/main/com/mongodb/MongoClient.java
@@ -43,7 +43,6 @@
import com.mongodb.internal.diagnostics.logging.Loggers;
import com.mongodb.internal.session.ServerSessionPool;
import com.mongodb.internal.thread.DaemonThreadFactory;
-import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.internal.validator.NoOpFieldNameValidator;
import com.mongodb.lang.Nullable;
import org.bson.BsonArray;
diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java
index 1689c9b31e9..250949549f2 100644
--- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java
+++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java
@@ -205,7 +205,7 @@ private OperationContext getOperationContext(final RequestContext requestContext
requestContext,
new ReadConcernAwareNoOpSessionContext(readConcern),
createTimeoutContext(session, timeoutSettings),
- mongoClient.getSettings().getTracingManager(),
+ TracingManager.NO_OP,
mongoClient.getSettings().getServerApi(),
commandName);
}
diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncClientSession.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncClientSession.java
index 494e5f8c74e..ab234ad6f3e 100644
--- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncClientSession.java
+++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncClientSession.java
@@ -22,6 +22,7 @@
import com.mongodb.client.ClientSession;
import com.mongodb.client.TransactionBody;
import com.mongodb.internal.TimeoutContext;
+import com.mongodb.internal.tracing.TransactionSpan;
import com.mongodb.lang.Nullable;
import com.mongodb.session.ServerSession;
import org.bson.BsonDocument;
@@ -188,6 +189,12 @@ public TimeoutContext getTimeoutContext() {
return wrapped.getTimeoutContext();
}
+ @Override
+ @Nullable
+ public TransactionSpan getTransactionSpan() {
+ return null;
+ }
+
private static void sleep(final long millis) {
try {
Thread.sleep(millis);
diff --git a/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncClientSession.scala b/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncClientSession.scala
index 2866ce7427d..4c0cb19217d 100644
--- a/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncClientSession.scala
+++ b/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncClientSession.scala
@@ -19,6 +19,7 @@ package org.mongodb.scala.syncadapter
import com.mongodb.{ ClientSessionOptions, MongoInterruptedException, ServerAddress, TransactionOptions }
import com.mongodb.client.{ ClientSession => JClientSession, TransactionBody }
import com.mongodb.internal.TimeoutContext
+import com.mongodb.internal.tracing.TransactionSpan
import com.mongodb.session.ServerSession
import org.bson.{ BsonDocument, BsonTimestamp }
import org.mongodb.scala._
@@ -96,4 +97,6 @@ case class SyncClientSession(wrapped: ClientSession, originator: Object) extends
}
override def getTimeoutContext: TimeoutContext = wrapped.getTimeoutContext
+
+ override def getTransactionSpan: TransactionSpan = null
}
diff --git a/driver-sync/build.gradle.kts b/driver-sync/build.gradle.kts
index 95cd0979973..b37d0226295 100644
--- a/driver-sync/build.gradle.kts
+++ b/driver-sync/build.gradle.kts
@@ -38,6 +38,9 @@ dependencies {
// lambda testing
testImplementation(libs.aws.lambda.core)
+
+ // Tracing
+ testImplementation(libs.bundles.micrometer.test)
}
configureMavenPublication {
diff --git a/driver-sync/src/main/com/mongodb/client/ClientSession.java b/driver-sync/src/main/com/mongodb/client/ClientSession.java
index 5d994b863e8..6af2b4be664 100644
--- a/driver-sync/src/main/com/mongodb/client/ClientSession.java
+++ b/driver-sync/src/main/com/mongodb/client/ClientSession.java
@@ -18,6 +18,7 @@
import com.mongodb.ServerAddress;
import com.mongodb.TransactionOptions;
+import com.mongodb.internal.tracing.TransactionSpan;
import com.mongodb.lang.Nullable;
/**
@@ -125,4 +126,13 @@ public interface ClientSession extends com.mongodb.session.ClientSession {
* @since 3.11
*/
T withTransaction(TransactionBody transactionBody, TransactionOptions options);
+
+ /**
+ * Get the transaction span (if started).
+ *
+ * @return the transaction span
+ * @since 5.6
+ */
+ @Nullable
+ TransactionSpan getTransactionSpan();
}
diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java
index b60fc90316a..c1937843a9d 100644
--- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java
+++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java
@@ -36,6 +36,8 @@
import com.mongodb.internal.operation.WriteOperation;
import com.mongodb.internal.session.BaseClientSessionImpl;
import com.mongodb.internal.session.ServerSessionPool;
+import com.mongodb.internal.tracing.TracingManager;
+import com.mongodb.internal.tracing.TransactionSpan;
import com.mongodb.lang.Nullable;
import static com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL;
@@ -54,11 +56,14 @@ final class ClientSessionImpl extends BaseClientSessionImpl implements ClientSes
private boolean messageSentInCurrentTransaction;
private boolean commitInProgress;
private TransactionOptions transactionOptions;
+ private final TracingManager tracingManager;
+ private TransactionSpan transactionSpan = null;
ClientSessionImpl(final ServerSessionPool serverSessionPool, final Object originator, final ClientSessionOptions options,
- final OperationExecutor operationExecutor) {
+ final OperationExecutor operationExecutor, final TracingManager tracingManager) {
super(serverSessionPool, originator, options);
this.operationExecutor = operationExecutor;
+ this.tracingManager = tracingManager;
}
@Override
@@ -141,6 +146,9 @@ public void abortTransaction() {
} finally {
clearTransactionContext();
cleanupTransaction(TransactionState.ABORTED);
+ if (transactionSpan != null) {
+ transactionSpan.finalizeTransactionSpan(TransactionState.ABORTED.name());
+ }
}
}
@@ -167,6 +175,10 @@ private void startTransaction(final TransactionOptions transactionOptions, final
if (!writeConcern.isAcknowledged()) {
throw new MongoClientException("Transactions do not support unacknowledged write concern");
}
+
+ if (tracingManager.isEnabled()) {
+ transactionSpan = new TransactionSpan(tracingManager);
+ }
clearTransactionContext();
setTimeoutContext(timeoutContext);
}
@@ -187,7 +199,7 @@ private void commitTransaction(final boolean resetTimeout) {
if (transactionState == TransactionState.NONE) {
throw new IllegalStateException("There is no transaction started");
}
-
+ boolean exceptionThrown = false;
try {
if (messageSentInCurrentTransaction) {
ReadConcern readConcern = transactionOptions.getReadConcern();
@@ -206,11 +218,20 @@ private void commitTransaction(final boolean resetTimeout) {
.recoveryToken(getRecoveryToken()), readConcern, this);
}
} catch (MongoException e) {
+ exceptionThrown = true;
clearTransactionContextOnError(e);
+ if (transactionSpan != null) {
+ transactionSpan.handleTransactionSpanError(e);
+ }
throw e;
} finally {
transactionState = TransactionState.COMMITTED;
commitInProgress = false;
+ if (!exceptionThrown) {
+ if (transactionSpan != null) {
+ transactionSpan.finalizeTransactionSpan(TransactionState.COMMITTED.name());
+ }
+ }
}
}
@@ -231,51 +252,72 @@ public T withTransaction(final TransactionBody transactionBody, final Tra
long startTime = ClientSessionClock.INSTANCE.now();
TimeoutContext withTransactionTimeoutContext = createTimeoutContext(options);
- outer:
- while (true) {
- T retVal;
- try {
- startTransaction(options, withTransactionTimeoutContext.copyTimeoutContext());
- retVal = transactionBody.execute();
- } catch (Throwable e) {
- if (transactionState == TransactionState.IN) {
- abortTransaction();
- }
- if (e instanceof MongoException && !(e instanceof MongoOperationTimeoutException)) {
- MongoException exceptionToHandle = OperationHelper.unwrap((MongoException) e);
- if (exceptionToHandle.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL)
- && ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) {
- continue;
+ try {
+ outer:
+ while (true) {
+ T retVal;
+ try {
+ startTransaction(options, withTransactionTimeoutContext.copyTimeoutContext());
+ if (transactionSpan != null) {
+ transactionSpan.setIsConvenientTransaction();
}
- }
- throw e;
- }
- if (transactionState == TransactionState.IN) {
- while (true) {
- try {
- commitTransaction(false);
- break;
- } catch (MongoException e) {
- clearTransactionContextOnError(e);
- if (!(e instanceof MongoOperationTimeoutException)
+ retVal = transactionBody.execute();
+ } catch (Throwable e) {
+ if (transactionState == TransactionState.IN) {
+ abortTransaction();
+ }
+ if (e instanceof MongoException && !(e instanceof MongoOperationTimeoutException)) {
+ MongoException exceptionToHandle = OperationHelper.unwrap((MongoException) e);
+ if (exceptionToHandle.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL)
&& ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) {
- applyMajorityWriteConcernToTransactionOptions();
+ if (transactionSpan != null) {
+ transactionSpan.spanFinalizing(false);
+ }
+ continue;
+ }
+ }
+ throw e;
+ }
+ if (transactionState == TransactionState.IN) {
+ while (true) {
+ try {
+ commitTransaction(false);
+ break;
+ } catch (MongoException e) {
+ clearTransactionContextOnError(e);
+ if (!(e instanceof MongoOperationTimeoutException)
+ && ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) {
+ applyMajorityWriteConcernToTransactionOptions();
- if (!(e instanceof MongoExecutionTimeoutException)
- && e.hasErrorLabel(UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) {
- continue;
- } else if (e.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL)) {
- continue outer;
+ if (!(e instanceof MongoExecutionTimeoutException)
+ && e.hasErrorLabel(UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) {
+ continue;
+ } else if (e.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL)) {
+ if (transactionSpan != null) {
+ transactionSpan.spanFinalizing(true);
+ }
+ continue outer;
+ }
}
+ throw e;
}
- throw e;
}
}
+ return retVal;
+ }
+ } finally {
+ if (transactionSpan != null) {
+ transactionSpan.spanFinalizing(true);
}
- return retVal;
}
}
+ @Override
+ @Nullable
+ public TransactionSpan getTransactionSpan() {
+ return transactionSpan;
+ }
+
@Override
public void close() {
try {
diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java
index acb19fe12d9..b227539557f 100644
--- a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java
+++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java
@@ -48,6 +48,7 @@
import com.mongodb.internal.diagnostics.logging.Logger;
import com.mongodb.internal.diagnostics.logging.Loggers;
import com.mongodb.internal.session.ServerSessionPool;
+import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import org.bson.BsonDocument;
import org.bson.Document;
@@ -107,7 +108,7 @@ public MongoClientImpl(final Cluster cluster,
settings.getRetryWrites(), settings.getServerApi(),
new ServerSessionPool(cluster, TimeoutSettings.create(settings), settings.getServerApi()),
TimeoutSettings.create(settings), settings.getUuidRepresentation(),
- settings.getWriteConcern(), settings.getTracingManager());
+ settings.getWriteConcern(), new TracingManager(settings.getTracer()));
this.closed = new AtomicBoolean();
BsonDocument clientMetadataDocument = delegate.getCluster().getClientMetadata().getBsonDocument();
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 5944edae4e2..f1ea046b591 100644
--- a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java
+++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java
@@ -43,6 +43,7 @@
import com.mongodb.client.model.bulk.ClientBulkWriteResult;
import com.mongodb.internal.IgnorableRequestContext;
import com.mongodb.internal.TimeoutSettings;
+import com.mongodb.internal.binding.BindingContext;
import com.mongodb.internal.binding.ClusterAwareReadWriteBinding;
import com.mongodb.internal.binding.ClusterBinding;
import com.mongodb.internal.binding.ReadBinding;
@@ -57,7 +58,11 @@
import com.mongodb.internal.operation.SyncOperations;
import com.mongodb.internal.operation.WriteOperation;
import com.mongodb.internal.session.ServerSessionPool;
+import com.mongodb.internal.tracing.Span;
+import com.mongodb.internal.tracing.Tags;
+import com.mongodb.internal.tracing.TraceContext;
import com.mongodb.internal.tracing.TracingManager;
+import com.mongodb.internal.tracing.TransactionSpan;
import com.mongodb.lang.Nullable;
import org.bson.BsonDocument;
import org.bson.Document;
@@ -253,7 +258,7 @@ public ClientSession startSession(final ClientSessionOptions options) {
.readPreference(readPreference)
.build()))
.build();
- return new ClientSessionImpl(serverSessionPool, originator, mergedOptions, operationExecutor);
+ return new ClientSessionImpl(serverSessionPool, originator, mergedOptions, operationExecutor, tracingManager);
}
@Override
@@ -423,6 +428,8 @@ public T execute(final ReadOperation operation, final ReadPreference read
ReadBinding binding = getReadBinding(readPreference, readConcern, actualClientSession, session == null,
operation.getCommandName());
+ Span span = createOperationSpan(actualClientSession, binding, operation.getCommandName());
+
try {
if (actualClientSession.hasActiveTransaction() && !binding.getReadPreference().equals(primary())) {
throw new MongoClientException("Read preference in a transaction must be primary");
@@ -432,9 +439,15 @@ public T execute(final ReadOperation operation, final ReadPreference read
MongoException exceptionToHandle = OperationHelper.unwrap(e);
labelException(actualClientSession, exceptionToHandle);
clearTransactionContextOnTransientTransactionError(session, exceptionToHandle);
+ if (span != null) {
+ span.error(e);
+ }
throw e;
} finally {
binding.release();
+ if (span != null) {
+ span.end();
+ }
}
}
@@ -448,15 +461,22 @@ public T execute(final WriteOperation operation, final ReadConcern readCo
ClientSession actualClientSession = getClientSession(session);
WriteBinding binding = getWriteBinding(readConcern, actualClientSession, session == null, operation.getCommandName());
+ Span span = createOperationSpan(actualClientSession, binding, operation.getCommandName());
try {
return operation.execute(binding);
} catch (MongoException e) {
MongoException exceptionToHandle = OperationHelper.unwrap(e);
labelException(actualClientSession, exceptionToHandle);
clearTransactionContextOnTransientTransactionError(session, exceptionToHandle);
+ if (span != null) {
+ span.error(e);
+ }
throw e;
} finally {
binding.release();
+ if (span != null) {
+ span.end();
+ }
}
}
@@ -503,6 +523,7 @@ private OperationContext getOperationContext(final ClientSession session, final
getRequestContext(),
new ReadConcernAwareNoOpSessionContext(readConcern),
createTimeoutContext(session, executorTimeoutSettings),
+ tracingManager,
serverApi,
commandName);
}
@@ -560,5 +581,35 @@ ClientSession getClientSession(@Nullable final ClientSession clientSessionFromOp
}
return session;
}
+
+ /**
+ * Create a tracing span for the given operation, and set it on operation context.
+ *
+ * @param actualClientSession the session that the operation is part of
+ * @param binding the binding for the operation
+ * @param commandName the name of the command
+ */
+ @Nullable
+ private Span createOperationSpan(ClientSession actualClientSession, BindingContext binding, String commandName) {
+ TracingManager tracingManager = binding.getOperationContext().getTracingManager();
+ if (tracingManager.isEnabled()) {
+ TraceContext parentContext = null;
+ TransactionSpan transactionSpan = actualClientSession.getTransactionSpan();
+ if (transactionSpan != null) {
+ parentContext = transactionSpan.getContext();
+ }
+
+ Span span = binding
+ .getOperationContext()
+ .getTracingManager()
+ .addSpan(commandName, parentContext);
+ binding.getOperationContext().setTracingContext(span.context());
+ span.tag(Tags.SYSTEM, "mongodb");
+ return span;
+
+ } else {
+ return null;
+ }
+ }
}
}
diff --git a/driver-sync/src/test/functional/com/mongodb/client/tracing/SpanTree.java b/driver-sync/src/test/functional/com/mongodb/client/tracing/SpanTree.java
new file mode 100644
index 00000000000..bca04e6647a
--- /dev/null
+++ b/driver-sync/src/test/functional/com/mongodb/client/tracing/SpanTree.java
@@ -0,0 +1,295 @@
+/*
+ * 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.client.tracing;
+
+import com.mongodb.internal.tracing.Tags;
+import com.mongodb.lang.Nullable;
+import io.micrometer.tracing.test.simple.SimpleSpan;
+import org.bson.BsonArray;
+import org.bson.BsonBinary;
+import org.bson.BsonDocument;
+import org.bson.BsonString;
+import org.bson.BsonValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.function.BiConsumer;
+
+import static org.bson.assertions.Assertions.notNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Represents a tree structure of spans, where each span can have nested spans as children.
+ * This class provides methods to create a span tree from various sources and to validate the spans against expected values.
+ */
+public class SpanTree {
+ private final List roots = new ArrayList<>();
+
+ /**
+ * Creates a SpanTree from a BsonArray of spans.
+ *
+ * @param spans the BsonArray containing span documents
+ * @return a SpanTree constructed from the provided spans
+ */
+ public static SpanTree from(final BsonArray spans) {
+ SpanTree spanTree = new SpanTree();
+ for (final BsonValue span : spans) {
+ if (span.isDocument()) {
+ final BsonDocument spanDoc = span.asDocument();
+ final String name = spanDoc.getString("name").getValue();
+ final SpanNode rootNode = new SpanNode(name);
+ spanTree.roots.add(rootNode);
+
+ if (spanDoc.containsKey("tags")) {
+ rootNode.tags = spanDoc.getDocument("tags");
+ }
+
+ if (spanDoc.containsKey("nested")) {
+ for (final BsonValue nestedSpan : spanDoc.getArray("nested")) {
+ addNestedSpans(rootNode, nestedSpan.asDocument());
+ }
+ }
+ }
+ }
+
+ return spanTree;
+ }
+
+ /**
+ * Creates a SpanTree from a JSON string representation of spans.
+ *
+ * @param spansAsJson the JSON string containing span documents
+ * @return a SpanTree constructed from the provided JSON spans
+ */
+ public static SpanTree from(final String spansAsJson) {
+ BsonArray spans = BsonArray.parse(spansAsJson);
+ return from(spans);
+ }
+
+ /**
+ * Creates a SpanTree from a Deque of SimpleSpan objects.
+ * This method is typically used to build a tree based on the actual collected tracing spans.
+ *
+ * @param spans the Deque containing SimpleSpan objects
+ * @return a SpanTree constructed from the provided spans
+ */
+ public static SpanTree from(final Deque spans) {
+ final SpanTree spanTree = new SpanTree();
+ final Map idToSpanNode = new HashMap<>();
+ for (final SimpleSpan span : spans) {
+ final SpanNode spanNode = new SpanNode(span.getName());
+ for (final Map.Entry tag : span.getTags().entrySet()) {
+ // handle special case of session id (needs to be parsed into a BsonBinary)
+ // this is needed because the SimpleTracer reports all the collected tags as strings
+ if (tag.getKey().equals(Tags.SESSION_ID)) {
+ spanNode.tags.append(tag.getKey(), new BsonDocument().append("id", new BsonBinary(UUID.fromString(tag.getValue()))));
+ } else {
+ spanNode.tags.append(tag.getKey(), new BsonString(tag.getValue()));
+ }
+ }
+ idToSpanNode.put(span.context().spanId(), spanNode);
+ }
+
+ for (final SimpleSpan span : spans) {
+ final String parentId = span.context().parentId();
+ final SpanNode node = idToSpanNode.get(span.context().spanId());
+
+ if (!parentId.isEmpty() && idToSpanNode.containsKey(parentId)) {
+ idToSpanNode.get(parentId).children.add(node);
+ } else { // doesn't have a parent, so it is a root node
+ spanTree.roots.add(node);
+ }
+ }
+ return spanTree;
+ }
+
+ /**
+ * Adds nested spans to the parent node based on the provided BsonDocument.
+ * This method recursively adds child spans to the parent span node.
+ *
+ * @param parentNode the parent span node to which nested spans will be added
+ * @param nestedSpan the BsonDocument representing a nested span
+ */
+ private static void addNestedSpans(final SpanNode parentNode, final BsonDocument nestedSpan) {
+ final String name = nestedSpan.getString("name").getValue();
+ final SpanNode childNode = new SpanNode(name, parentNode);
+
+ if (nestedSpan.containsKey("tags")) {
+ childNode.tags = nestedSpan.getDocument("tags");
+ }
+
+ if (nestedSpan.containsKey("nested")) {
+ for (final BsonValue nested : nestedSpan.getArray("nested")) {
+ addNestedSpans(childNode, nested.asDocument());
+ }
+ }
+ }
+
+ /**
+ * Asserts that the reported spans are valid against the expected spans.
+ * This method checks that the reported spans match the expected spans in terms of names, tags, and structure.
+ *
+ * @param reportedSpans the SpanTree containing the reported spans
+ * @param expectedSpans the SpanTree containing the expected spans
+ * @param valueMatcher a BiConsumer to match values of tags between reported and expected spans
+ * @param ignoreExtraSpans if true, allows reported spans to contain extra spans not present in expected spans
+ */
+ public static void assertValid(final SpanTree reportedSpans, final SpanTree expectedSpans,
+ final BiConsumer valueMatcher,
+ final boolean ignoreExtraSpans) {
+ if (ignoreExtraSpans) {
+ // remove from the reported spans all the nodes that are not expected
+ reportedSpans.roots.removeIf(node -> !expectedSpans.roots.contains(node));
+
+ }
+
+ // check that we have the same root spans
+ if (reportedSpans.roots.size() != expectedSpans.roots.size()) {
+ fail("The number of reported spans does not match expected spans size. "
+ + "Reported: " + reportedSpans.roots.size()
+ + ", Expected: " + expectedSpans.roots.size()
+ + " ignoreExtraSpans: " + ignoreExtraSpans);
+ }
+
+ for (int i = 0; i < reportedSpans.roots.size(); i++) {
+ assertValid(reportedSpans.roots.get(i), expectedSpans.roots.get(i), valueMatcher);
+ }
+ }
+
+ /**
+ * Asserts that a reported span node is valid against an expected span node.
+ * This method checks that the reported span's name, tags, and children match the expected span.
+ *
+ * @param reportedNode the reported span node to validate
+ * @param expectedNode the expected span node to validate against
+ * @param valueMatcher a BiConsumer to match values of tags between reported and expected spans
+ */
+ private static void assertValid(final SpanNode reportedNode, final SpanNode expectedNode,
+ final BiConsumer valueMatcher) {
+ // Check that the span names match
+ if (!reportedNode.getName().equalsIgnoreCase(expectedNode.getName())) {
+ fail("Reported span name "
+ + reportedNode.getName()
+ + " does not match expected span name "
+ + expectedNode.getName());
+ }
+
+ valueMatcher.accept(expectedNode.tags, reportedNode.tags);
+
+ // Spans should have the same number of children
+ if (reportedNode.children.size() != expectedNode.children.size()) {
+ fail("Reported span " + reportedNode.getName()
+ + " has " + reportedNode.children.size()
+ + " children, but expected " + expectedNode.children.size());
+ }
+
+ // For every reported child span make sure it is valid against the expected child span
+ for (int i = 0; i < reportedNode.children.size(); i++) {
+ assertValid(reportedNode.children.get(i), expectedNode.children.get(i), valueMatcher);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SpanTree{"
+ + "roots=" + roots
+ + '}';
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final SpanTree spanTree = (SpanTree) o;
+ return Objects.deepEquals(roots, spanTree.roots);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(roots);
+ }
+
+ /**
+ * Represents a node in the span tree, which can have nested child spans.
+ * Each span node contains a name, tags, and a list of child span nodes.
+ */
+ public static class SpanNode {
+ private final String name;
+ private BsonDocument tags = new BsonDocument();
+ private final List children = new ArrayList<>();
+
+ public SpanNode(final String name) {
+ this.name = notNull("name", name);
+ }
+
+ public SpanNode(final String name, @Nullable final SpanNode parent) {
+ this.name = notNull("name", name);
+ if (parent != null) {
+ parent.children.add(this);
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List getChildren() {
+ return Collections.unmodifiableList(children);
+ }
+
+ @Override
+ public String toString() {
+ return "SpanNode{"
+ + "name='" + name + '\''
+ + ", tags=" + tags
+ + ", children=" + children
+ + '}';
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final SpanNode spanNode = (SpanNode) o;
+ return name.equalsIgnoreCase(spanNode.name)
+ && Objects.equals(tags, spanNode.tags)
+ && Objects.equals(children, spanNode.children);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name.hashCode();
+ result = 31 * result + tags.hashCode();
+ result = 31 * result + children.hashCode();
+ return result;
+ }
+ }
+}
diff --git a/driver-sync/src/test/functional/com/mongodb/client/tracing/ZipkinTracer.java b/driver-sync/src/test/functional/com/mongodb/client/tracing/ZipkinTracer.java
new file mode 100644
index 00000000000..305e916d213
--- /dev/null
+++ b/driver-sync/src/test/functional/com/mongodb/client/tracing/ZipkinTracer.java
@@ -0,0 +1,95 @@
+/*
+ * 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.client.tracing;
+
+import io.micrometer.tracing.Tracer;
+import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext;
+import io.micrometer.tracing.otel.bridge.OtelTracer;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.context.propagation.ContextPropagators;
+import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
+import io.opentelemetry.extension.trace.propagation.B3Propagator;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.resources.Resource;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.SpanProcessor;
+import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
+import io.opentelemetry.semconv.ResourceAttributes;
+
+/**
+ * A utility class to create a Zipkin tracer using OpenTelemetry protocol, useful for visualizing spans in Zipkin UI
+ * This tracer can be used to send spans to a Zipkin server.
+ *
+ * Spans are visible in the Zipkin UI at ....
+ *
+ * To Start Zipkin server, you can use the following command:
+ *
{@code
+ * docker run -d -p 9411:9411 openzipkin/zipkin
+ * }
+ */
+public final class ZipkinTracer {
+ private static final String ENDPOINT = "http://localhost:9411/api/v2/spans";
+
+ private ZipkinTracer() {
+ }
+
+ /**
+ * Creates a Zipkin tracer with the specified service name.
+ *
+ * @param serviceName the name of the service to be used in the tracer
+ * @return a Tracer instance configured to send spans to Zipkin
+ */
+ public static Tracer getTracer(final String serviceName) {
+ ZipkinSpanExporter zipkinExporter = ZipkinSpanExporter.builder()
+ .setEndpoint(ENDPOINT)
+ .build();
+
+ Resource resource = Resource.getDefault()
+ .merge(Resource.create(
+ Attributes.of(
+ ResourceAttributes.SERVICE_NAME, serviceName,
+ ResourceAttributes.SERVICE_VERSION, "1.0.0"
+ )
+ ));
+
+ SpanProcessor spanProcessor = SimpleSpanProcessor.create(zipkinExporter);
+
+ SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
+ .addSpanProcessor(spanProcessor)
+ .setResource(resource)
+ .build();
+
+ OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
+ .setTracerProvider(tracerProvider)
+ .setPropagators(ContextPropagators.create(
+ B3Propagator.injectingSingleHeader()
+ ))
+ .build();
+
+ io.opentelemetry.api.trace.Tracer otelTracer = openTelemetry.getTracer("my-java-service", "1.0.0");
+
+ OtelCurrentTraceContext otelCurrentTraceContext = new OtelCurrentTraceContext();
+
+ return new OtelTracer(
+ otelTracer,
+ otelCurrentTraceContext,
+ null // EventPublisher can be null for basic usage
+ );
+ }
+
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 90aec4c1c38..38805b02353 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -25,6 +25,8 @@ snappy = "1.1.10.3"
zstd = "1.5.5-3"
jetbrains-annotations = "26.0.2"
micrometer = "1.4.5"
+zipkin-reporter = "2.16.3"
+opentelemetry-exporter-zipkin = "1.30.0"
kotlin = "1.8.10"
kotlinx-coroutines-bom = "1.6.4"
@@ -174,6 +176,12 @@ project-reactor-test = { module = "io.projectreactor:reactor-test" }
reactive-streams-tck = { module = " org.reactivestreams:reactive-streams-tck", version.ref = "reactive-streams" }
reflections = { module = "org.reflections:reflections", version.ref = "reflections" }
+micrometer-tracing-test = { module = " io.micrometer:micrometer-tracing-test", version.ref = "micrometer" }
+micrometer-tracing-bridge-brave = { module = " io.micrometer:micrometer-tracing-bridge-brave", version.ref = "micrometer" }
+micrometer-tracing = { module = " io.micrometer:micrometer-tracing", version.ref = "micrometer" }
+micrometer-tracing-bridge-otel = { module = " io.micrometer:micrometer-tracing-bridge-otel", version.ref = "micrometer" }
+zipkin-reporter = { module = " io.zipkin.reporter2:zipkin-reporter", version.ref = "zipkin-reporter" }
+opentelemetry-exporter-zipkin = { module = " io.opentelemetry:opentelemetry-exporter-zipkin", version.ref = "opentelemetry-exporter-zipkin" }
[bundles]
aws-java-sdk-v1 = ["aws-java-sdk-v1-core", "aws-java-sdk-v1-sts"]
@@ -200,6 +208,9 @@ scala-test-v2-v12 = ["scala-test-flatspec-v2-v12", "scala-test-shouldmatchers-v2
scala-test-v2-v11 = ["scala-test-flatspec-v2-v11", "scala-test-shouldmatchers-v2-v11", "scala-test-mockito-v2-v11",
"scala-test-junit-runner-v2-v11", "reflections"]
+micrometer-test = ["micrometer-tracing-test", "micrometer-tracing-bridge-brave", "micrometer-tracing-bridge-otel",
+ "micrometer-tracing", "zipkin-reporter", "opentelemetry-exporter-zipkin"]
+
[plugins]
kotlin-gradle = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
bnd = { id = "biz.aQute.bnd.builder", version.ref = "plugin-bnd" }
From 33d46e8ca5f87a7f5ad96318930ab3bfe2aa8e48 Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Mon, 4 Aug 2025 13:00:02 +0100
Subject: [PATCH 03/14] Merging back missing changes after rebase
---
.../AsyncCommandBatchCursorTest.java | 2 ++
.../MongoClientSettingsSpecification.groovy | 4 +--
...hangeStreamBatchCursorSpecification.groovy | 4 +++
...syncCommandBatchCursorSpecification.groovy | 2 ++
.../ChangeStreamBatchCursorTest.java | 2 ++
.../CommandBatchCursorSpecification.groovy | 2 ++
.../operation/CommandBatchCursorTest.java | 3 +++
driver-kotlin-coroutine/build.gradle.kts | 1 +
driver-kotlin-sync/build.gradle.kts | 1 +
.../scala/ApiAliasAndCompanionSpec.scala | 3 ++-
.../mongodb/client/tracing/ZipkinTracer.java | 2 +-
.../com/mongodb/client/unified/Entities.java | 26 +++++++++++++++++-
.../client/unified/MicrometerTracingTest.java | 27 +++++++++++++++++++
.../internal/MongoClusterSpecification.groovy | 4 ++-
14 files changed, 77 insertions(+), 6 deletions(-)
create mode 100644 driver-sync/src/test/functional/com/mongodb/client/unified/MicrometerTracingTest.java
diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorTest.java
index e9a30686d5f..3708081fc26 100644
--- a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorTest.java
+++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorTest.java
@@ -32,6 +32,7 @@
import com.mongodb.internal.binding.AsyncConnectionSource;
import com.mongodb.internal.connection.AsyncConnection;
import com.mongodb.internal.connection.OperationContext;
+import com.mongodb.internal.tracing.TracingManager;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonInt32;
@@ -96,6 +97,7 @@ void setUp() {
connectionSource = mock(AsyncConnectionSource.class);
operationContext = mock(OperationContext.class);
+ when(operationContext.getTracingManager()).thenReturn(TracingManager.NO_OP);
timeoutContext = new TimeoutContext(TimeoutSettings.create(
MongoClientSettings.builder().timeout(TIMEOUT.toMillis(), MILLISECONDS).build()));
serverDescription = mock(ServerDescription.class);
diff --git a/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy b/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy
index ec5d92b1e49..02b33aa8d27 100644
--- a/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy
+++ b/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy
@@ -555,7 +555,7 @@ class MongoClientSettingsSpecification extends Specification {
'heartbeatConnectTimeoutMS', 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'loggerSettingsBuilder',
'readConcern', 'readPreference', 'retryReads',
'retryWrites', 'serverApi', 'serverSettingsBuilder', 'socketSettingsBuilder', 'sslSettingsBuilder',
- 'timeoutMS', 'transportSettings', 'uuidRepresentation', 'writeConcern']
+ 'timeoutMS', 'tracer', 'transportSettings', 'uuidRepresentation', 'writeConcern']
then:
actual == expected
@@ -570,7 +570,7 @@ class MongoClientSettingsSpecification extends Specification {
'applyToSslSettings', 'autoEncryptionSettings', 'build', 'codecRegistry', 'commandListenerList',
'compressorList', 'contextProvider', 'credential', 'dnsClient', 'heartbeatConnectTimeoutMS',
'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'readConcern', 'readPreference', 'retryReads', 'retryWrites',
- 'serverApi', 'timeout', 'transportSettings', 'uuidRepresentation', 'writeConcern']
+ 'serverApi', 'timeout', 'tracer', 'transportSettings', 'uuidRepresentation', 'writeConcern']
then:
actual == expected
diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncChangeStreamBatchCursorSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncChangeStreamBatchCursorSpecification.groovy
index 998c0a28b6e..b7f8f0e9408 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncChangeStreamBatchCursorSpecification.groovy
+++ b/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncChangeStreamBatchCursorSpecification.groovy
@@ -22,6 +22,7 @@ import com.mongodb.internal.TimeoutContext
import com.mongodb.internal.async.SingleResultCallback
import com.mongodb.internal.binding.AsyncReadBinding
import com.mongodb.internal.connection.OperationContext
+import com.mongodb.internal.tracing.TracingManager
import org.bson.Document
import spock.lang.Specification
@@ -34,6 +35,7 @@ class AsyncChangeStreamBatchCursorSpecification extends Specification {
def changeStreamOpertation = Stub(ChangeStreamOperation)
def binding = Mock(AsyncReadBinding)
def operationContext = Mock(OperationContext)
+ operationContext.getTracingManager() >> TracingManager.NO_OP
def timeoutContext = Mock(TimeoutContext)
binding.getOperationContext() >> operationContext
operationContext.getTimeoutContext() >> timeoutContext
@@ -78,6 +80,7 @@ class AsyncChangeStreamBatchCursorSpecification extends Specification {
def changeStreamOpertation = Stub(ChangeStreamOperation)
def binding = Mock(AsyncReadBinding)
def operationContext = Mock(OperationContext)
+ operationContext.getTracingManager() >> TracingManager.NO_OP
def timeoutContext = Mock(TimeoutContext)
binding.getOperationContext() >> operationContext
operationContext.getTimeoutContext() >> timeoutContext
@@ -111,6 +114,7 @@ class AsyncChangeStreamBatchCursorSpecification extends Specification {
def changeStreamOpertation = Stub(ChangeStreamOperation)
def binding = Mock(AsyncReadBinding)
def operationContext = Mock(OperationContext)
+ operationContext.getTracingManager() >> TracingManager.NO_OP
def timeoutContext = Mock(TimeoutContext)
binding.getOperationContext() >> operationContext
operationContext.getTimeoutContext() >> timeoutContext
diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncCommandBatchCursorSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncCommandBatchCursorSpecification.groovy
index d2bcd0804bb..e3f2e525146 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncCommandBatchCursorSpecification.groovy
+++ b/driver-core/src/test/unit/com/mongodb/internal/operation/AsyncCommandBatchCursorSpecification.groovy
@@ -35,6 +35,7 @@ import com.mongodb.internal.async.SingleResultCallback
import com.mongodb.internal.binding.AsyncConnectionSource
import com.mongodb.internal.connection.AsyncConnection
import com.mongodb.internal.connection.OperationContext
+import com.mongodb.internal.tracing.TracingManager
import org.bson.BsonArray
import org.bson.BsonDocument
import org.bson.BsonInt32
@@ -524,6 +525,7 @@ class AsyncCommandBatchCursorSpecification extends Specification {
.build()
}
OperationContext operationContext = Mock(OperationContext)
+ operationContext.getTracingManager() >> TracingManager.NO_OP
def timeoutContext = Spy(new TimeoutContext(TimeoutSettings.create(
MongoClientSettings.builder().timeout(3, TimeUnit.SECONDS).build())))
operationContext.getTimeoutContext() >> timeoutContext
diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/ChangeStreamBatchCursorTest.java b/driver-core/src/test/unit/com/mongodb/internal/operation/ChangeStreamBatchCursorTest.java
index 48c3a50e79a..552afaea95b 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/operation/ChangeStreamBatchCursorTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/operation/ChangeStreamBatchCursorTest.java
@@ -25,6 +25,7 @@
import com.mongodb.internal.binding.ReadBinding;
import com.mongodb.internal.connection.Connection;
import com.mongodb.internal.connection.OperationContext;
+import com.mongodb.internal.tracing.TracingManager;
import org.bson.BsonDocument;
import org.bson.BsonInt32;
import org.bson.Document;
@@ -296,6 +297,7 @@ void setUp() {
doNothing().when(timeoutContext).resetTimeoutIfPresent();
operationContext = mock(OperationContext.class);
+ when(operationContext.getTracingManager()).thenReturn(TracingManager.NO_OP);
when(operationContext.getTimeoutContext()).thenReturn(timeoutContext);
connection = mock(Connection.class);
when(connection.command(any(), any(), any(), any(), any(), any())).thenReturn(null);
diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/CommandBatchCursorSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/CommandBatchCursorSpecification.groovy
index c95a119134a..3190c1f6289 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/operation/CommandBatchCursorSpecification.groovy
+++ b/driver-core/src/test/unit/com/mongodb/internal/operation/CommandBatchCursorSpecification.groovy
@@ -35,6 +35,7 @@ import com.mongodb.internal.TimeoutSettings
import com.mongodb.internal.binding.ConnectionSource
import com.mongodb.internal.connection.Connection
import com.mongodb.internal.connection.OperationContext
+import com.mongodb.internal.tracing.TracingManager
import org.bson.BsonArray
import org.bson.BsonDocument
import org.bson.BsonInt32
@@ -574,6 +575,7 @@ class CommandBatchCursorSpecification extends Specification {
.build()
}
OperationContext operationContext = Mock(OperationContext)
+ operationContext.getTracingManager() >> TracingManager.NO_OP
def timeoutContext = Spy(new TimeoutContext(TimeoutSettings.create(
MongoClientSettings.builder().timeout(3, TimeUnit.SECONDS).build())))
operationContext.getTimeoutContext() >> timeoutContext
diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/CommandBatchCursorTest.java b/driver-core/src/test/unit/com/mongodb/internal/operation/CommandBatchCursorTest.java
index c3bec291432..b4b4101bd56 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/operation/CommandBatchCursorTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/operation/CommandBatchCursorTest.java
@@ -31,6 +31,7 @@
import com.mongodb.internal.binding.ConnectionSource;
import com.mongodb.internal.connection.Connection;
import com.mongodb.internal.connection.OperationContext;
+import com.mongodb.internal.tracing.TracingManager;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonInt32;
@@ -94,6 +95,8 @@ void setUp() {
connectionSource = mock(ConnectionSource.class);
operationContext = mock(OperationContext.class);
+ when(operationContext.getTracingManager()).thenReturn(TracingManager.NO_OP);
+
timeoutContext = new TimeoutContext(TimeoutSettings.create(
MongoClientSettings.builder().timeout(TIMEOUT.toMillis(), MILLISECONDS).build()));
serverDescription = mock(ServerDescription.class);
diff --git a/driver-kotlin-coroutine/build.gradle.kts b/driver-kotlin-coroutine/build.gradle.kts
index 02a2bf047aa..dd127a4dd6a 100644
--- a/driver-kotlin-coroutine/build.gradle.kts
+++ b/driver-kotlin-coroutine/build.gradle.kts
@@ -38,6 +38,7 @@ dependencies {
integrationTestImplementation(project(path = ":bson", configuration = "testArtifacts"))
integrationTestImplementation(project(path = ":driver-sync", configuration = "testArtifacts"))
integrationTestImplementation(project(path = ":driver-core", configuration = "testArtifacts"))
+ integrationTestImplementation(libs.micrometer)
}
configureMavenPublication {
diff --git a/driver-kotlin-sync/build.gradle.kts b/driver-kotlin-sync/build.gradle.kts
index 5da1a5eec26..b6113200628 100644
--- a/driver-kotlin-sync/build.gradle.kts
+++ b/driver-kotlin-sync/build.gradle.kts
@@ -32,6 +32,7 @@ dependencies {
integrationTestImplementation(project(path = ":bson", configuration = "testArtifacts"))
integrationTestImplementation(project(path = ":driver-sync", configuration = "testArtifacts"))
integrationTestImplementation(project(path = ":driver-core", configuration = "testArtifacts"))
+ integrationTestImplementation(libs.micrometer)
}
configureMavenPublication {
diff --git a/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala b/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala
index a5b76965651..4e93a331776 100644
--- a/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala
+++ b/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala
@@ -94,7 +94,8 @@ class ApiAliasAndCompanionSpec extends BaseSpec {
"SyncClientEncryption",
"BaseClientUpdateOptions",
"BaseClientDeleteOptions",
- "MongoBaseInterfaceAssertions"
+ "MongoBaseInterfaceAssertions",
+ "MicrometerTracer"
)
val scalaExclusions = Set(
"BuildInfo",
diff --git a/driver-sync/src/test/functional/com/mongodb/client/tracing/ZipkinTracer.java b/driver-sync/src/test/functional/com/mongodb/client/tracing/ZipkinTracer.java
index 305e916d213..186835c6ca2 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/tracing/ZipkinTracer.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/tracing/ZipkinTracer.java
@@ -35,7 +35,7 @@
* A utility class to create a Zipkin tracer using OpenTelemetry protocol, useful for visualizing spans in Zipkin UI
* This tracer can be used to send spans to a Zipkin server.
*
- * Spans are visible in the Zipkin UI at ....
+ * Spans are visible in the Zipkin UI at http://localhost:9411.
*
* To Start Zipkin server, you can use the following command:
*
{@code
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 f1429431690..4dfedf0a8d5 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
@@ -66,6 +66,9 @@
import com.mongodb.lang.NonNull;
import com.mongodb.lang.Nullable;
import com.mongodb.logging.TestLoggingInterceptor;
+import com.mongodb.tracing.MicrometerTracer;
+import io.micrometer.tracing.Tracer;
+import io.micrometer.tracing.test.simple.SimpleTracer;
import org.bson.BsonArray;
import org.bson.BsonBoolean;
import org.bson.BsonDocument;
@@ -112,7 +115,7 @@ public final class Entities {
private static final Set SUPPORTED_CLIENT_ENTITY_OPTIONS = new HashSet<>(
asList(
"id", "uriOptions", "serverApi", "useMultipleMongoses", "storeEventsAsEntities",
- "observeEvents", "observeLogMessages", "observeSensitiveCommands", "ignoreCommandMonitoringEvents"));
+ "observeEvents", "observeLogMessages", "observeSensitiveCommands", "ignoreCommandMonitoringEvents", "tracing"));
private final Set entityNames = new HashSet<>();
private final Map threads = new HashMap<>();
private final Map>> tasks = new HashMap<>();
@@ -126,6 +129,7 @@ public final class Entities {
private final Map clientEncryptions = new HashMap<>();
private final Map clientCommandListeners = new HashMap<>();
private final Map clientLoggingInterceptors = new HashMap<>();
+ private final Map clientTracing = new HashMap<>();
private final Map clientConnectionPoolListeners = new HashMap<>();
private final Map clientServerListeners = new HashMap<>();
private final Map clientClusterListeners = new HashMap<>();
@@ -294,6 +298,10 @@ public TestLoggingInterceptor getClientLoggingInterceptor(final String id) {
return getEntity(id + "-logging-interceptor", clientLoggingInterceptors, "logging interceptor");
}
+ public Tracer getClientTracer(final String id) {
+ return getEntity(id + "-tracing", clientTracing, "micrometer tracing");
+ }
+
public TestConnectionPoolListener getConnectionPoolListener(final String id) {
return getEntity(id + "-connection-pool-listener", clientConnectionPoolListeners, "connection pool listener");
}
@@ -604,6 +612,22 @@ private void initClient(final BsonDocument entity, final String id,
}
clientSettingsBuilder.serverApi(serverApiBuilder.build());
}
+
+ if (entity.containsKey("tracing")) {
+ boolean enableCommandPayload = entity.getDocument("tracing").get("enableCommandPayload", BsonBoolean.FALSE).asBoolean().getValue();
+ /* To enable Zipkin backend, uncomment the following lines and ensure you have the server started
+ (docker run -d -p 9411:9411 openzipkin/zipkin). The tests will fail but the captured spans will be
+ visible in the Zipkin UI at http://localhost:9411 for debugging purpose.
+ *
+ * Tracer tracer = ZipkinTracer.getTracer("UTR");
+ * putEntity(id + "-tracing", new SimpleTracer(), clientTracing);
+ */
+ Tracer tracer = new SimpleTracer();
+ putEntity(id + "-tracing", tracer, clientTracing);
+
+ clientSettingsBuilder.tracer(new MicrometerTracer(tracer, enableCommandPayload));
+ }
+
MongoClientSettings clientSettings = clientSettingsBuilder.build();
if (entity.containsKey("observeLogMessages")) {
diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/MicrometerTracingTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/MicrometerTracingTest.java
new file mode 100644
index 00000000000..84bc0734c5f
--- /dev/null
+++ b/driver-sync/src/test/functional/com/mongodb/client/unified/MicrometerTracingTest.java
@@ -0,0 +1,27 @@
+/*
+ * 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.client.unified;
+
+import org.junit.jupiter.params.provider.Arguments;
+
+import java.util.Collection;
+
+final class MicrometerTracingTest extends UnifiedSyncTest {
+ private static Collection data() {
+ return getTestData("tracing/tests");
+ }
+}
diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy
index 62c16330950..f7b099a604d 100644
--- a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy
+++ b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy
@@ -28,6 +28,7 @@ import com.mongodb.internal.TimeoutSettings
import com.mongodb.internal.client.model.changestream.ChangeStreamLevel
import com.mongodb.internal.connection.Cluster
import com.mongodb.internal.session.ServerSessionPool
+import com.mongodb.internal.tracing.TracingManager
import org.bson.BsonDocument
import org.bson.Document
import org.bson.codecs.UuidCodec
@@ -258,6 +259,7 @@ class MongoClusterSpecification extends Specification {
MongoClusterImpl createMongoCluster(final MongoClientSettings settings, final OperationExecutor operationExecutor) {
new MongoClusterImpl(null, cluster, settings.codecRegistry, null, null,
originator, operationExecutor, settings.readConcern, settings.readPreference, settings.retryReads, settings.retryWrites,
- null, serverSessionPool, TimeoutSettings.create(settings), settings.uuidRepresentation, settings.writeConcern)
+ null, serverSessionPool, TimeoutSettings.create(settings), settings.uuidRepresentation,
+ settings.writeConcern, TracingManager.NO_OP)
}
}
From 86f179171381bcb432db45926afcad675133e02f Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Tue, 5 Aug 2025 00:27:55 +0100
Subject: [PATCH 04/14] Fixing tests
---
.../internal/connection/InternalStreamConnection.java | 10 +++++-----
.../mongodb/internal/connection/CommandHelperTest.java | 1 +
.../LoggingCommandEventSenderSpecification.groovy | 10 ++++++----
.../kotlin/com/mongodb/kotlin/client/ClientSession.kt | 4 ++++
driver-reactive-streams/build.gradle.kts | 3 +++
.../com/mongodb/client/internal/MongoClusterImpl.java | 2 +-
6 files changed, 20 insertions(+), 10 deletions(-)
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 bd3008cf927..56bcc48ce34 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
@@ -390,7 +390,7 @@ public T sendAndReceive(final CommandMessage message, final Decoder decod
message, decoder, operationContext);
try {
return sendAndReceiveInternal.get();
- } catch (Throwable e) {
+ } catch (MongoCommandException e) {
if (reauthenticationIsTriggered(e)) {
return reauthenticateAndRetry(sendAndReceiveInternal, operationContext);
}
@@ -405,8 +405,9 @@ public void sendAndReceiveAsync(final CommandMessage message, final Decoder<
AsyncSupplier sendAndReceiveAsyncInternal = c -> sendAndReceiveAsyncInternal(
message, decoder, operationContext, c);
-
- beginAsync().thenSupply(sendAndReceiveAsyncInternal::getAsync).onErrorIf(this::reauthenticationIsTriggered, (t, c) -> {
+ beginAsync().thenSupply(c -> {
+ sendAndReceiveAsyncInternal.getAsync(c);
+ }).onErrorIf(e -> reauthenticationIsTriggered(e), (t, c) -> {
reauthenticateAndRetryAsync(sendAndReceiveAsyncInternal, operationContext, c);
}).finish(callback);
}
@@ -1034,9 +1035,8 @@ private Span createTracingSpan(final CommandMessage message, final OperationCont
TracingManager tracingManager = operationContext.getTracingManager();
BsonDocument command = message.getCommandDocument(bsonOutput);
-// BsonDocument command = message.getCommand();
String commandName = command.getFirstKey();
-// Span newSpan = tracingManager.addSpan("_____Command_____[ " + commandName + " ]", myparentContext);
+
if (!tracingManager.isEnabled()
|| SECURITY_SENSITIVE_COMMANDS.contains(commandName)
|| SECURITY_SENSITIVE_HELLO_COMMANDS.contains(commandName)) {
diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java
index 447a1efb5dd..f7873379c3b 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/connection/CommandHelperTest.java
@@ -118,6 +118,7 @@ void testIsCommandOk() {
assertFalse(CommandHelper.isCommandOk(new BsonDocument()));
}
+
OperationContext createOperationContext() {
return new OperationContext(IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE,
new TimeoutContext(TimeoutSettings.DEFAULT), ServerApi.builder().version(ServerApiVersion.V1).build());
diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/LoggingCommandEventSenderSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/LoggingCommandEventSenderSpecification.groovy
index 6f8eaf33314..35bced971fa 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/connection/LoggingCommandEventSenderSpecification.groovy
+++ b/driver-core/src/test/unit/com/mongodb/internal/connection/LoggingCommandEventSenderSpecification.groovy
@@ -66,7 +66,8 @@ class LoggingCommandEventSenderSpecification extends Specification {
}
def operationContext = OPERATION_CONTEXT
def sender = new LoggingCommandEventSender([] as Set, [] as Set, connectionDescription, commandListener,
- operationContext, message, bsonOutput, new StructuredLogger(logger), LoggerSettings.builder().build())
+ operationContext, message, message.getCommandDocument(bsonOutput),
+ new StructuredLogger(logger), LoggerSettings.builder().build())
when:
sender.sendStartedEvent()
@@ -111,7 +112,7 @@ class LoggingCommandEventSenderSpecification extends Specification {
}
def operationContext = OPERATION_CONTEXT
def sender = new LoggingCommandEventSender([] as Set, [] as Set, connectionDescription, commandListener,
- operationContext, message, bsonOutput, new StructuredLogger(logger),
+ operationContext, message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger),
LoggerSettings.builder().build())
when:
sender.sendStartedEvent()
@@ -169,7 +170,7 @@ class LoggingCommandEventSenderSpecification extends Specification {
def operationContext = OPERATION_CONTEXT
def sender = new LoggingCommandEventSender([] as Set, [] as Set, connectionDescription, null, operationContext,
- message, bsonOutput, new StructuredLogger(logger), LoggerSettings.builder().build())
+ message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger), LoggerSettings.builder().build())
when:
sender.sendStartedEvent()
@@ -202,7 +203,8 @@ class LoggingCommandEventSenderSpecification extends Specification {
}
def operationContext = OPERATION_CONTEXT
def sender = new LoggingCommandEventSender(['createUser'] as Set, [] as Set, connectionDescription, null,
- operationContext, message, bsonOutput, new StructuredLogger(logger), LoggerSettings.builder().build())
+ operationContext, message, message.getCommandDocument(bsonOutput), new StructuredLogger(logger),
+ LoggerSettings.builder().build())
when:
sender.sendStartedEvent()
diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt
index 9103689b251..9786f5592e6 100644
--- a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt
+++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/ClientSession.kt
@@ -18,6 +18,7 @@ package com.mongodb.kotlin.client
import com.mongodb.ClientSessionOptions
import com.mongodb.TransactionOptions
import com.mongodb.client.ClientSession as JClientSession
+import com.mongodb.internal.tracing.TransactionSpan
import java.io.Closeable
import java.util.concurrent.TimeUnit
@@ -86,6 +87,9 @@ public class ClientSession(public val wrapped: JClientSession) : Closeable {
transactionBody: () -> T,
options: TransactionOptions = TransactionOptions.builder().build()
): T = wrapped.withTransaction(transactionBody, options)
+
+ /** Get the transaction span (if started). */
+ public fun getTransactionSpan(): TransactionSpan? = wrapped.getTransactionSpan()
}
/**
diff --git a/driver-reactive-streams/build.gradle.kts b/driver-reactive-streams/build.gradle.kts
index f1c758b31da..f9ac5301625 100644
--- a/driver-reactive-streams/build.gradle.kts
+++ b/driver-reactive-streams/build.gradle.kts
@@ -44,6 +44,9 @@ dependencies {
// Reactive Streams TCK testing
testImplementation(libs.reactive.streams.tck)
+
+ // Tracing
+ testImplementation(libs.micrometer)
}
configureMavenPublication {
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 f1ea046b591..ec2fbb5c759 100644
--- a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java
+++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java
@@ -590,7 +590,7 @@ ClientSession getClientSession(@Nullable final ClientSession clientSessionFromOp
* @param commandName the name of the command
*/
@Nullable
- private Span createOperationSpan(ClientSession actualClientSession, BindingContext binding, String commandName) {
+ private Span createOperationSpan(final ClientSession actualClientSession, final BindingContext binding, final String commandName) {
TracingManager tracingManager = binding.getOperationContext().getTracingManager();
if (tracingManager.isEnabled()) {
TraceContext parentContext = null;
From da3ac3294db32f2fd20732abb2e0f9935bf49ae2 Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Sun, 10 Aug 2025 18:33:33 +0100
Subject: [PATCH 05/14] Adding method to get namespace for Read and Write
Operations
---
.../internal/operation/AbortTransactionOperation.java | 6 ++++++
.../operation/AbstractWriteSearchIndexOperation.java | 4 +++-
.../mongodb/internal/operation/AggregateOperation.java | 3 ++-
.../internal/operation/AggregateOperationImpl.java | 9 +++++----
.../operation/AggregateToCollectionOperation.java | 5 +++++
.../internal/operation/BaseFindAndModifyOperation.java | 1 +
.../internal/operation/ClientBulkWriteOperation.java | 5 +++++
.../mongodb/internal/operation/CommandReadOperation.java | 7 +++++++
.../internal/operation/CommitTransactionOperation.java | 6 ++++++
.../internal/operation/CountDocumentsOperation.java | 5 +++++
.../com/mongodb/internal/operation/CountOperation.java | 5 +++++
.../internal/operation/CreateCollectionOperation.java | 6 ++++++
.../internal/operation/CreateIndexesOperation.java | 5 +++++
.../mongodb/internal/operation/CreateViewOperation.java | 6 ++++++
.../mongodb/internal/operation/DistinctOperation.java | 5 +++++
.../internal/operation/DropCollectionOperation.java | 5 +++++
.../internal/operation/DropDatabaseOperation.java | 6 ++++++
.../mongodb/internal/operation/DropIndexOperation.java | 5 +++++
.../operation/EstimatedDocumentCountOperation.java | 5 +++++
.../com/mongodb/internal/operation/FindOperation.java | 1 +
.../internal/operation/ListCollectionsOperation.java | 7 +++++++
.../internal/operation/ListDatabasesOperation.java | 7 +++++++
.../mongodb/internal/operation/ListIndexesOperation.java | 5 +++++
.../internal/operation/ListSearchIndexesOperation.java | 5 +++++
.../operation/MapReduceToCollectionOperation.java | 1 +
.../operation/MapReduceWithInlineResultsOperation.java | 1 +
.../internal/operation/MixedBulkWriteOperation.java | 1 +
.../com/mongodb/internal/operation/ReadOperation.java | 6 ++++++
.../internal/operation/RenameCollectionOperation.java | 5 +++++
.../com/mongodb/internal/operation/WriteOperation.java | 6 ++++++
.../mongodb/client/internal/MapReduceIterableImpl.java | 5 +++++
31 files changed, 143 insertions(+), 6 deletions(-)
diff --git a/driver-core/src/main/com/mongodb/internal/operation/AbortTransactionOperation.java b/driver-core/src/main/com/mongodb/internal/operation/AbortTransactionOperation.java
index bc7e6655bc7..4f48722747e 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/AbortTransactionOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/AbortTransactionOperation.java
@@ -17,6 +17,7 @@
package com.mongodb.internal.operation;
import com.mongodb.Function;
+import com.mongodb.MongoNamespace;
import com.mongodb.WriteConcern;
import com.mongodb.internal.TimeoutContext;
import com.mongodb.lang.Nullable;
@@ -48,6 +49,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return new MongoNamespace("admin", MongoNamespace.COMMAND_COLLECTION_NAME); // TODO double check
+ }
+
@Override
CommandCreator getCommandCreator() {
return (operationContext, serverDescription, connectionDescription) -> {
diff --git a/driver-core/src/main/com/mongodb/internal/operation/AbstractWriteSearchIndexOperation.java b/driver-core/src/main/com/mongodb/internal/operation/AbstractWriteSearchIndexOperation.java
index 6ebcfda6dbe..ba033eb949a 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/AbstractWriteSearchIndexOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/AbstractWriteSearchIndexOperation.java
@@ -96,7 +96,9 @@ void swallowOrThrow(@Nullable final E mongoExecutionExcept
abstract BsonDocument buildCommand();
- MongoNamespace getNamespace() {
+
+ @Override
+ public MongoNamespace getNamespace() {
return namespace;
}
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/AggregateOperation.java b/driver-core/src/main/com/mongodb/internal/operation/AggregateOperation.java
index 1c9abfc68ca..55c5c231a21 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/AggregateOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/AggregateOperation.java
@@ -164,7 +164,8 @@ CommandReadOperation createExplainableOperation(@Nullable final ExplainVe
}, resultDecoder);
}
- MongoNamespace getNamespace() {
+ @Override
+ public MongoNamespace getNamespace() {
return wrapped.getNamespace();
}
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/AggregateOperationImpl.java b/driver-core/src/main/com/mongodb/internal/operation/AggregateOperationImpl.java
index 4c9bc3828b7..e2e8d6fb426 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/AggregateOperationImpl.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/AggregateOperationImpl.java
@@ -92,10 +92,6 @@ class AggregateOperationImpl implements ReadOperationCursor {
this.pipelineCreator = notNull("pipelineCreator", pipelineCreator);
}
- MongoNamespace getNamespace() {
- return namespace;
- }
-
List getPipeline() {
return pipeline;
}
@@ -191,6 +187,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return namespace;
+ }
+
@Override
public BatchCursor execute(final ReadBinding binding) {
return executeRetryableRead(binding, namespace.getDatabaseName(),
diff --git a/driver-core/src/main/com/mongodb/internal/operation/AggregateToCollectionOperation.java b/driver-core/src/main/com/mongodb/internal/operation/AggregateToCollectionOperation.java
index 16f33ad45e5..296b4eabb88 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/AggregateToCollectionOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/AggregateToCollectionOperation.java
@@ -157,6 +157,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return namespace;
+ }
+
@Override
public Void execute(final ReadBinding binding) {
return executeRetryableRead(binding,
diff --git a/driver-core/src/main/com/mongodb/internal/operation/BaseFindAndModifyOperation.java b/driver-core/src/main/com/mongodb/internal/operation/BaseFindAndModifyOperation.java
index c5d56fda81c..8f66333eb02 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/BaseFindAndModifyOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/BaseFindAndModifyOperation.java
@@ -92,6 +92,7 @@ public void executeAsync(final AsyncWriteBinding binding, final SingleResultCall
FindAndModifyHelper.asyncTransformer(), cmd -> cmd, callback);
}
+ @Override
public MongoNamespace getNamespace() {
return namespace;
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java
index 2b9e79f6f06..ad380781f73 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java
@@ -182,6 +182,11 @@ public String getCommandName() {
return "bulkWrite";
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return getNamespacedModel(models, 0).getNamespace();
+ }
+
@Override
public ClientBulkWriteResult execute(final WriteBinding binding) throws ClientBulkWriteException {
WriteConcern effectiveWriteConcern = validateAndGetEffectiveWriteConcern(binding.getOperationContext().getSessionContext());
diff --git a/driver-core/src/main/com/mongodb/internal/operation/CommandReadOperation.java b/driver-core/src/main/com/mongodb/internal/operation/CommandReadOperation.java
index 6965bfc34a3..0fbc6eb06e9 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/CommandReadOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/CommandReadOperation.java
@@ -16,12 +16,14 @@
package com.mongodb.internal.operation;
+import com.mongodb.MongoNamespace;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.binding.AsyncReadBinding;
import com.mongodb.internal.binding.ReadBinding;
import org.bson.BsonDocument;
import org.bson.codecs.Decoder;
+import static com.mongodb.MongoNamespace.COMMAND_COLLECTION_NAME;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.internal.operation.AsyncOperationHelper.executeRetryableReadAsync;
import static com.mongodb.internal.operation.CommandOperationHelper.CommandCreator;
@@ -55,6 +57,11 @@ public String getCommandName() {
return commandName;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return new MongoNamespace(databaseName, COMMAND_COLLECTION_NAME);
+ }
+
@Override
public T execute(final ReadBinding binding) {
return executeRetryableRead(binding, databaseName, commandCreator, decoder,
diff --git a/driver-core/src/main/com/mongodb/internal/operation/CommitTransactionOperation.java b/driver-core/src/main/com/mongodb/internal/operation/CommitTransactionOperation.java
index 998a002f348..97e62ffceac 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/CommitTransactionOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/CommitTransactionOperation.java
@@ -19,6 +19,7 @@
import com.mongodb.Function;
import com.mongodb.MongoException;
import com.mongodb.MongoExecutionTimeoutException;
+import com.mongodb.MongoNamespace;
import com.mongodb.MongoNodeIsRecoveringException;
import com.mongodb.MongoNotPrimaryException;
import com.mongodb.MongoSocketException;
@@ -116,6 +117,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return new MongoNamespace("admin", MongoNamespace.COMMAND_COLLECTION_NAME);
+ }
+
@Override
CommandCreator getCommandCreator() {
CommandCreator creator = (operationContext, serverDescription, connectionDescription) -> {
diff --git a/driver-core/src/main/com/mongodb/internal/operation/CountDocumentsOperation.java b/driver-core/src/main/com/mongodb/internal/operation/CountDocumentsOperation.java
index 9460026062a..157d3660904 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/CountDocumentsOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/CountDocumentsOperation.java
@@ -125,6 +125,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return namespace;
+ }
+
@Override
public Long execute(final ReadBinding binding) {
try (BatchCursor cursor = getAggregateOperation().execute(binding)) {
diff --git a/driver-core/src/main/com/mongodb/internal/operation/CountOperation.java b/driver-core/src/main/com/mongodb/internal/operation/CountOperation.java
index 6d0b7b78f93..d38a7c11333 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/CountOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/CountOperation.java
@@ -115,6 +115,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return namespace;
+ }
+
@Override
public Long execute(final ReadBinding binding) {
return executeRetryableRead(binding, namespace.getDatabaseName(),
diff --git a/driver-core/src/main/com/mongodb/internal/operation/CreateCollectionOperation.java b/driver-core/src/main/com/mongodb/internal/operation/CreateCollectionOperation.java
index 5284076eecb..d8e757054c0 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/CreateCollectionOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/CreateCollectionOperation.java
@@ -18,6 +18,7 @@
import com.mongodb.MongoClientException;
import com.mongodb.MongoException;
+import com.mongodb.MongoNamespace;
import com.mongodb.WriteConcern;
import com.mongodb.client.model.ChangeStreamPreAndPostImagesOptions;
import com.mongodb.client.model.Collation;
@@ -236,6 +237,11 @@ public String getCommandName() {
return "createCollection";
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return new MongoNamespace(databaseName, collectionName);
+ }
+
@Override
public Void execute(final WriteBinding binding) {
return withConnection(binding, connection -> {
diff --git a/driver-core/src/main/com/mongodb/internal/operation/CreateIndexesOperation.java b/driver-core/src/main/com/mongodb/internal/operation/CreateIndexesOperation.java
index b9b4242a3f4..7e634f136e2 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/CreateIndexesOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/CreateIndexesOperation.java
@@ -105,6 +105,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return namespace;
+ }
+
@Override
public Void execute(final WriteBinding binding) {
try {
diff --git a/driver-core/src/main/com/mongodb/internal/operation/CreateViewOperation.java b/driver-core/src/main/com/mongodb/internal/operation/CreateViewOperation.java
index 49b47fb7e9c..61fd58d5a0f 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/CreateViewOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/CreateViewOperation.java
@@ -16,6 +16,7 @@
package com.mongodb.internal.operation;
+import com.mongodb.MongoNamespace;
import com.mongodb.WriteConcern;
import com.mongodb.client.model.Collation;
import com.mongodb.internal.async.SingleResultCallback;
@@ -128,6 +129,11 @@ public String getCommandName() {
return "createView";
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return new MongoNamespace(databaseName, viewName);
+ }
+
@Override
public Void execute(final WriteBinding binding) {
return withConnection(binding, connection -> {
diff --git a/driver-core/src/main/com/mongodb/internal/operation/DistinctOperation.java b/driver-core/src/main/com/mongodb/internal/operation/DistinctOperation.java
index 489e3923bdc..10c4c320100 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/DistinctOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/DistinctOperation.java
@@ -113,6 +113,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return namespace;
+ }
+
@Override
public BatchCursor execute(final ReadBinding binding) {
return executeRetryableRead(binding, namespace.getDatabaseName(), getCommandCreator(), createCommandDecoder(),
diff --git a/driver-core/src/main/com/mongodb/internal/operation/DropCollectionOperation.java b/driver-core/src/main/com/mongodb/internal/operation/DropCollectionOperation.java
index 5f61f2980f8..6bdbfa7bbcd 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/DropCollectionOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/DropCollectionOperation.java
@@ -91,6 +91,11 @@ public String getCommandName() {
return "dropCollection";
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return namespace;
+ }
+
@Override
public Void execute(final WriteBinding binding) {
BsonDocument localEncryptedFields = getEncryptedFields((ReadWriteBinding) binding);
diff --git a/driver-core/src/main/com/mongodb/internal/operation/DropDatabaseOperation.java b/driver-core/src/main/com/mongodb/internal/operation/DropDatabaseOperation.java
index d619176e8a3..2ee963923fe 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/DropDatabaseOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/DropDatabaseOperation.java
@@ -16,6 +16,7 @@
package com.mongodb.internal.operation;
+import com.mongodb.MongoNamespace;
import com.mongodb.WriteConcern;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.binding.AsyncWriteBinding;
@@ -60,6 +61,11 @@ public String getCommandName() {
return "dropDatabase";
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return new MongoNamespace(databaseName, MongoNamespace.COMMAND_COLLECTION_NAME);
+ }
+
@Override
public Void execute(final WriteBinding binding) {
return withConnection(binding, connection -> {
diff --git a/driver-core/src/main/com/mongodb/internal/operation/DropIndexOperation.java b/driver-core/src/main/com/mongodb/internal/operation/DropIndexOperation.java
index 3671a90aa56..8a3a66e3c50 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/DropIndexOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/DropIndexOperation.java
@@ -70,6 +70,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return namespace;
+ }
+
@Override
public Void execute(final WriteBinding binding) {
try {
diff --git a/driver-core/src/main/com/mongodb/internal/operation/EstimatedDocumentCountOperation.java b/driver-core/src/main/com/mongodb/internal/operation/EstimatedDocumentCountOperation.java
index 427cd40dc40..6308aae56ea 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/EstimatedDocumentCountOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/EstimatedDocumentCountOperation.java
@@ -75,6 +75,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return namespace;
+ }
+
@Override
public Long execute(final ReadBinding binding) {
try {
diff --git a/driver-core/src/main/com/mongodb/internal/operation/FindOperation.java b/driver-core/src/main/com/mongodb/internal/operation/FindOperation.java
index 2e6560e23c3..b18fc4ca1e0 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/FindOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/FindOperation.java
@@ -99,6 +99,7 @@ public FindOperation(final MongoNamespace namespace, final Decoder decoder) {
this.decoder = notNull("decoder", decoder);
}
+ @Override
public MongoNamespace getNamespace() {
return namespace;
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/ListCollectionsOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ListCollectionsOperation.java
index 8740986b23f..da3966b26de 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/ListCollectionsOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/ListCollectionsOperation.java
@@ -17,6 +17,7 @@
package com.mongodb.internal.operation;
import com.mongodb.MongoCommandException;
+import com.mongodb.MongoNamespace;
import com.mongodb.client.cursor.TimeoutMode;
import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.async.AsyncBatchCursor;
@@ -34,6 +35,7 @@
import java.util.function.Supplier;
+import static com.mongodb.MongoNamespace.COMMAND_COLLECTION_NAME;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
import static com.mongodb.internal.async.ErrorHandlingResultCallback.errorHandlingCallback;
@@ -86,6 +88,11 @@ public ListCollectionsOperation(final String databaseName, final Decoder deco
this.decoder = notNull("decoder", decoder);
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return new MongoNamespace(databaseName, COMMAND_COLLECTION_NAME);
+ }
+
public BsonDocument getFilter() {
return filter;
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/ListDatabasesOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ListDatabasesOperation.java
index 4787153190b..d51194406b6 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/ListDatabasesOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/ListDatabasesOperation.java
@@ -16,6 +16,7 @@
package com.mongodb.internal.operation;
+import com.mongodb.MongoNamespace;
import com.mongodb.internal.async.AsyncBatchCursor;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.binding.AsyncReadBinding;
@@ -26,6 +27,7 @@
import org.bson.BsonValue;
import org.bson.codecs.Decoder;
+import static com.mongodb.MongoNamespace.COMMAND_COLLECTION_NAME;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.internal.async.ErrorHandlingResultCallback.errorHandlingCallback;
import static com.mongodb.internal.operation.AsyncOperationHelper.asyncSingleBatchCursorTransformer;
@@ -107,6 +109,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return new MongoNamespace("admin", COMMAND_COLLECTION_NAME);
+ }
+
@Override
public BatchCursor execute(final ReadBinding binding) {
return executeRetryableRead(binding, "admin", getCommandCreator(), CommandResultDocumentCodec.create(decoder, DATABASES),
diff --git a/driver-core/src/main/com/mongodb/internal/operation/ListIndexesOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ListIndexesOperation.java
index a97acd64d58..76900ab296e 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/ListIndexesOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/ListIndexesOperation.java
@@ -122,6 +122,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return namespace;
+ }
+
@Override
public BatchCursor execute(final ReadBinding binding) {
RetryState retryState = initialRetryState(retryReads, binding.getOperationContext().getTimeoutContext());
diff --git a/driver-core/src/main/com/mongodb/internal/operation/ListSearchIndexesOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ListSearchIndexesOperation.java
index 7fadead0b57..3c78297463e 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/ListSearchIndexesOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/ListSearchIndexesOperation.java
@@ -78,6 +78,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return namespace;
+ }
+
@Override
public BatchCursor execute(final ReadBinding binding) {
try {
diff --git a/driver-core/src/main/com/mongodb/internal/operation/MapReduceToCollectionOperation.java b/driver-core/src/main/com/mongodb/internal/operation/MapReduceToCollectionOperation.java
index bfcc73a5aa6..96f5a8418d0 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/MapReduceToCollectionOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/MapReduceToCollectionOperation.java
@@ -87,6 +87,7 @@ public MapReduceToCollectionOperation(final MongoNamespace namespace, final Bson
this.writeConcern = writeConcern;
}
+ @Override
public MongoNamespace getNamespace() {
return namespace;
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/MapReduceWithInlineResultsOperation.java b/driver-core/src/main/com/mongodb/internal/operation/MapReduceWithInlineResultsOperation.java
index 6661c2a5c77..abbd2fc6ae8 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/MapReduceWithInlineResultsOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/MapReduceWithInlineResultsOperation.java
@@ -76,6 +76,7 @@ public MapReduceWithInlineResultsOperation(final MongoNamespace namespace, final
this.decoder = notNull("decoder", decoder);
}
+ @Override
public MongoNamespace getNamespace() {
return namespace;
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/MixedBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/MixedBulkWriteOperation.java
index 39ff2dab17f..b17a3bae30b 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/MixedBulkWriteOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/MixedBulkWriteOperation.java
@@ -101,6 +101,7 @@ public MixedBulkWriteOperation(final MongoNamespace namespace, final List exte
this.retryWrites = retryWrites;
}
+ @Override
public MongoNamespace getNamespace() {
return namespace;
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/ReadOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ReadOperation.java
index 6a90d490b30..2e198381cf0 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/ReadOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/ReadOperation.java
@@ -16,6 +16,7 @@
package com.mongodb.internal.operation;
+import com.mongodb.MongoNamespace;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.binding.AsyncReadBinding;
import com.mongodb.internal.binding.ReadBinding;
@@ -32,6 +33,11 @@ public interface ReadOperation {
*/
String getCommandName();
+ /**
+ * @return the namespace of the operation
+ */
+ MongoNamespace getNamespace();
+
/**
* General execute which can return anything of type T
*
diff --git a/driver-core/src/main/com/mongodb/internal/operation/RenameCollectionOperation.java b/driver-core/src/main/com/mongodb/internal/operation/RenameCollectionOperation.java
index ea477bf67bd..81d3b0bffe9 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/RenameCollectionOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/RenameCollectionOperation.java
@@ -79,6 +79,11 @@ public String getCommandName() {
return COMMAND_NAME;
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return originalNamespace;
+ }
+
@Override
public Void execute(final WriteBinding binding) {
return withConnection(binding, connection -> executeCommand(binding, "admin", getCommand(), connection,
diff --git a/driver-core/src/main/com/mongodb/internal/operation/WriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/WriteOperation.java
index 73cec2f416b..b4e2b4a25b4 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/WriteOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/WriteOperation.java
@@ -16,6 +16,7 @@
package com.mongodb.internal.operation;
+import com.mongodb.MongoNamespace;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.binding.AsyncWriteBinding;
import com.mongodb.internal.binding.WriteBinding;
@@ -32,6 +33,11 @@ public interface WriteOperation {
*/
String getCommandName();
+ /**
+ * @return the namespace of the operation
+ */
+ MongoNamespace getNamespace();
+
/**
* General execute which can return anything of type T
*
diff --git a/driver-sync/src/main/com/mongodb/client/internal/MapReduceIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MapReduceIterableImpl.java
index be3e8ca05e9..b7c05c5ffc2 100644
--- a/driver-sync/src/main/com/mongodb/client/internal/MapReduceIterableImpl.java
+++ b/driver-sync/src/main/com/mongodb/client/internal/MapReduceIterableImpl.java
@@ -240,6 +240,11 @@ public String getCommandName() {
return operation.getCommandName();
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return operation.getNamespace();
+ }
+
@Override
public BatchCursor execute(final ReadBinding binding) {
return operation.execute(binding);
From 1a75290ed1a90521b6fa6900eb96f078dc2d4544 Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Sun, 10 Aug 2025 18:56:30 +0100
Subject: [PATCH 06/14] Refactoring operations and command spans
---
config/checkstyle/suppressions.xml | 1 +
.../connection/InternalStreamConnection.java | 42 +++++++++++++------
.../internal/connection/OperationContext.java | 24 +++++------
.../com/mongodb/internal/tracing/Tags.java | 9 +++-
.../com/mongodb/tracing/MicrometerTracer.java | 17 ++++++++
driver-core/src/test/resources/specifications | 2 +-
.../LegacyMixedBulkWriteOperation.java | 5 +++
.../internal/MapReducePublisherImpl.java | 10 +++++
...dReadOperationThenCursorReadOperation.java | 6 +++
...WriteOperationThenCursorReadOperation.java | 6 +++
.../client/internal/MongoClusterImpl.java | 23 ++++++----
.../com/mongodb/client/unified/Entities.java | 6 +--
.../client/unified/MicrometerTracingTest.java | 2 +-
.../mongodb/client/unified/UnifiedTest.java | 24 ++++++++++-
14 files changed, 138 insertions(+), 39 deletions(-)
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
index 6d24f861e08..f3e6d3ef2ff 100644
--- a/config/checkstyle/suppressions.xml
+++ b/config/checkstyle/suppressions.xml
@@ -60,6 +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 56bcc48ce34..535c2fb2255 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
@@ -30,6 +30,7 @@
import com.mongodb.MongoSocketWriteException;
import com.mongodb.MongoSocketWriteTimeoutException;
import com.mongodb.ServerAddress;
+import com.mongodb.UnixServerAddress;
import com.mongodb.annotations.NotThreadSafe;
import com.mongodb.connection.AsyncCompletionHandler;
import com.mongodb.connection.ClusterConnectionMode;
@@ -97,8 +98,11 @@
import static com.mongodb.internal.logging.LogMessage.Level.DEBUG;
import static com.mongodb.internal.thread.InterruptionUtil.translateInterruptedException;
import static com.mongodb.internal.tracing.Tags.CLIENT_CONNECTION_ID;
+import static com.mongodb.internal.tracing.Tags.COLLECTION;
+import static com.mongodb.internal.tracing.Tags.COMMAND_NAME;
import static com.mongodb.internal.tracing.Tags.CURSOR_ID;
import static com.mongodb.internal.tracing.Tags.NAMESPACE;
+import static com.mongodb.internal.tracing.Tags.NETWORK_TRANSPORT;
import static com.mongodb.internal.tracing.Tags.QUERY_SUMMARY;
import static com.mongodb.internal.tracing.Tags.QUERY_TEXT;
import static com.mongodb.internal.tracing.Tags.SERVER_ADDRESS;
@@ -446,10 +450,10 @@ public boolean reauthenticationIsTriggered(@Nullable final Throwable t) {
private T sendAndReceiveInternal(final CommandMessage message, final Decoder decoder,
final OperationContext operationContext) {
CommandEventSender commandEventSender;
-
+ Span tracingSpan;
try (ByteBufferBsonOutput bsonOutput = new ByteBufferBsonOutput(this)) {
message.encode(bsonOutput, operationContext);
- Span tracingSpan = createTracingSpan(message, operationContext, bsonOutput);
+ tracingSpan = createTracingSpan(message, operationContext, bsonOutput);
boolean isLoggingCommandNeeded = isLoggingCommandNeeded();
boolean isTracingCommandPayloadNeeded = tracingSpan != null && operationContext.getTracingManager().isCommandPayloadEnabled();
@@ -480,17 +484,16 @@ private T sendAndReceiveInternal(final CommandMessage message, final Decoder
}
commandEventSender.sendFailedEvent(e);
throw e;
- } finally {
- if (tracingSpan != null) {
- tracingSpan.end();
- }
}
}
if (message.isResponseExpected()) {
- return receiveCommandMessageResponse(decoder, commandEventSender, operationContext);
+ return receiveCommandMessageResponse(decoder, commandEventSender, operationContext, tracingSpan);
} else {
commandEventSender.sendSucceededEventForOneWayCommand();
+ if (tracingSpan != null) {
+ tracingSpan.end();
+ }
return null;
}
}
@@ -509,7 +512,7 @@ public void send(final CommandMessage message, final Decoder decoder, fin
@Override
public T receive(final Decoder decoder, final OperationContext operationContext) {
isTrue("Response is expected", hasMoreToCome);
- return receiveCommandMessageResponse(decoder, new NoOpCommandEventSender(), operationContext);
+ return receiveCommandMessageResponse(decoder, new NoOpCommandEventSender(), operationContext, null);
}
@Override
@@ -555,7 +558,7 @@ private void trySendMessage(final CommandMessage message, final ByteBufferBsonOu
}
private T receiveCommandMessageResponse(final Decoder decoder, final CommandEventSender commandEventSender,
- final OperationContext operationContext) {
+ final OperationContext operationContext, @Nullable final Span tracingSpan) {
boolean commandSuccessful = false;
try (ResponseBuffers responseBuffers = receiveResponseBuffers(operationContext)) {
updateSessionContext(operationContext.getSessionContext(), responseBuffers);
@@ -580,7 +583,14 @@ private T receiveCommandMessageResponse(final Decoder decoder, final Comm
if (!commandSuccessful) {
commandEventSender.sendFailedEvent(e);
}
+ if (tracingSpan != null) {
+ tracingSpan.error(e);
+ }
throw e;
+ } finally {
+ if (tracingSpan != null) {
+ tracingSpan.end();
+ }
}
}
@@ -1043,14 +1053,22 @@ private Span createTracingSpan(final CommandMessage message, final OperationCont
return null;
}
+ Span operationSpan = operationContext.getTracingSpan();
Span span = tracingManager
- .addSpan("Command " + commandName, operationContext.getTracingSpanContext())
+ .addSpan("Command " + commandName, operationSpan != null ? operationSpan.context() : null)
.tag(SYSTEM, "mongodb")
.tag(NAMESPACE, message.getNamespace().getDatabaseName())
- .tag(QUERY_SUMMARY, commandName);
+ .tag(COLLECTION, message.getCollectionName())
+ .tag(QUERY_SUMMARY, commandName)
+ .tag(COMMAND_NAME, commandName)
+ .tag(NETWORK_TRANSPORT, getServerAddress() instanceof UnixServerAddress ? "unix" : "tcp");
if (command.containsKey("getMore")) {
- span.tag(CURSOR_ID, command.getInt64("getMore").longValue());
+ long cursorId = command.getInt64("getMore").longValue();
+ span.tag(CURSOR_ID, cursorId);
+ if (operationSpan != null) {
+ operationSpan.tag(CURSOR_ID, cursorId);
+ }
}
tagServerAndConnectionInfo(span, message);
diff --git a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
index 9f1c232b274..bc4a785d545 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
@@ -27,7 +27,7 @@
import com.mongodb.internal.TimeoutSettings;
import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.session.SessionContext;
-import com.mongodb.internal.tracing.TraceContext;
+import com.mongodb.internal.tracing.Span;
import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import com.mongodb.selector.ServerSelector;
@@ -55,7 +55,7 @@ public class OperationContext {
@Nullable
private final String operationName;
@Nullable
- private TraceContext tracingContext;
+ private Span tracingSpan;
public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext,
@Nullable final ServerApi serverApi) {
@@ -97,17 +97,17 @@ public static OperationContext simpleOperationContext(final TimeoutContext timeo
public OperationContext withSessionContext(final SessionContext sessionContext) {
return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi,
- operationName, tracingContext);
+ operationName, tracingSpan);
}
public OperationContext withTimeoutContext(final TimeoutContext timeoutContext) {
return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi,
- operationName, tracingContext);
+ operationName, tracingSpan);
}
public OperationContext withOperationName(final String operationName) {
return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi,
- operationName, tracingContext);
+ operationName, tracingSpan);
}
public long getId() {
@@ -141,12 +141,12 @@ public String getOperationName() {
}
@Nullable
- public TraceContext getTracingSpanContext() {
- return tracingContext != null ? tracingContext : null;
+ public Span getTracingSpan() {
+ return tracingSpan;
}
- public void setTracingContext(final TraceContext tracingContext) {
- this.tracingContext = tracingContext;
+ public void setTracingSpan(final Span tracingSpan) {
+ this.tracingSpan = tracingSpan;
}
@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
@@ -158,7 +158,7 @@ public OperationContext(final long id,
final TracingManager tracingManager,
@Nullable final ServerApi serverApi,
@Nullable final String operationName,
- @Nullable final TraceContext tracingContext) {
+ @Nullable final Span tracingSpan) {
this.id = id;
this.serverDeprioritization = serverDeprioritization;
@@ -168,7 +168,7 @@ public OperationContext(final long id,
this.tracingManager = tracingManager;
this.serverApi = serverApi;
this.operationName = operationName;
- this.tracingContext = tracingContext;
+ this.tracingSpan = tracingSpan;
}
@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
@@ -187,7 +187,7 @@ public OperationContext(final long id,
this.tracingManager = tracingManager;
this.serverApi = serverApi;
this.operationName = operationName;
- this.tracingContext = null;
+ this.tracingSpan = null;
}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Tags.java b/driver-core/src/main/com/mongodb/internal/tracing/Tags.java
index e4407f2a401..c7261a63af4 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/Tags.java
+++ b/driver-core/src/main/com/mongodb/internal/tracing/Tags.java
@@ -29,14 +29,21 @@ private Tags() {
public static final String SYSTEM = "db.system";
public static final String NAMESPACE = "db.namespace";
public static final String COLLECTION = "db.collection.name";
+ public static final String OPERATION_NAME = "db.operation.name";
+ public static final String COMMAND_NAME = "db.command.name";
+ public static final String NETWORK_TRANSPORT = "network.transport";
+ public static final String OPERATION_SUMMARY = "db.operation.summary";
public static final String QUERY_SUMMARY = "db.query.summary";
public static final String QUERY_TEXT = "db.query.text";
public static final String CURSOR_ID = "db.mongodb.cursor_id";
public static final String SERVER_ADDRESS = "server.address";
public static final String SERVER_PORT = "server.port";
public static final String SERVER_TYPE = "server.type";
- public static final String CLIENT_CONNECTION_ID = "db.mongodb.client_connection_id";
+ public static final String CLIENT_CONNECTION_ID = "db.mongodb.driver_connection_id";
public static final String SERVER_CONNECTION_ID = "db.mongodb.server_connection_id";
public static final String TRANSACTION_NUMBER = "db.mongodb.txnNumber";
public static final String SESSION_ID = "db.mongodb.lsid";
+ public static final String EXCEPTION_STACKTRACE = "exception.stacktrace";
+ public static final String EXCEPTION_TYPE = "exception.type";
+ public static final String EXCEPTION_MESSAGE = "exception.message";
}
diff --git a/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java b/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
index ed678b93cab..7ed0119d21d 100644
--- a/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
+++ b/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
@@ -21,6 +21,13 @@
import com.mongodb.internal.tracing.Tracer;
import com.mongodb.lang.Nullable;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import static com.mongodb.internal.tracing.Tags.EXCEPTION_MESSAGE;
+import static com.mongodb.internal.tracing.Tags.EXCEPTION_STACKTRACE;
+import static com.mongodb.internal.tracing.Tags.EXCEPTION_TYPE;
+
/**
* A {@link Tracer} implementation that delegates tracing operations to a Micrometer {@link io.micrometer.tracing.Tracer}.
*
@@ -148,6 +155,9 @@ public void event(final String event) {
@Override
public void error(final Throwable throwable) {
+ span.tag(EXCEPTION_MESSAGE, throwable.getMessage());
+ span.tag(EXCEPTION_TYPE, throwable.getClass().getName());
+ span.tag(EXCEPTION_STACKTRACE, getStackTraceAsString(throwable));
span.error(throwable);
}
@@ -160,5 +170,12 @@ public void end() {
public TraceContext context() {
return new MicrometerTraceContext(span.context());
}
+
+ private 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/test/resources/specifications b/driver-core/src/test/resources/specifications
index 61270d61656..8466c1b9df5 160000
--- a/driver-core/src/test/resources/specifications
+++ b/driver-core/src/test/resources/specifications
@@ -1 +1 @@
-Subproject commit 61270d61656709944e6b75e160453e3bfa658483
+Subproject commit 8466c1b9df5b2c4b293bb9223b55aa16d34e9e0f
diff --git a/driver-legacy/src/main/com/mongodb/LegacyMixedBulkWriteOperation.java b/driver-legacy/src/main/com/mongodb/LegacyMixedBulkWriteOperation.java
index 95990833f00..47749129115 100644
--- a/driver-legacy/src/main/com/mongodb/LegacyMixedBulkWriteOperation.java
+++ b/driver-legacy/src/main/com/mongodb/LegacyMixedBulkWriteOperation.java
@@ -97,6 +97,11 @@ public String getCommandName() {
return wrappedOperation.getCommandName();
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return wrappedOperation.getNamespace();
+ }
+
@Override
public WriteConcernResult execute(final WriteBinding binding) {
try {
diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MapReducePublisherImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MapReducePublisherImpl.java
index 27e69762a09..46096d6ff58 100644
--- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MapReducePublisherImpl.java
+++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MapReducePublisherImpl.java
@@ -240,6 +240,11 @@ public String getCommandName() {
return operation.getCommandName();
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return operation.getNamespace();
+ }
+
@Override
public void executeAsync(final AsyncReadBinding binding, final SingleResultCallback> callback) {
operation.executeAsync(binding, callback::onResult);
@@ -262,6 +267,11 @@ public String getCommandName() {
return operation.getCommandName();
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return operation.getNamespace();
+ }
+
@Override
public Void execute(final WriteBinding binding) {
throw new UnsupportedOperationException("This operation is async only");
diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/VoidReadOperationThenCursorReadOperation.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/VoidReadOperationThenCursorReadOperation.java
index e74949432b9..f5f3ae29969 100644
--- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/VoidReadOperationThenCursorReadOperation.java
+++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/VoidReadOperationThenCursorReadOperation.java
@@ -16,6 +16,7 @@
package com.mongodb.reactivestreams.client.internal;
+import com.mongodb.MongoNamespace;
import com.mongodb.internal.async.AsyncBatchCursor;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.binding.AsyncReadBinding;
@@ -45,6 +46,11 @@ public String getCommandName() {
return readOperation.getCommandName();
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return readOperation.getNamespace();
+ }
+
@Override
public void executeAsync(final AsyncReadBinding binding, final SingleResultCallback> callback) {
readOperation.executeAsync(binding, (result, t) -> {
diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/VoidWriteOperationThenCursorReadOperation.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/VoidWriteOperationThenCursorReadOperation.java
index 428ad21ca26..1a741d7d0f6 100644
--- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/VoidWriteOperationThenCursorReadOperation.java
+++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/VoidWriteOperationThenCursorReadOperation.java
@@ -16,6 +16,7 @@
package com.mongodb.reactivestreams.client.internal;
+import com.mongodb.MongoNamespace;
import com.mongodb.internal.async.AsyncBatchCursor;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.binding.AsyncReadBinding;
@@ -38,6 +39,11 @@ public String getCommandName() {
return writeOperation.getCommandName();
}
+ @Override
+ public MongoNamespace getNamespace() {
+ return writeOperation.getNamespace();
+ }
+
@Override
public void executeAsync(final AsyncReadBinding binding, final SingleResultCallback> callback) {
writeOperation.executeAsync((AsyncWriteBinding) binding, (result, t) -> {
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 37f911306fc..db8fd03adaf 100644
--- a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java
+++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java
@@ -22,6 +22,7 @@
import com.mongodb.MongoClientException;
import com.mongodb.MongoException;
import com.mongodb.MongoInternalException;
+import com.mongodb.MongoNamespace;
import com.mongodb.MongoQueryException;
import com.mongodb.MongoSocketException;
import com.mongodb.MongoTimeoutException;
@@ -428,7 +429,7 @@ public T execute(final ReadOperation operation, final ReadPreference r
ReadBinding binding = getReadBinding(readPreference, readConcern, actualClientSession, session == null,
operation.getCommandName());
- Span span = createOperationSpan(actualClientSession, binding, operation.getCommandName());
+ Span span = createOperationSpan(actualClientSession, binding, operation.getCommandName(), operation.getNamespace());
try {
if (actualClientSession.hasActiveTransaction() && !binding.getReadPreference().equals(primary())) {
@@ -461,7 +462,7 @@ public T execute(final WriteOperation operation, final ReadConcern readCo
ClientSession actualClientSession = getClientSession(session);
WriteBinding binding = getWriteBinding(readConcern, actualClientSession, session == null, operation.getCommandName());
- Span span = createOperationSpan(actualClientSession, binding, operation.getCommandName());
+ Span span = createOperationSpan(actualClientSession, binding, operation.getCommandName(), operation.getNamespace());
try {
return operation.execute(binding);
} catch (MongoException e) {
@@ -586,11 +587,13 @@ ClientSession getClientSession(@Nullable final ClientSession clientSessionFromOp
* Create a tracing span for the given operation, and set it on operation context.
*
* @param actualClientSession the session that the operation is part of
- * @param binding the binding for the operation
- * @param commandName the name of the command
+ * @param binding the binding for the operation
+ * @param commandName the name of the command
+ * @param namespace the namespace of the command
+ * @return the created span, or null if tracing is not enabled
*/
@Nullable
- private Span createOperationSpan(final ClientSession actualClientSession, final BindingContext binding, final String commandName) {
+ private Span createOperationSpan(final ClientSession actualClientSession, final BindingContext binding, final String commandName, final MongoNamespace namespace) {
TracingManager tracingManager = binding.getOperationContext().getTracingManager();
if (tracingManager.isEnabled()) {
TraceContext parentContext = null;
@@ -598,13 +601,17 @@ private Span createOperationSpan(final ClientSession actualClientSession, final
if (transactionSpan != null) {
parentContext = transactionSpan.getContext();
}
-
+ String name = commandName + " " + namespace.getFullName();
Span span = binding
.getOperationContext()
.getTracingManager()
- .addSpan(commandName, parentContext);
- binding.getOperationContext().setTracingContext(span.context());
+ .addSpan(name, parentContext);
+ binding.getOperationContext().setTracingSpan(span);
span.tag(Tags.SYSTEM, "mongodb");
+ span.tag(Tags.NAMESPACE, namespace.getDatabaseName());
+ span.tag(Tags.COLLECTION, namespace.getCollectionName());
+ span.tag(Tags.OPERATION_NAME, commandName);
+ span.tag(Tags.OPERATION_SUMMARY, name);
return span;
} else {
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 4dfedf0a8d5..c0d33b06d91 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
@@ -115,7 +115,7 @@ public final class Entities {
private static final Set SUPPORTED_CLIENT_ENTITY_OPTIONS = new HashSet<>(
asList(
"id", "uriOptions", "serverApi", "useMultipleMongoses", "storeEventsAsEntities",
- "observeEvents", "observeLogMessages", "observeSensitiveCommands", "ignoreCommandMonitoringEvents", "tracing"));
+ "observeEvents", "observeLogMessages", "observeSensitiveCommands", "ignoreCommandMonitoringEvents", "observeTracingMessages"));
private final Set entityNames = new HashSet<>();
private final Map threads = new HashMap<>();
private final Map>> tasks = new HashMap<>();
@@ -613,8 +613,8 @@ private void initClient(final BsonDocument entity, final String id,
clientSettingsBuilder.serverApi(serverApiBuilder.build());
}
- if (entity.containsKey("tracing")) {
- boolean enableCommandPayload = entity.getDocument("tracing").get("enableCommandPayload", BsonBoolean.FALSE).asBoolean().getValue();
+ if (entity.containsKey("observeTracingMessages")) {
+ boolean enableCommandPayload = entity.getDocument("observeTracingMessages").get("enableCommandPayload", BsonBoolean.FALSE).asBoolean().getValue();
/* To enable Zipkin backend, uncomment the following lines and ensure you have the server started
(docker run -d -p 9411:9411 openzipkin/zipkin). The tests will fail but the captured spans will be
visible in the Zipkin UI at http://localhost:9411 for debugging purpose.
diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/MicrometerTracingTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/MicrometerTracingTest.java
index 84bc0734c5f..8c65317d257 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/unified/MicrometerTracingTest.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/unified/MicrometerTracingTest.java
@@ -22,6 +22,6 @@
final class MicrometerTracingTest extends UnifiedSyncTest {
private static Collection data() {
- return getTestData("tracing/tests");
+ return getTestData("open-telemetry/tests");
}
}
diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java
index e067e36d993..8e91e85205b 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java
@@ -28,6 +28,7 @@
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.model.Filters;
import com.mongodb.client.test.CollectionHelper;
+import com.mongodb.client.tracing.SpanTree;
import com.mongodb.client.unified.UnifiedTestModifications.TestDef;
import com.mongodb.client.vault.ClientEncryption;
import com.mongodb.connection.ClusterDescription;
@@ -44,6 +45,8 @@
import com.mongodb.lang.Nullable;
import com.mongodb.logging.TestLoggingInterceptor;
import com.mongodb.test.AfterBeforeParameterResolver;
+import io.micrometer.tracing.Tracer;
+import io.micrometer.tracing.test.simple.SimpleTracer;
import org.bson.BsonArray;
import org.bson.BsonBoolean;
import org.bson.BsonDocument;
@@ -111,7 +114,7 @@ public abstract class UnifiedTest {
private static final Set PRESTART_POOL_ASYNC_WORK_MANAGER_FILE_DESCRIPTIONS = Collections.singleton(
"wait queue timeout errors include details about checked out connections");
- private static final String MAX_SUPPORTED_SCHEMA_VERSION = "1.22";
+ private static final String MAX_SUPPORTED_SCHEMA_VERSION = "1.26";
private static final List MAX_SUPPORTED_SCHEMA_VERSION_COMPONENTS = Arrays.stream(MAX_SUPPORTED_SCHEMA_VERSION.split("\\."))
.map(Integer::parseInt)
.collect(Collectors.toList());
@@ -380,6 +383,11 @@ public void shouldPassAllOutcomes(
}
compareLogMessages(rootContext, definition, tweaks);
}
+
+ if (definition.containsKey("expectTracingMessages")) {
+ compareTracingSpans(definition);
+ }
+
} catch (TestAbortedException e) {
// if a test is ignored, we do not retry
throw e;
@@ -487,6 +495,20 @@ private void compareLogMessages(final UnifiedTestContext rootContext, final Bson
}
}
+ private void compareTracingSpans(final BsonDocument definition) {
+ BsonDocument curTracingSpansForClient = definition.getDocument("expectTracingMessages");
+ String clientId = curTracingSpansForClient.getString("client").getValue();
+
+ // Get the tracer for the client
+ Tracer micrometerTracer = entities.getClientTracer(clientId);
+ SimpleTracer simpleTracer = (SimpleTracer) micrometerTracer;
+
+ SpanTree expectedSpans = SpanTree.from(curTracingSpansForClient.getArray("spans"));
+ SpanTree reportedSpans = SpanTree.from(simpleTracer.getSpans());
+ boolean ignoreExtraSpans = curTracingSpansForClient.getBoolean("ignoreExtraSpans", BsonBoolean.TRUE).getValue();
+ SpanTree.assertValid(reportedSpans, expectedSpans, rootContext.valueMatcher::assertValuesMatch, ignoreExtraSpans);
+ }
+
private void assertOutcome(final UnifiedTestContext context) {
for (BsonValue cur : definition.getArray("outcome")) {
BsonDocument curDocument = cur.asDocument();
From 26cc57a2bdc953bbeb4f5433cdf1a506aad51a91 Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Thu, 2 Oct 2025 14:17:28 +0100
Subject: [PATCH 07/14] Refactor the tracing implementation to use Observation
instead of working with micrometer-tracing directly
---
driver-core/build.gradle.kts | 2 +-
.../connection/InternalStreamConnection.java | 104 ++++++----
.../operation/ClientBulkWriteOperation.java | 4 +-
.../com/mongodb/internal/tracing/Span.java | 53 +++--
.../com/mongodb/internal/tracing/Tags.java | 49 -----
.../com/mongodb/internal/tracing/Tracer.java | 38 +---
.../internal/tracing/TracingManager.java | 41 ++--
.../internal/tracing/TransactionSpan.java | 2 +
.../com/mongodb/tracing/MicrometerTracer.java | 141 +++++++------
.../mongodb/tracing/MongodbObservation.java | 185 ++++++++++++++++++
.../com/mongodb/tracing/package-info.java | 2 +-
driver-kotlin-coroutine/build.gradle.kts | 2 +-
driver-kotlin-sync/build.gradle.kts | 2 +-
driver-reactive-streams/build.gradle.kts | 2 +-
driver-sync/build.gradle.kts | 5 +-
.../client/internal/MongoClusterImpl.java | 32 ++-
.../com/mongodb/client/tracing/SpanTree.java | 68 ++++++-
.../mongodb/client/tracing/ZipkinTracer.java | 95 ---------
.../com/mongodb/client/unified/Entities.java | 26 ++-
.../mongodb/client/unified/UnifiedTest.java | 29 +--
gradle/libs.versions.toml | 18 +-
21 files changed, 528 insertions(+), 372 deletions(-)
delete mode 100644 driver-core/src/main/com/mongodb/internal/tracing/Tags.java
create mode 100644 driver-core/src/main/com/mongodb/tracing/MongodbObservation.java
delete mode 100644 driver-sync/src/test/functional/com/mongodb/client/tracing/ZipkinTracer.java
diff --git a/driver-core/build.gradle.kts b/driver-core/build.gradle.kts
index 7e260b18d23..1614d9fb8d4 100644
--- a/driver-core/build.gradle.kts
+++ b/driver-core/build.gradle.kts
@@ -54,7 +54,7 @@ dependencies {
optionalImplementation(libs.snappy.java)
optionalImplementation(libs.zstd.jni)
- optionalImplementation(libs.micrometer)
+ optionalImplementation(libs.micrometer.observation)
testImplementation(project(path = ":bson", configuration = "testArtifacts"))
testImplementation(libs.reflections)
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 535c2fb2255..d4104462c8c 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
@@ -23,6 +23,7 @@
import com.mongodb.MongoException;
import com.mongodb.MongoInternalException;
import com.mongodb.MongoInterruptedException;
+import com.mongodb.MongoNamespace;
import com.mongodb.MongoOperationTimeoutException;
import com.mongodb.MongoSocketClosedException;
import com.mongodb.MongoSocketReadException;
@@ -55,6 +56,7 @@
import com.mongodb.internal.tracing.Span;
import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
+import io.micrometer.common.KeyValues;
import org.bson.BsonBinaryReader;
import org.bson.BsonDocument;
import org.bson.ByteBuf;
@@ -97,21 +99,22 @@
import static com.mongodb.internal.connection.ProtocolHelper.isCommandOk;
import static com.mongodb.internal.logging.LogMessage.Level.DEBUG;
import static com.mongodb.internal.thread.InterruptionUtil.translateInterruptedException;
-import static com.mongodb.internal.tracing.Tags.CLIENT_CONNECTION_ID;
-import static com.mongodb.internal.tracing.Tags.COLLECTION;
-import static com.mongodb.internal.tracing.Tags.COMMAND_NAME;
-import static com.mongodb.internal.tracing.Tags.CURSOR_ID;
-import static com.mongodb.internal.tracing.Tags.NAMESPACE;
-import static com.mongodb.internal.tracing.Tags.NETWORK_TRANSPORT;
-import static com.mongodb.internal.tracing.Tags.QUERY_SUMMARY;
-import static com.mongodb.internal.tracing.Tags.QUERY_TEXT;
-import static com.mongodb.internal.tracing.Tags.SERVER_ADDRESS;
-import static com.mongodb.internal.tracing.Tags.SERVER_CONNECTION_ID;
-import static com.mongodb.internal.tracing.Tags.SERVER_PORT;
-import static com.mongodb.internal.tracing.Tags.SERVER_TYPE;
-import static com.mongodb.internal.tracing.Tags.SESSION_ID;
-import static com.mongodb.internal.tracing.Tags.SYSTEM;
-import static com.mongodb.internal.tracing.Tags.TRANSACTION_NUMBER;
+import static com.mongodb.tracing.MongodbObservation.HighCardinalityKeyNames.QUERY_TEXT;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.CLIENT_CONNECTION_ID;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.COLLECTION;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.COMMAND_NAME;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.CURSOR_ID;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.NAMESPACE;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.NETWORK_TRANSPORT;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.QUERY_SUMMARY;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.RESPONSE_STATUS_CODE;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.SERVER_ADDRESS;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.SERVER_CONNECTION_ID;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.SERVER_PORT;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.SERVER_TYPE;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.SESSION_ID;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.SYSTEM;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.TRANSACTION_NUMBER;
import static java.util.Arrays.asList;
/**
@@ -473,7 +476,7 @@ private T sendAndReceiveInternal(final CommandMessage message, final Decoder
commandEventSender = new NoOpCommandEventSender();
}
if (isTracingCommandPayloadNeeded) {
- tracingSpan.tag(QUERY_TEXT, commandDocument.toJson());
+ tracingSpan.tagHighCardinality(QUERY_TEXT.withValue(commandDocument.toJson()));
}
try {
@@ -584,6 +587,9 @@ private T receiveCommandMessageResponse(final Decoder decoder, final Comm
commandEventSender.sendFailedEvent(e);
}
if (tracingSpan != null) {
+ if (e instanceof MongoCommandException) {
+ tracingSpan.tagLowCardinality(RESPONSE_STATUS_CODE.withValue(String.valueOf(((MongoCommandException) e).getErrorCode())));
+ }
tracingSpan.error(e);
}
throw e;
@@ -1055,43 +1061,75 @@ private Span createTracingSpan(final CommandMessage message, final OperationCont
Span operationSpan = operationContext.getTracingSpan();
Span span = tracingManager
- .addSpan("Command " + commandName, operationSpan != null ? operationSpan.context() : null)
- .tag(SYSTEM, "mongodb")
- .tag(NAMESPACE, message.getNamespace().getDatabaseName())
- .tag(COLLECTION, message.getCollectionName())
- .tag(QUERY_SUMMARY, commandName)
- .tag(COMMAND_NAME, commandName)
- .tag(NETWORK_TRANSPORT, getServerAddress() instanceof UnixServerAddress ? "unix" : "tcp");
+ .addSpan(commandName, operationSpan != null ? operationSpan.context() : null);
if (command.containsKey("getMore")) {
long cursorId = command.getInt64("getMore").longValue();
- span.tag(CURSOR_ID, cursorId);
+ span.tagLowCardinality(CURSOR_ID.withValue(String.valueOf(cursorId)));
if (operationSpan != null) {
- operationSpan.tag(CURSOR_ID, cursorId);
+ operationSpan.tagLowCardinality(CURSOR_ID.withValue(String.valueOf(cursorId)));
}
}
+ tagNamespace(span, operationSpan, message, commandName);
tagServerAndConnectionInfo(span, message);
tagSessionAndTransactionInfo(span, operationContext);
return span;
}
+ private void tagNamespace(final Span span, @Nullable final Span parentSpan, final CommandMessage message, final String commandName) {
+ String namespace;
+ String collection;
+ if (parentSpan != null) {
+ MongoNamespace parentNamespace = parentSpan.getNamespace();
+ if (parentNamespace != null) {
+ namespace = parentNamespace.getDatabaseName();
+ collection =
+ MongoNamespace.COMMAND_COLLECTION_NAME.equalsIgnoreCase(parentNamespace.getCollectionName()) ? ""
+ : parentNamespace.getCollectionName();
+ } else {
+ namespace = message.getNamespace().getDatabaseName();
+ collection = message.getCollectionName().contains("$cmd") ? "" : message.getCollectionName();
+ }
+ } else {
+ namespace = message.getNamespace().getDatabaseName();
+ collection = message.getCollectionName().contains("$cmd") ? "" : message.getCollectionName();
+ }
+ 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));
+
+ if (!collection.isEmpty()) {
+ keyValues = keyValues.and(COLLECTION.withValue(collection));
+ }
+ span.tagLowCardinality(keyValues);
+ }
+
private void tagServerAndConnectionInfo(final Span span, final CommandMessage message) {
- span.tag(SERVER_ADDRESS, serverId.getAddress().getHost())
- .tag(SERVER_PORT, String.valueOf(serverId.getAddress().getPort()))
- .tag(SERVER_TYPE, message.getSettings().getServerType().name())
- .tag(CLIENT_CONNECTION_ID, this.description.getConnectionId().toString())
- .tag(SERVER_CONNECTION_ID, String.valueOf(this.description.getConnectionId().getServerValue()));
+ span.tagLowCardinality(KeyValues.of(
+ SERVER_ADDRESS.withValue(serverId.getAddress().getHost()),
+ SERVER_PORT.withValue(String.valueOf(serverId.getAddress().getPort())),
+ SERVER_TYPE.withValue(message.getSettings().getServerType().name()),
+ CLIENT_CONNECTION_ID.withValue(String.valueOf(this.description.getConnectionId().getLocalValue())),
+ SERVER_CONNECTION_ID.withValue(String.valueOf(this.description.getConnectionId().getServerValue())),
+ NETWORK_TRANSPORT.withValue(getServerAddress() instanceof UnixServerAddress ? "unix" : "tcp")
+ ));
}
private void tagSessionAndTransactionInfo(final Span span, final OperationContext operationContext) {
SessionContext sessionContext = operationContext.getSessionContext();
if (sessionContext.hasSession() && !sessionContext.isImplicitSession()) {
- span.tag(TRANSACTION_NUMBER, String.valueOf(sessionContext.getTransactionNumber()))
- .tag(SESSION_ID, String.valueOf(sessionContext.getSessionId()
+ span.tagLowCardinality(KeyValues.of(
+ TRANSACTION_NUMBER.withValue(String.valueOf(sessionContext.getTransactionNumber())),
+ SESSION_ID.withValue(String.valueOf(sessionContext.getSessionId()
.get(sessionContext.getSessionId().getFirstKey())
- .asBinary().asUuid()));
+ .asBinary().asUuid()))
+ ));
}
}
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java
index ad380781f73..c94fcc04783 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java
@@ -115,6 +115,7 @@
import java.util.function.Supplier;
import java.util.stream.Stream;
+import static com.mongodb.MongoNamespace.COMMAND_COLLECTION_NAME;
import static com.mongodb.assertions.Assertions.assertFalse;
import static com.mongodb.assertions.Assertions.assertNotNull;
import static com.mongodb.assertions.Assertions.assertTrue;
@@ -184,7 +185,8 @@ public String getCommandName() {
@Override
public MongoNamespace getNamespace() {
- return getNamespacedModel(models, 0).getNamespace();
+ // The bulkWrite command is executed on the "admin" database.
+ return new MongoNamespace("admin", COMMAND_COLLECTION_NAME);
}
@Override
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Span.java b/driver-core/src/main/com/mongodb/internal/tracing/Span.java
index 41f6ef0afdf..0549a194c6c 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/Span.java
+++ b/driver-core/src/main/com/mongodb/internal/tracing/Span.java
@@ -16,6 +16,10 @@
package com.mongodb.internal.tracing;
+import com.mongodb.MongoNamespace;
+import com.mongodb.lang.Nullable;
+import io.micrometer.common.KeyValue;
+import io.micrometer.common.KeyValues;
/**
* Represents a tracing span for the driver internal operations.
@@ -32,7 +36,7 @@
*
*
*
- * @since 5.6
+ * @since 5.7
*/
public interface Span {
/**
@@ -44,13 +48,15 @@ public interface Span {
*/
Span EMPTY = new Span() {
@Override
- public Span tag(final String key, final String value) {
- return this;
+ public void tagLowCardinality(final KeyValue tag) {
}
@Override
- public Span tag(final String key, final Long value) {
- return this;
+ public void tagLowCardinality(final KeyValues keyValues) {
+ }
+
+ @Override
+ public void tagHighCardinality(final KeyValue keyValue) {
}
@Override
@@ -69,25 +75,34 @@ public void end() {
public TraceContext context() {
return TraceContext.EMPTY;
}
+
+ @Override
+ @Nullable
+ public MongoNamespace getNamespace() {
+ return null;
+ }
};
/**
- * Adds a tag to the span with a key-value pair.
+ * Adds a low-cardinality tag to the span.
+ *
+ * @param keyValue The key-value pair representing the tag.
+ */
+ void tagLowCardinality(KeyValue keyValue);
+
+ /**
+ * Adds multiple low-cardinality tags to the span.
*
- * @param key The tag key.
- * @param value The tag value.
- * @return The current instance of the span.
+ * @param keyValues The key-value pairs representing the tags.
*/
- Span tag(String key, String value);
+ void tagLowCardinality(KeyValues keyValues);
/**
- * Adds a tag to the span with a key and a numeric value.
+ * Adds a high-cardinality (highly variable values) tag to the span.
*
- * @param key The tag key.
- * @param value The numeric tag value.
- * @return The current instance of the span.
+ * @param keyValue The key-value pair representing the tag.
*/
- Span tag(String key, Long value);
+ void tagHighCardinality(KeyValue keyValue);
/**
* Records an event in the span.
@@ -114,4 +129,12 @@ public TraceContext context() {
* @return The trace context associated with the span.
*/
TraceContext context();
+
+ /**
+ * Retrieves the MongoDB namespace associated with the span, if any.
+ *
+ * @return The MongoDB namespace, or null if none is associated.
+ */
+ @Nullable
+ MongoNamespace getNamespace();
}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Tags.java b/driver-core/src/main/com/mongodb/internal/tracing/Tags.java
deleted file mode 100644
index c7261a63af4..00000000000
--- a/driver-core/src/main/com/mongodb/internal/tracing/Tags.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.tracing;
-
-/**
- * Contains constant tag names used for tracing and monitoring MongoDB operations.
- * These tags are typically used to annotate spans or events with relevant metadata.
- *
- * @since 5.6
- */
-public final class Tags {
- private Tags() {
- }
-
- public static final String SYSTEM = "db.system";
- public static final String NAMESPACE = "db.namespace";
- public static final String COLLECTION = "db.collection.name";
- public static final String OPERATION_NAME = "db.operation.name";
- public static final String COMMAND_NAME = "db.command.name";
- public static final String NETWORK_TRANSPORT = "network.transport";
- public static final String OPERATION_SUMMARY = "db.operation.summary";
- public static final String QUERY_SUMMARY = "db.query.summary";
- public static final String QUERY_TEXT = "db.query.text";
- public static final String CURSOR_ID = "db.mongodb.cursor_id";
- public static final String SERVER_ADDRESS = "server.address";
- public static final String SERVER_PORT = "server.port";
- public static final String SERVER_TYPE = "server.type";
- public static final String CLIENT_CONNECTION_ID = "db.mongodb.driver_connection_id";
- public static final String SERVER_CONNECTION_ID = "db.mongodb.server_connection_id";
- public static final String TRANSACTION_NUMBER = "db.mongodb.txnNumber";
- public static final String SESSION_ID = "db.mongodb.lsid";
- public static final String EXCEPTION_STACKTRACE = "exception.stacktrace";
- public static final String EXCEPTION_TYPE = "exception.type";
- public static final String EXCEPTION_MESSAGE = "exception.message";
-}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java b/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java
index c2881e9e2fb..6f5403213d6 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java
+++ b/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.mongodb.internal.tracing;
+import com.mongodb.MongoNamespace;
import com.mongodb.lang.Nullable;
/**
@@ -25,23 +25,12 @@
* It also includes a no-operation (NO_OP) implementation for cases where tracing is not required.
*
*
- * @since 5.6
+ * @since 5.7
*/
public interface Tracer {
Tracer NO_OP = new Tracer() {
-
- @Override
- public TraceContext currentContext() {
- return TraceContext.EMPTY;
- }
-
- @Override
- public Span nextSpan(final String name) {
- return Span.EMPTY;
- }
-
@Override
- public Span nextSpan(final String name, @Nullable final TraceContext parent) {
+ public Span nextSpan(final String name, @Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) {
return Span.EMPTY;
}
@@ -56,30 +45,15 @@ public boolean includeCommandPayload() {
}
};
- /**
- * Retrieves the current trace context from the Micrometer tracer.
- *
- * @return A {@link TraceContext} representing the underlying {@link io.micrometer.tracing.TraceContext}.
- * exists.
- */
- TraceContext currentContext();
-
- /**
- * Creates a new span with the specified name.
- *
- * @param name The name of the span.
- * @return A {@link Span} representing the newly created span.
- */
- Span nextSpan(String name); // uses current active span
-
/**
* Creates a new span with the specified name and optional parent trace context.
*
* @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); // manually attach the next span to the provided parent
+ Span nextSpan(String name, @Nullable TraceContext parent, @Nullable MongoNamespace namespace);
/**
* Indicates whether tracing is enabled.
@@ -93,5 +67,5 @@ public boolean includeCommandPayload() {
*
* @return {@code true} if command payloads are allowed, {@code false} otherwise.
*/
- boolean includeCommandPayload(); // whether the tracer allows command payloads in the trace context
+ boolean includeCommandPayload();
}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
index 6db92f0d75a..6fa5613ef47 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
+++ b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
@@ -16,9 +16,10 @@
package com.mongodb.internal.tracing;
+import com.mongodb.MongoNamespace;
import com.mongodb.lang.Nullable;
-import static com.mongodb.internal.tracing.Tags.SYSTEM;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.SYSTEM;
import static java.lang.System.getenv;
/**
@@ -34,30 +35,17 @@ public class TracingManager {
*/
public static final TracingManager NO_OP = new TracingManager(Tracer.NO_OP);
private static final String ENV_ALLOW_COMMAND_PAYLOAD = "MONGODB_TRACING_ALLOW_COMMAND_PAYLOAD";
-
private final Tracer tracer;
- private final TraceContext parentContext;
private final boolean enableCommandPayload;
- /**
- * Constructs a new TracingManager with the specified tracer.
- *
- * @param tracer The tracer to use for tracing operations.
- */
- public TracingManager(final Tracer tracer) {
- this(tracer, tracer.currentContext());
- }
-
/**
* Constructs a new TracingManager with the specified tracer and parent context.
* Setting the environment variable {@code MONGODB_TRACING_ALLOW_COMMAND_PAYLOAD} to "true" will enable command payload tracing.
*
* @param tracer The tracer to use for tracing operations.
- * @param parentContext The parent trace context.
*/
- public TracingManager(final Tracer tracer, final TraceContext parentContext) {
+ public TracingManager(final Tracer tracer) {
this.tracer = tracer;
- this.parentContext = parentContext;
String envAllowCommandPayload = getenv(ENV_ALLOW_COMMAND_PAYLOAD);
if (envAllowCommandPayload != null) {
this.enableCommandPayload = Boolean.parseBoolean(envAllowCommandPayload);
@@ -78,7 +66,24 @@ public TracingManager(final Tracer tracer, final TraceContext parentContext) {
* @return The created span.
*/
public Span addSpan(final String name, @Nullable final TraceContext parentContext) {
- return tracer.nextSpan(name, parentContext);
+ return tracer.nextSpan(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).
+ *
+ *
+ * @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);
}
/**
@@ -87,8 +92,8 @@ public Span addSpan(final String name, @Nullable final TraceContext parentContex
* @return The created transaction span.
*/
public Span addTransactionSpan() {
- Span span = tracer.nextSpan("transaction", parentContext);
- span.tag(SYSTEM, "mongodb");
+ Span span = tracer.nextSpan("transaction", null, null);
+ span.tagLowCardinality(SYSTEM.withValue("mongodb"));
return span;
}
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java b/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java
index d975f6931e0..789f259e82c 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java
+++ b/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java
@@ -20,6 +20,8 @@
/**
* State class for transaction tracing.
+ *
+ * @since 5.7
*/
public class TransactionSpan {
private boolean isConvenientTransaction = false;
diff --git a/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java b/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
index 7ed0119d21d..ae5a708ef96 100644
--- a/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
+++ b/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
@@ -16,73 +16,71 @@
package com.mongodb.tracing;
+import com.mongodb.MongoNamespace;
import com.mongodb.internal.tracing.Span;
import com.mongodb.internal.tracing.TraceContext;
import com.mongodb.internal.tracing.Tracer;
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 java.io.PrintWriter;
import java.io.StringWriter;
-import static com.mongodb.internal.tracing.Tags.EXCEPTION_MESSAGE;
-import static com.mongodb.internal.tracing.Tags.EXCEPTION_STACKTRACE;
-import static com.mongodb.internal.tracing.Tags.EXCEPTION_TYPE;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_MESSAGE;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_STACKTRACE;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_TYPE;
+import static com.mongodb.tracing.MongodbObservation.MONGODB_OBSERVATION;
+
/**
- * A {@link Tracer} implementation that delegates tracing operations to a Micrometer {@link io.micrometer.tracing.Tracer}.
+ * A {@link Tracer} implementation that delegates tracing operations to a Micrometer {@link io.micrometer.observation.ObservationRegistry}.
*
* This class enables integration of MongoDB driver tracing with Micrometer-based tracing systems.
- * It provides methods to create and manage spans using the Micrometer tracing API.
+ * It provides integration with Micrometer to propagate observations into tracing API.
*
*
- * @since 5.6
+ * @since 5.7
*/
public class MicrometerTracer implements Tracer {
- private final io.micrometer.tracing.Tracer tracer;
private final boolean allowCommandPayload;
+ private final ObservationRegistry observationRegistry;
/**
* Constructs a new {@link MicrometerTracer} instance.
*
- * @param tracer The Micrometer {@link io.micrometer.tracing.Tracer} to delegate tracing operations to.
+ * @param observationRegistry The Micrometer {@link ObservationRegistry} to delegate tracing operations to.
*/
- public MicrometerTracer(final io.micrometer.tracing.Tracer tracer) {
- this(tracer, false);
+ public MicrometerTracer(final ObservationRegistry observationRegistry) {
+ this(observationRegistry, false);
}
/**
* Constructs a new {@link MicrometerTracer} instance with an option to allow command payloads.
*
- * @param tracer The Micrometer {@link io.micrometer.tracing.Tracer} to delegate tracing operations to.
+ * @param observationRegistry The Micrometer {@link ObservationRegistry} to delegate tracing operations to.
* @param allowCommandPayload Whether to allow command payloads in the trace context.
*/
- public MicrometerTracer(final io.micrometer.tracing.Tracer tracer, final boolean allowCommandPayload) {
- this.tracer = tracer;
+ public MicrometerTracer(final ObservationRegistry observationRegistry, final boolean allowCommandPayload) {
this.allowCommandPayload = allowCommandPayload;
+ this.observationRegistry = observationRegistry;
}
@Override
- public TraceContext currentContext() {
- return new MicrometerTraceContext(tracer.currentTraceContext().context());
- }
-
- @Override
- public Span nextSpan(final String name) {
- return new MicrometerSpan(tracer.nextSpan().name(name).start());
- }
-
- @Override
- public Span nextSpan(final String name, @Nullable final TraceContext parent) {
+ public Span nextSpan(final String name, @Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) {
if (parent instanceof MicrometerTraceContext) {
- io.micrometer.tracing.TraceContext micrometerContext = ((MicrometerTraceContext) parent).getTraceContext();
- if (micrometerContext != null) {
- return new MicrometerSpan(tracer.spanBuilder()
- .name(name)
- .setParent(micrometerContext)
- .start());
+ Observation parentObservation = ((MicrometerTraceContext) parent).observation;
+ if (parentObservation != null) {
+ return new MicrometerSpan(MONGODB_OBSERVATION
+ .observation(observationRegistry)
+ .contextualName(name)
+ .parentObservation(parentObservation)
+ .start(), namespace);
}
}
- return nextSpan(name);
+ return new MicrometerSpan(MONGODB_OBSERVATION.observation(observationRegistry).contextualName(name).start(), namespace);
}
@Override
@@ -99,25 +97,15 @@ public boolean includeCommandPayload() {
* Represents a Micrometer-based trace context.
*/
private static class MicrometerTraceContext implements TraceContext {
- private final io.micrometer.tracing.TraceContext traceContext;
+ private final Observation observation;
/**
- * Constructs a new {@link MicrometerTraceContext} instance.
+ * Constructs a new {@link MicrometerTraceContext} instance with an associated Observation.
*
- * @param traceContext The Micrometer {@link io.micrometer.tracing.TraceContext}, or null if none exists.
+ * @param observation The Micrometer {@link Observation}, or null if none exists.
*/
- MicrometerTraceContext(@Nullable final io.micrometer.tracing.TraceContext traceContext) {
- this.traceContext = traceContext;
- }
-
- /**
- * Retrieves the underlying Micrometer trace context.
- *
- * @return The Micrometer {@link io.micrometer.tracing.TraceContext}, or null if none exists.
- */
- @Nullable
- public io.micrometer.tracing.TraceContext getTraceContext() {
- return traceContext;
+ MicrometerTraceContext(@Nullable final Observation observation) {
+ this.observation = observation;
}
}
@@ -125,50 +113,75 @@ public io.micrometer.tracing.TraceContext getTraceContext() {
* Represents a Micrometer-based span.
*/
private static class MicrometerSpan implements Span {
- private final io.micrometer.tracing.Span span;
+ private final Observation observation;
+ @Nullable
+ private final MongoNamespace namespace;
/**
- * Constructs a new {@link MicrometerSpan} instance.
+ * Constructs a new {@link MicrometerSpan} instance with an associated Observation.
*
- * @param span The Micrometer {@link io.micrometer.tracing.Span} to delegate operations to.
+ * @param observation The Micrometer {@link Observation}, or null if none exists.
*/
- MicrometerSpan(final io.micrometer.tracing.Span span) {
- this.span = span;
+ MicrometerSpan(final Observation observation) {
+ this.observation = observation;
+ this.namespace = null;
+ }
+
+ /**
+ * 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.
+ */
+ MicrometerSpan(final Observation observation, @Nullable final MongoNamespace namespace) {
+ this.namespace = namespace;
+ this.observation = observation;
}
@Override
- public Span tag(final String key, final String value) {
- span.tag(key, value);
- return this;
+ public void tagLowCardinality(final KeyValue keyValue) {
+ observation.lowCardinalityKeyValue(keyValue);
}
@Override
- public Span tag(final String key, final Long value) {
- span.tag(key, value);
- return this;
+ public void tagLowCardinality(final KeyValues keyValues) {
+ observation.lowCardinalityKeyValues(keyValues);
+ }
+
+ @Override
+ public void tagHighCardinality(final KeyValue keyValue) {
+ observation.highCardinalityKeyValue(keyValue);
}
@Override
public void event(final String event) {
- span.event(event);
+ observation.event(() -> event);
}
@Override
public void error(final Throwable throwable) {
- span.tag(EXCEPTION_MESSAGE, throwable.getMessage());
- span.tag(EXCEPTION_TYPE, throwable.getClass().getName());
- span.tag(EXCEPTION_STACKTRACE, getStackTraceAsString(throwable));
- span.error(throwable);
+ observation.lowCardinalityKeyValues(KeyValues.of(
+ EXCEPTION_MESSAGE.withValue(throwable.getMessage()),
+ EXCEPTION_TYPE.withValue(throwable.getClass().getName()),
+ EXCEPTION_STACKTRACE.withValue(getStackTraceAsString(throwable))
+ ));
+ observation.error(throwable);
}
@Override
public void end() {
- span.end();
+ observation.stop();
}
@Override
public TraceContext context() {
- return new MicrometerTraceContext(span.context());
+ return new MicrometerTraceContext(observation);
+ }
+
+ @Override
+ @Nullable
+ public MongoNamespace getNamespace() {
+ return namespace;
}
private String getStackTraceAsString(final Throwable throwable) {
diff --git a/driver-core/src/main/com/mongodb/tracing/MongodbObservation.java b/driver-core/src/main/com/mongodb/tracing/MongodbObservation.java
new file mode 100644
index 00000000000..9b173e11ac9
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/tracing/MongodbObservation.java
@@ -0,0 +1,185 @@
+/*
+ * 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.tracing;
+
+import io.micrometer.common.docs.KeyName;
+import io.micrometer.observation.docs.ObservationDocumentation;
+
+/**
+ * A MongoDB-based {@link io.micrometer.observation.Observation}.
+ *
+ * @since 5.7
+ */
+public enum MongodbObservation implements ObservationDocumentation {
+
+ MONGODB_OBSERVATION {
+ @Override
+ public String getName() {
+ return "mongodb";
+ }
+
+ @Override
+ public KeyName[] getLowCardinalityKeyNames() {
+ return LowCardinalityKeyNames.values();
+ }
+
+ @Override
+ public KeyName[] getHighCardinalityKeyNames() {
+ return HighCardinalityKeyNames.values();
+ }
+
+ };
+
+ /**
+ * Enums related to low cardinality key names for MongoDB tags.
+ */
+ public enum LowCardinalityKeyNames implements KeyName {
+
+ SYSTEM {
+ @Override
+ public String asString() {
+ return "db.system";
+ }
+ },
+ NAMESPACE {
+ @Override
+ public String asString() {
+ return "db.namespace";
+ }
+ },
+ COLLECTION {
+ @Override
+ public String asString() {
+ return "db.collection.name";
+ }
+ },
+ OPERATION_NAME {
+ @Override
+ public String asString() {
+ return "db.operation.name";
+ }
+ },
+ COMMAND_NAME {
+ @Override
+ public String asString() {
+ return "db.command.name";
+ }
+ },
+ NETWORK_TRANSPORT {
+ @Override
+ public String asString() {
+ return "network.transport";
+ }
+ },
+ OPERATION_SUMMARY {
+ @Override
+ public String asString() {
+ return "db.operation.summary";
+ }
+ },
+ QUERY_SUMMARY {
+ @Override
+ public String asString() {
+ return "db.query.summary";
+ }
+ },
+ CURSOR_ID {
+ @Override
+ public String asString() {
+ return "db.mongodb.cursor_id";
+ }
+ },
+ SERVER_ADDRESS {
+ @Override
+ public String asString() {
+ return "server.address";
+ }
+ },
+ SERVER_PORT {
+ @Override
+ public String asString() {
+ return "server.port";
+ }
+ },
+ SERVER_TYPE {
+ @Override
+ public String asString() {
+ return "server.type";
+ }
+ },
+ CLIENT_CONNECTION_ID {
+ @Override
+ public String asString() {
+ return "db.mongodb.driver_connection_id";
+ }
+ },
+ SERVER_CONNECTION_ID {
+ @Override
+ public String asString() {
+ return "db.mongodb.server_connection_id";
+ }
+ },
+ TRANSACTION_NUMBER {
+ @Override
+ public String asString() {
+ return "db.mongodb.txn_number";
+ }
+ },
+ SESSION_ID {
+ @Override
+ public String asString() {
+ return "db.mongodb.lsid";
+ }
+ },
+ EXCEPTION_STACKTRACE {
+ @Override
+ public String asString() {
+ return "exception.stacktrace";
+ }
+ },
+ EXCEPTION_TYPE {
+ @Override
+ 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 {
+ @Override
+ public String asString() {
+ return "db.query.text";
+ }
+ }
+ }
+}
diff --git a/driver-core/src/main/com/mongodb/tracing/package-info.java b/driver-core/src/main/com/mongodb/tracing/package-info.java
index 43e82603a09..247576d1537 100644
--- a/driver-core/src/main/com/mongodb/tracing/package-info.java
+++ b/driver-core/src/main/com/mongodb/tracing/package-info.java
@@ -17,7 +17,7 @@
/**
* This package defines the API for MongoDB driver tracing.
*
- * @since 5.6
+ * @since 5.7
*/
@NonNullApi
package com.mongodb.tracing;
diff --git a/driver-kotlin-coroutine/build.gradle.kts b/driver-kotlin-coroutine/build.gradle.kts
index dd127a4dd6a..49e94bf7e2c 100644
--- a/driver-kotlin-coroutine/build.gradle.kts
+++ b/driver-kotlin-coroutine/build.gradle.kts
@@ -38,7 +38,7 @@ dependencies {
integrationTestImplementation(project(path = ":bson", configuration = "testArtifacts"))
integrationTestImplementation(project(path = ":driver-sync", configuration = "testArtifacts"))
integrationTestImplementation(project(path = ":driver-core", configuration = "testArtifacts"))
- integrationTestImplementation(libs.micrometer)
+ integrationTestImplementation(libs.micrometer.observation)
}
configureMavenPublication {
diff --git a/driver-kotlin-sync/build.gradle.kts b/driver-kotlin-sync/build.gradle.kts
index b6113200628..1a510220351 100644
--- a/driver-kotlin-sync/build.gradle.kts
+++ b/driver-kotlin-sync/build.gradle.kts
@@ -32,7 +32,7 @@ dependencies {
integrationTestImplementation(project(path = ":bson", configuration = "testArtifacts"))
integrationTestImplementation(project(path = ":driver-sync", configuration = "testArtifacts"))
integrationTestImplementation(project(path = ":driver-core", configuration = "testArtifacts"))
- integrationTestImplementation(libs.micrometer)
+ integrationTestImplementation(libs.micrometer.observation)
}
configureMavenPublication {
diff --git a/driver-reactive-streams/build.gradle.kts b/driver-reactive-streams/build.gradle.kts
index f9ac5301625..12c461b3257 100644
--- a/driver-reactive-streams/build.gradle.kts
+++ b/driver-reactive-streams/build.gradle.kts
@@ -46,7 +46,7 @@ dependencies {
testImplementation(libs.reactive.streams.tck)
// Tracing
- testImplementation(libs.micrometer)
+ testImplementation(libs.micrometer.observation)
}
configureMavenPublication {
diff --git a/driver-sync/build.gradle.kts b/driver-sync/build.gradle.kts
index b37d0226295..1bed5ed69c0 100644
--- a/driver-sync/build.gradle.kts
+++ b/driver-sync/build.gradle.kts
@@ -35,12 +35,13 @@ dependencies {
testImplementation(project(path = ":bson", configuration = "testArtifacts"))
testImplementation(project(path = ":driver-core", configuration = "testArtifacts"))
+ optionalImplementation(libs.micrometer.observation)
// lambda testing
testImplementation(libs.aws.lambda.core)
- // Tracing
- testImplementation(libs.bundles.micrometer.test)
+ // Tracing testing
+ testImplementation(libs.micrometer.tracing.integration.test) { exclude(group = "org.junit.jupiter") }
}
configureMavenPublication {
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 db8fd03adaf..54f476e8a35 100644
--- a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java
+++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java
@@ -60,11 +60,11 @@
import com.mongodb.internal.operation.WriteOperation;
import com.mongodb.internal.session.ServerSessionPool;
import com.mongodb.internal.tracing.Span;
-import com.mongodb.internal.tracing.Tags;
import com.mongodb.internal.tracing.TraceContext;
import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.internal.tracing.TransactionSpan;
import com.mongodb.lang.Nullable;
+import io.micrometer.common.KeyValues;
import org.bson.BsonDocument;
import org.bson.Document;
import org.bson.UuidRepresentation;
@@ -78,11 +78,17 @@
import static com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL;
import static com.mongodb.MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL;
+import static com.mongodb.MongoNamespace.COMMAND_COLLECTION_NAME;
import static com.mongodb.ReadPreference.primary;
import static com.mongodb.assertions.Assertions.isTrue;
import static com.mongodb.assertions.Assertions.isTrueArgument;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.internal.TimeoutContext.createTimeoutContext;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.COLLECTION;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.NAMESPACE;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.OPERATION_NAME;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.OPERATION_SUMMARY;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.SYSTEM;
final class MongoClusterImpl implements MongoCluster {
@Nullable
@@ -601,17 +607,27 @@ private Span createOperationSpan(final ClientSession actualClientSession, final
if (transactionSpan != null) {
parentContext = transactionSpan.getContext();
}
- String name = commandName + " " + namespace.getFullName();
+ String name = commandName + " " + namespace.getDatabaseName() + (COMMAND_COLLECTION_NAME.equalsIgnoreCase(namespace.getCollectionName())
+ ? ""
+ : "." + namespace.getCollectionName());
+
+ KeyValues keyValues = KeyValues.of(
+ SYSTEM.withValue("mongodb"),
+ NAMESPACE.withValue(namespace.getDatabaseName()));
+ if (!COMMAND_COLLECTION_NAME.equalsIgnoreCase(namespace.getCollectionName())) {
+ keyValues = keyValues.and(COLLECTION.withValue(namespace.getCollectionName()));
+ }
+ keyValues = keyValues.and(OPERATION_NAME.withValue(commandName),
+ OPERATION_SUMMARY.withValue(name));
+
Span span = binding
.getOperationContext()
.getTracingManager()
- .addSpan(name, parentContext);
+ .addSpan(name, parentContext, namespace);
+
+ span.tagLowCardinality(keyValues);
+
binding.getOperationContext().setTracingSpan(span);
- span.tag(Tags.SYSTEM, "mongodb");
- span.tag(Tags.NAMESPACE, namespace.getDatabaseName());
- span.tag(Tags.COLLECTION, namespace.getCollectionName());
- span.tag(Tags.OPERATION_NAME, commandName);
- span.tag(Tags.OPERATION_SUMMARY, name);
return span;
} else {
diff --git a/driver-sync/src/test/functional/com/mongodb/client/tracing/SpanTree.java b/driver-sync/src/test/functional/com/mongodb/client/tracing/SpanTree.java
index bca04e6647a..c5ec573ad76 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/tracing/SpanTree.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/tracing/SpanTree.java
@@ -16,12 +16,13 @@
package com.mongodb.client.tracing;
-import com.mongodb.internal.tracing.Tags;
import com.mongodb.lang.Nullable;
+import io.micrometer.tracing.exporter.FinishedSpan;
import io.micrometer.tracing.test.simple.SimpleSpan;
import org.bson.BsonArray;
import org.bson.BsonBinary;
import org.bson.BsonDocument;
+import org.bson.BsonInt64;
import org.bson.BsonString;
import org.bson.BsonValue;
@@ -35,6 +36,12 @@
import java.util.UUID;
import java.util.function.BiConsumer;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.CLIENT_CONNECTION_ID;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.CURSOR_ID;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.SERVER_CONNECTION_ID;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.SERVER_PORT;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.SESSION_ID;
+import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.TRANSACTION_NUMBER;
import static org.bson.assertions.Assertions.notNull;
import static org.junit.jupiter.api.Assertions.fail;
@@ -60,8 +67,8 @@ public static SpanTree from(final BsonArray spans) {
final SpanNode rootNode = new SpanNode(name);
spanTree.roots.add(rootNode);
- if (spanDoc.containsKey("tags")) {
- rootNode.tags = spanDoc.getDocument("tags");
+ if (spanDoc.containsKey("attributes")) {
+ rootNode.tags = spanDoc.getDocument("attributes");
}
if (spanDoc.containsKey("nested")) {
@@ -101,8 +108,15 @@ public static SpanTree from(final Deque spans) {
for (final Map.Entry tag : span.getTags().entrySet()) {
// handle special case of session id (needs to be parsed into a BsonBinary)
// this is needed because the SimpleTracer reports all the collected tags as strings
- if (tag.getKey().equals(Tags.SESSION_ID)) {
+ if (tag.getKey().equals(SESSION_ID.asString())) {
spanNode.tags.append(tag.getKey(), new BsonDocument().append("id", new BsonBinary(UUID.fromString(tag.getValue()))));
+
+ } else if (tag.getKey().equals(CURSOR_ID.asString())
+ || tag.getKey().equals(SERVER_PORT.asString())
+ || tag.getKey().equals(TRANSACTION_NUMBER.asString())
+ || tag.getKey().equals(CLIENT_CONNECTION_ID.asString())
+ || tag.getKey().equals(SERVER_CONNECTION_ID.asString())) {
+ spanNode.tags.append(tag.getKey(), new BsonInt64(Long.parseLong(tag.getValue())));
} else {
spanNode.tags.append(tag.getKey(), new BsonString(tag.getValue()));
}
@@ -123,6 +137,43 @@ public static SpanTree from(final Deque spans) {
return spanTree;
}
+ public static SpanTree from(final List spans) {
+ final SpanTree spanTree = new SpanTree();
+ final Map idToSpanNode = new HashMap<>();
+ for (final FinishedSpan span : spans) {
+ final SpanNode spanNode = new SpanNode(span.getName());
+ for (final Map.Entry tag : span.getTags().entrySet()) {
+ // handle special case of session id (needs to be parsed into a BsonBinary)
+ // this is needed because the SimpleTracer reports all the collected tags as strings
+ if (tag.getKey().equals(SESSION_ID.asString())) {
+ spanNode.tags.append(tag.getKey(), new BsonDocument().append("id", new BsonBinary(UUID.fromString(tag.getValue()))));
+
+ } else if (tag.getKey().equals(CURSOR_ID.asString())
+ || tag.getKey().equals(SERVER_PORT.asString())
+ || tag.getKey().equals(TRANSACTION_NUMBER.asString())
+ || tag.getKey().equals(CLIENT_CONNECTION_ID.asString())
+ || tag.getKey().equals(SERVER_CONNECTION_ID.asString())) {
+ spanNode.tags.append(tag.getKey(), new BsonInt64(Long.parseLong(tag.getValue())));
+ } else {
+ spanNode.tags.append(tag.getKey(), new BsonString(tag.getValue()));
+ }
+ }
+ idToSpanNode.put(span.getSpanId(), spanNode);
+ }
+
+ for (final FinishedSpan span : spans) {
+ final String parentId = span.getParentId();
+ final SpanNode node = idToSpanNode.get(span.getSpanId());
+
+ if (parentId != null && !parentId.isEmpty() && idToSpanNode.containsKey(parentId)) {
+ idToSpanNode.get(parentId).children.add(node);
+ } else { // doesn't have a parent, so it is a root node
+ spanTree.roots.add(node);
+ }
+ }
+ return spanTree;
+ }
+
/**
* Adds nested spans to the parent node based on the provided BsonDocument.
* This method recursively adds child spans to the parent span node.
@@ -134,8 +185,8 @@ private static void addNestedSpans(final SpanNode parentNode, final BsonDocument
final String name = nestedSpan.getString("name").getValue();
final SpanNode childNode = new SpanNode(name, parentNode);
- if (nestedSpan.containsKey("tags")) {
- childNode.tags = nestedSpan.getDocument("tags");
+ if (nestedSpan.containsKey("attributes")) {
+ childNode.tags = nestedSpan.getDocument("attributes");
}
if (nestedSpan.containsKey("nested")) {
@@ -159,8 +210,9 @@ public static void assertValid(final SpanTree reportedSpans, final SpanTree expe
final boolean ignoreExtraSpans) {
if (ignoreExtraSpans) {
// remove from the reported spans all the nodes that are not expected
- reportedSpans.roots.removeIf(node -> !expectedSpans.roots.contains(node));
-
+ reportedSpans.roots.removeIf(node ->
+ expectedSpans.roots.stream().noneMatch(expectedNode -> expectedNode.getName().equalsIgnoreCase(node.getName()))
+ );
}
// check that we have the same root spans
diff --git a/driver-sync/src/test/functional/com/mongodb/client/tracing/ZipkinTracer.java b/driver-sync/src/test/functional/com/mongodb/client/tracing/ZipkinTracer.java
deleted file mode 100644
index 186835c6ca2..00000000000
--- a/driver-sync/src/test/functional/com/mongodb/client/tracing/ZipkinTracer.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.client.tracing;
-
-import io.micrometer.tracing.Tracer;
-import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext;
-import io.micrometer.tracing.otel.bridge.OtelTracer;
-import io.opentelemetry.api.OpenTelemetry;
-import io.opentelemetry.api.common.Attributes;
-import io.opentelemetry.context.propagation.ContextPropagators;
-import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
-import io.opentelemetry.extension.trace.propagation.B3Propagator;
-import io.opentelemetry.sdk.OpenTelemetrySdk;
-import io.opentelemetry.sdk.resources.Resource;
-import io.opentelemetry.sdk.trace.SdkTracerProvider;
-import io.opentelemetry.sdk.trace.SpanProcessor;
-import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
-import io.opentelemetry.semconv.ResourceAttributes;
-
-/**
- * A utility class to create a Zipkin tracer using OpenTelemetry protocol, useful for visualizing spans in Zipkin UI
- * This tracer can be used to send spans to a Zipkin server.
- *
- * Spans are visible in the Zipkin UI at http://localhost:9411.
- *
- * To Start Zipkin server, you can use the following command:
- *
{@code
- * docker run -d -p 9411:9411 openzipkin/zipkin
- * }
- */
-public final class ZipkinTracer {
- private static final String ENDPOINT = "http://localhost:9411/api/v2/spans";
-
- private ZipkinTracer() {
- }
-
- /**
- * Creates a Zipkin tracer with the specified service name.
- *
- * @param serviceName the name of the service to be used in the tracer
- * @return a Tracer instance configured to send spans to Zipkin
- */
- public static Tracer getTracer(final String serviceName) {
- ZipkinSpanExporter zipkinExporter = ZipkinSpanExporter.builder()
- .setEndpoint(ENDPOINT)
- .build();
-
- Resource resource = Resource.getDefault()
- .merge(Resource.create(
- Attributes.of(
- ResourceAttributes.SERVICE_NAME, serviceName,
- ResourceAttributes.SERVICE_VERSION, "1.0.0"
- )
- ));
-
- SpanProcessor spanProcessor = SimpleSpanProcessor.create(zipkinExporter);
-
- SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
- .addSpanProcessor(spanProcessor)
- .setResource(resource)
- .build();
-
- OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
- .setTracerProvider(tracerProvider)
- .setPropagators(ContextPropagators.create(
- B3Propagator.injectingSingleHeader()
- ))
- .build();
-
- io.opentelemetry.api.trace.Tracer otelTracer = openTelemetry.getTracer("my-java-service", "1.0.0");
-
- OtelCurrentTraceContext otelCurrentTraceContext = new OtelCurrentTraceContext();
-
- return new OtelTracer(
- otelTracer,
- otelCurrentTraceContext,
- null // EventPublisher can be null for basic usage
- );
- }
-
-}
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 c0d33b06d91..bff1951c16b 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
@@ -67,8 +67,8 @@
import com.mongodb.lang.Nullable;
import com.mongodb.logging.TestLoggingInterceptor;
import com.mongodb.tracing.MicrometerTracer;
-import io.micrometer.tracing.Tracer;
-import io.micrometer.tracing.test.simple.SimpleTracer;
+import io.micrometer.observation.ObservationRegistry;
+import io.micrometer.tracing.test.reporter.inmemory.InMemoryOtelSetup;
import org.bson.BsonArray;
import org.bson.BsonBoolean;
import org.bson.BsonDocument;
@@ -129,7 +129,8 @@ public final class Entities {
private final Map clientEncryptions = new HashMap<>();
private final Map clientCommandListeners = new HashMap<>();
private final Map clientLoggingInterceptors = new HashMap<>();
- private final Map clientTracing = new HashMap<>();
+ private final Map clientTracing = new HashMap<>();
+ private final Set inMemoryOTelInstances = new HashSet<>();
private final Map clientConnectionPoolListeners = new HashMap<>();
private final Map clientServerListeners = new HashMap<>();
private final Map clientClusterListeners = new HashMap<>();
@@ -298,7 +299,7 @@ public TestLoggingInterceptor getClientLoggingInterceptor(final String id) {
return getEntity(id + "-logging-interceptor", clientLoggingInterceptors, "logging interceptor");
}
- public Tracer getClientTracer(final String id) {
+ public InMemoryOtelSetup.Builder.OtelBuildingBlocks getClientTracer(final String id) {
return getEntity(id + "-tracing", clientTracing, "micrometer tracing");
}
@@ -615,17 +616,13 @@ private void initClient(final BsonDocument entity, final String id,
if (entity.containsKey("observeTracingMessages")) {
boolean enableCommandPayload = entity.getDocument("observeTracingMessages").get("enableCommandPayload", BsonBoolean.FALSE).asBoolean().getValue();
- /* To enable Zipkin backend, uncomment the following lines and ensure you have the server started
- (docker run -d -p 9411:9411 openzipkin/zipkin). The tests will fail but the captured spans will be
- visible in the Zipkin UI at http://localhost:9411 for debugging purpose.
- *
- * Tracer tracer = ZipkinTracer.getTracer("UTR");
- * putEntity(id + "-tracing", new SimpleTracer(), clientTracing);
- */
- Tracer tracer = new SimpleTracer();
- putEntity(id + "-tracing", tracer, clientTracing);
+ ObservationRegistry observationRegistry = ObservationRegistry.create();
+ InMemoryOtelSetup inMemoryOtel = InMemoryOtelSetup.builder().register(observationRegistry);
+ InMemoryOtelSetup.Builder.OtelBuildingBlocks tracer = inMemoryOtel.getBuildingBlocks();
- clientSettingsBuilder.tracer(new MicrometerTracer(tracer, enableCommandPayload));
+ putEntity(id + "-tracing", tracer, clientTracing);
+ inMemoryOTelInstances.add(inMemoryOtel);
+ clientSettingsBuilder.tracer(new MicrometerTracer(observationRegistry, enableCommandPayload));
}
MongoClientSettings clientSettings = clientSettingsBuilder.build();
@@ -818,6 +815,7 @@ public void close() {
clients.values().forEach(MongoClient::close);
clientLoggingInterceptors.values().forEach(TestLoggingInterceptor::close);
threads.values().forEach(ExecutorService::shutdownNow);
+ inMemoryOTelInstances.forEach(InMemoryOtelSetup::close);
}
private static class EntityCommandListener implements CommandListener {
diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java
index 8e91e85205b..9effc2a2e72 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java
@@ -45,8 +45,7 @@
import com.mongodb.lang.Nullable;
import com.mongodb.logging.TestLoggingInterceptor;
import com.mongodb.test.AfterBeforeParameterResolver;
-import io.micrometer.tracing.Tracer;
-import io.micrometer.tracing.test.simple.SimpleTracer;
+import io.micrometer.tracing.test.reporter.inmemory.InMemoryOtelSetup;
import org.bson.BsonArray;
import org.bson.BsonBoolean;
import org.bson.BsonDocument;
@@ -114,7 +113,7 @@ public abstract class UnifiedTest {
private static final Set PRESTART_POOL_ASYNC_WORK_MANAGER_FILE_DESCRIPTIONS = Collections.singleton(
"wait queue timeout errors include details about checked out connections");
- private static final String MAX_SUPPORTED_SCHEMA_VERSION = "1.26";
+ private static final String MAX_SUPPORTED_SCHEMA_VERSION = "1.27";
private static final List MAX_SUPPORTED_SCHEMA_VERSION_COMPONENTS = Arrays.stream(MAX_SUPPORTED_SCHEMA_VERSION.split("\\."))
.map(Integer::parseInt)
.collect(Collectors.toList());
@@ -496,17 +495,19 @@ private void compareLogMessages(final UnifiedTestContext rootContext, final Bson
}
private void compareTracingSpans(final BsonDocument definition) {
- BsonDocument curTracingSpansForClient = definition.getDocument("expectTracingMessages");
- String clientId = curTracingSpansForClient.getString("client").getValue();
-
- // Get the tracer for the client
- Tracer micrometerTracer = entities.getClientTracer(clientId);
- SimpleTracer simpleTracer = (SimpleTracer) micrometerTracer;
-
- SpanTree expectedSpans = SpanTree.from(curTracingSpansForClient.getArray("spans"));
- SpanTree reportedSpans = SpanTree.from(simpleTracer.getSpans());
- boolean ignoreExtraSpans = curTracingSpansForClient.getBoolean("ignoreExtraSpans", BsonBoolean.TRUE).getValue();
- SpanTree.assertValid(reportedSpans, expectedSpans, rootContext.valueMatcher::assertValuesMatch, ignoreExtraSpans);
+ BsonArray curTracingSpansForClients = definition.getArray("expectTracingMessages");
+ for (BsonValue tracingSpan : curTracingSpansForClients) {
+ BsonDocument curTracingSpansForClient = tracingSpan.asDocument();
+ String clientId = curTracingSpansForClient.getString("client").getValue();
+
+ // Get the tracer for the client
+ InMemoryOtelSetup.Builder.OtelBuildingBlocks micrometerTracer = entities.getClientTracer(clientId);
+
+ SpanTree expectedSpans = SpanTree.from(curTracingSpansForClient.getArray("spans"));
+ SpanTree reportedSpans = SpanTree.from(micrometerTracer.getFinishedSpans());
+ boolean ignoreExtraSpans = curTracingSpansForClient.getBoolean("ignoreExtraSpans", BsonBoolean.TRUE).getValue();
+ SpanTree.assertValid(reportedSpans, expectedSpans, rootContext.valueMatcher::assertValuesMatch, ignoreExtraSpans);
+ }
}
private void assertOutcome(final UnifiedTestContext context) {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 38805b02353..057e3bc5430 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -24,9 +24,8 @@ reactive-streams = "1.0.4"
snappy = "1.1.10.3"
zstd = "1.5.5-3"
jetbrains-annotations = "26.0.2"
-micrometer = "1.4.5"
-zipkin-reporter = "2.16.3"
-opentelemetry-exporter-zipkin = "1.30.0"
+micrometer = "1.6.0-M3" # This version has a fix for https://github.com/micrometer-metrics/tracing/issues/1092
+micrometer-observation = "1.15.4"
kotlin = "1.8.10"
kotlinx-coroutines-bom = "1.6.4"
@@ -96,7 +95,7 @@ reactive-streams = { module = " org.reactivestreams:reactive-streams", version.r
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
snappy-java = { module = "org.xerial.snappy:snappy-java", version.ref = "snappy" }
zstd-jni = { module = "com.github.luben:zstd-jni", version.ref = "zstd" }
-micrometer = { module = "io.micrometer:micrometer-tracing", version.ref = "micrometer" }
+micrometer-observation = { module = "io.micrometer:micrometer-observation", version.ref = "micrometer-observation" }
graal-sdk = { module = "org.graalvm.sdk:graal-sdk", version.ref = "graal-sdk" }
graal-sdk-nativeimage = { module = "org.graalvm.sdk:nativeimage", version.ref = "graal-sdk" }
@@ -175,13 +174,7 @@ objenesis = { module = "org.objenesis:objenesis", version.ref = "objenesis" }
project-reactor-test = { module = "io.projectreactor:reactor-test" }
reactive-streams-tck = { module = " org.reactivestreams:reactive-streams-tck", version.ref = "reactive-streams" }
reflections = { module = "org.reflections:reflections", version.ref = "reflections" }
-
-micrometer-tracing-test = { module = " io.micrometer:micrometer-tracing-test", version.ref = "micrometer" }
-micrometer-tracing-bridge-brave = { module = " io.micrometer:micrometer-tracing-bridge-brave", version.ref = "micrometer" }
-micrometer-tracing = { module = " io.micrometer:micrometer-tracing", version.ref = "micrometer" }
-micrometer-tracing-bridge-otel = { module = " io.micrometer:micrometer-tracing-bridge-otel", version.ref = "micrometer" }
-zipkin-reporter = { module = " io.zipkin.reporter2:zipkin-reporter", version.ref = "zipkin-reporter" }
-opentelemetry-exporter-zipkin = { module = " io.opentelemetry:opentelemetry-exporter-zipkin", version.ref = "opentelemetry-exporter-zipkin" }
+micrometer-tracing-integration-test = { module = " io.micrometer:micrometer-tracing-integration-test", version.ref = "micrometer" }
[bundles]
aws-java-sdk-v1 = ["aws-java-sdk-v1-core", "aws-java-sdk-v1-sts"]
@@ -208,9 +201,6 @@ scala-test-v2-v12 = ["scala-test-flatspec-v2-v12", "scala-test-shouldmatchers-v2
scala-test-v2-v11 = ["scala-test-flatspec-v2-v11", "scala-test-shouldmatchers-v2-v11", "scala-test-mockito-v2-v11",
"scala-test-junit-runner-v2-v11", "reflections"]
-micrometer-test = ["micrometer-tracing-test", "micrometer-tracing-bridge-brave", "micrometer-tracing-bridge-otel",
- "micrometer-tracing", "zipkin-reporter", "opentelemetry-exporter-zipkin"]
-
[plugins]
kotlin-gradle = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
bnd = { id = "biz.aQute.bnd.builder", version.ref = "plugin-bnd" }
From 45f3cb89e095736abdcf5a25f57f81448ac3e64c Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Mon, 6 Oct 2025 15:18:55 +0100
Subject: [PATCH 08/14] Added Prose Tests
---
.../main/com/mongodb/MongoClientSettings.java | 12 +-
.../connection/InternalStreamConnection.java | 2 +-
.../com/mongodb/internal/tracing/Tracer.java | 4 +
.../internal/tracing/TracingManager.java | 16 +-
.../mongodb/tracing/MongodbObservation.java | 32 ++++
driver-sync/build.gradle.kts | 6 +
.../client/tracing/MicrometerProseTest.java | 163 ++++++++++++++++++
7 files changed, 225 insertions(+), 10 deletions(-)
create mode 100644 driver-sync/src/test/functional/com/mongodb/client/tracing/MicrometerProseTest.java
diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java
index 938b9c76068..500f402b405 100644
--- a/driver-core/src/main/com/mongodb/MongoClientSettings.java
+++ b/driver-core/src/main/com/mongodb/MongoClientSettings.java
@@ -58,6 +58,7 @@
import static com.mongodb.assertions.Assertions.isTrueArgument;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.internal.TimeoutSettings.convertAndValidateTimeout;
+import static java.lang.System.getenv;
import static java.util.Arrays.asList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
@@ -88,6 +89,7 @@ public final class MongoClientSettings {
new ExpressionCodecProvider(),
new Jep395RecordCodecProvider(),
new KotlinCodecProvider()));
+ private static final String ENV_OTEL_ENABLED = "OTEL_JAVA_INSTRUMENTATION_MONGODB_ENABLED";
private final ReadPreference readPreference;
private final WriteConcern writeConcern;
@@ -1184,6 +1186,14 @@ private MongoClientSettings(final Builder builder) {
heartbeatConnectTimeoutSetExplicitly = builder.heartbeatConnectTimeoutMS != 0;
contextProvider = builder.contextProvider;
timeoutMS = builder.timeoutMS;
- tracer = (builder.tracer == null) ? Tracer.NO_OP : builder.tracer;
+
+ String envOtelInstrumentationEnabled = getenv(ENV_OTEL_ENABLED);
+ boolean enableTracing = true;
+ if (envOtelInstrumentationEnabled != null) {
+ enableTracing = Boolean.parseBoolean(envOtelInstrumentationEnabled);
+ }
+ tracer = (builder.tracer == null) ? Tracer.NO_OP
+ : (enableTracing) ? builder.tracer
+ : Tracer.NO_OP;
}
}
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 d4104462c8c..0953cf68b73 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
@@ -476,7 +476,7 @@ private T sendAndReceiveInternal(final CommandMessage message, final Decoder
commandEventSender = new NoOpCommandEventSender();
}
if (isTracingCommandPayloadNeeded) {
- tracingSpan.tagHighCardinality(QUERY_TEXT.withValue(commandDocument.toJson()));
+ tracingSpan.tagHighCardinality(QUERY_TEXT.withBson(commandDocument));
}
try {
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java b/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java
index 6f5403213d6..3bdaa4f6293 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java
+++ b/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java
@@ -25,6 +25,10 @@
* It also includes a no-operation (NO_OP) implementation for cases where tracing is not required.
*
*
+ * Note: You can use the environment variable {@code OTEL_JAVA_INSTRUMENTATION_MONGODB_ENABLED} to override the behaviour of enabling/disabling
+ * tracing before you create the {@link com.mongodb.MongoClientSettings} instance.
+ * You can also use the environment variable {@code OTEL_JAVA_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH} to enable or disable command payload when tracing is enabled. .
+ *
* @since 5.7
*/
public interface Tracer {
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
index 6fa5613ef47..e6e6ba57433 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
+++ b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
@@ -34,23 +34,23 @@ public class TracingManager {
* A no-op instance of the TracingManager used when tracing is disabled.
*/
public static final TracingManager NO_OP = new TracingManager(Tracer.NO_OP);
- private static final String ENV_ALLOW_COMMAND_PAYLOAD = "MONGODB_TRACING_ALLOW_COMMAND_PAYLOAD";
+ private static final String ENV_OTEL_QUERY_TEXT_MAX_LENGTH = "OTEL_JAVA_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH";
private final Tracer tracer;
- private final boolean enableCommandPayload;
+ private final int queryTextMaxLength;
/**
* Constructs a new TracingManager with the specified tracer and parent context.
- * Setting the environment variable {@code MONGODB_TRACING_ALLOW_COMMAND_PAYLOAD} to "true" will enable command payload tracing.
+ * Setting the environment variable {@code OTEL_JAVA_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH} will enable command payload tracing.
*
* @param tracer The tracer to use for tracing operations.
*/
public TracingManager(final Tracer tracer) {
this.tracer = tracer;
- String envAllowCommandPayload = getenv(ENV_ALLOW_COMMAND_PAYLOAD);
- if (envAllowCommandPayload != null) {
- this.enableCommandPayload = Boolean.parseBoolean(envAllowCommandPayload);
+ String queryTextMaxLength = getenv(ENV_OTEL_QUERY_TEXT_MAX_LENGTH);
+ if (queryTextMaxLength != null) {
+ this.queryTextMaxLength = Integer.parseInt(queryTextMaxLength);
} else {
- this.enableCommandPayload = tracer.includeCommandPayload();
+ this.queryTextMaxLength = tracer.includeCommandPayload() ? Integer.MAX_VALUE : 0;
}
}
@@ -112,6 +112,6 @@ public boolean isEnabled() {
* @return True if command payload tracing is enabled, false otherwise.
*/
public boolean isCommandPayloadEnabled() {
- return enableCommandPayload;
+ return queryTextMaxLength > 0;
}
}
diff --git a/driver-core/src/main/com/mongodb/tracing/MongodbObservation.java b/driver-core/src/main/com/mongodb/tracing/MongodbObservation.java
index 9b173e11ac9..5a4f24ebc48 100644
--- a/driver-core/src/main/com/mongodb/tracing/MongodbObservation.java
+++ b/driver-core/src/main/com/mongodb/tracing/MongodbObservation.java
@@ -16,8 +16,16 @@
package com.mongodb.tracing;
+import io.micrometer.common.KeyValue;
import io.micrometer.common.docs.KeyName;
import io.micrometer.observation.docs.ObservationDocumentation;
+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.StringWriter;
/**
* A MongoDB-based {@link io.micrometer.observation.Observation}.
@@ -180,6 +188,30 @@ public enum HighCardinalityKeyNames implements KeyName {
public String asString() {
return "db.query.text";
}
+ };
+
+ public KeyValue withBson(final BsonDocument commandDocument) {
+ return KeyValue.of(asString(), getTruncatedJsonCommand(commandDocument));
+ }
+
+ private String getTruncatedJsonCommand(final BsonDocument commandDocument) {
+ StringWriter writer = new StringWriter();
+
+ try (BsonReader bsonReader = commandDocument.asBsonReader()) {
+ JsonWriter jsonWriter = new JsonWriter(writer,
+ JsonWriterSettings.builder().outputMode(JsonMode.RELAXED)
+ .maxLength(42)
+ .build());
+
+ jsonWriter.pipe(bsonReader);
+
+ if (jsonWriter.isTruncated()) {
+ writer.append(" ...");
+ }
+
+ return writer.toString();
+ }
}
+
}
}
diff --git a/driver-sync/build.gradle.kts b/driver-sync/build.gradle.kts
index 1bed5ed69c0..ab9bc5c01f4 100644
--- a/driver-sync/build.gradle.kts
+++ b/driver-sync/build.gradle.kts
@@ -44,6 +44,12 @@ dependencies {
testImplementation(libs.micrometer.tracing.integration.test) { exclude(group = "org.junit.jupiter") }
}
+tasks.withType {
+ // Needed for MicrometerProseTest to set env variable programmatically (calls
+ // `field.setAccessible(true)`)
+ jvmArgs("--add-opens=java.base/java.util=ALL-UNNAMED")
+}
+
configureMavenPublication {
pom {
name.set("MongoDB Driver")
diff --git a/driver-sync/src/test/functional/com/mongodb/client/tracing/MicrometerProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/tracing/MicrometerProseTest.java
new file mode 100644
index 00000000000..257f49a49b8
--- /dev/null
+++ b/driver-sync/src/test/functional/com/mongodb/client/tracing/MicrometerProseTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.client.tracing;
+
+import com.mongodb.MongoClientSettings;
+import com.mongodb.client.Fixture;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.tracing.MicrometerTracer;
+import io.micrometer.observation.ObservationRegistry;
+import io.micrometer.tracing.test.reporter.inmemory.InMemoryOtelSetup;
+import org.bson.Document;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+import static com.mongodb.ClusterFixture.getDefaultDatabaseName;
+import static com.mongodb.tracing.MongodbObservation.HighCardinalityKeyNames.QUERY_TEXT;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Implementation of the prose tests for Micrometer OpenTelemetry tracing.
+ */
+public class MicrometerProseTest {
+ private ObservationRegistry observationRegistry = ObservationRegistry.create();
+ private InMemoryOtelSetup memoryOtelSetup;
+ private InMemoryOtelSetup.Builder.OtelBuildingBlocks inMemoryOtel;
+
+ @BeforeEach
+ void setUp() {
+ memoryOtelSetup = InMemoryOtelSetup.builder().register(observationRegistry);
+ inMemoryOtel = memoryOtelSetup.getBuildingBlocks();
+ }
+
+ @AfterEach
+ void tearDown() {
+ memoryOtelSetup.close();
+ }
+
+ // Test 1: Tracing Enable/Disable via Environment Variable
+ @Test
+ void testControlOtelInstrumentationViaEnvironmentVariable() throws Exception {
+ MicrometerTracer tracer = new MicrometerTracer(observationRegistry);
+ setEnv("OTEL_JAVA_INSTRUMENTATION_MONGODB_ENABLED", "false");
+
+ MongoClientSettings clientSettings = Fixture.getMongoClientSettingsBuilder()
+ .tracer(tracer).build();
+
+ try (MongoClient client = MongoClients.create(clientSettings)) {
+ MongoDatabase database = client.getDatabase(getDefaultDatabaseName());
+ MongoCollection collection = database.getCollection("test");
+ collection.find().first();
+
+ // Assert that no OpenTelemetry tracing spans are emitted for the operation.
+ assertTrue(inMemoryOtel.getFinishedSpans().isEmpty(), "Spans should not be emitted when instrumentation is enabled.");
+ }
+
+ setEnv("OTEL_JAVA_INSTRUMENTATION_MONGODB_ENABLED", "true");
+ clientSettings = Fixture.getMongoClientSettingsBuilder()
+ .tracer(tracer).build();
+ try (MongoClient client = MongoClients.create(clientSettings)) {
+ MongoDatabase database = client.getDatabase(getDefaultDatabaseName());
+ MongoCollection collection = database.getCollection("test");
+ collection.find().first();
+
+ // Assert that OpenTelemetry tracing spans are emitted for the operation.
+ assertEquals(2, inMemoryOtel.getFinishedSpans().size(), "Spans should be emitted when instrumentation is disabled.");
+ assertEquals("find", inMemoryOtel.getFinishedSpans().get(0).getName());
+ assertEquals("find " + getDefaultDatabaseName() + ".test", inMemoryOtel.getFinishedSpans().get(1).getName());
+ }
+ }
+
+ @Test
+ void testControlCommandPayloadViaEnvironmentVariable() throws Exception {
+ MicrometerTracer tracer = new MicrometerTracer(observationRegistry); // don't enable command payload by default
+ setEnv("OTEL_JAVA_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH", "42");
+
+ MongoClientSettings clientSettings = Fixture.getMongoClientSettingsBuilder()
+ .tracer(tracer).build();
+
+ try (MongoClient client = MongoClients.create(clientSettings)) {
+ MongoDatabase database = client.getDatabase(getDefaultDatabaseName());
+ MongoCollection collection = database.getCollection("test");
+ collection.find().first();
+
+ // Assert that the emitted tracing span includes the db.query.text attribute.
+ assertEquals(2, inMemoryOtel.getFinishedSpans().size(), "Spans should be emitted when instrumentation is disabled.");
+ assertEquals("find", inMemoryOtel.getFinishedSpans().get(0).getName());
+
+ Map.Entry queryTag = inMemoryOtel.getFinishedSpans().get(0).getTags().entrySet()
+ .stream()
+ .filter(entry -> entry.getKey().equals(QUERY_TEXT.asString()))
+ .findFirst()
+ .orElseThrow(() -> new AssertionError("Attribute " + QUERY_TEXT.asString() + " not found."));
+ assertEquals(46, queryTag.getValue().length(), "Query text length should be 46."); // 42 truncated string + " ..."
+ } finally {
+ memoryOtelSetup.close();
+ }
+
+ memoryOtelSetup = InMemoryOtelSetup.builder().register(observationRegistry);
+ inMemoryOtel = memoryOtelSetup.getBuildingBlocks();
+ tracer = new MicrometerTracer(observationRegistry); // don't enable command payload by default
+ setEnv("OTEL_JAVA_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH", null); // Unset the environment variable
+
+ clientSettings = Fixture.getMongoClientSettingsBuilder()
+ .tracer(tracer).build();
+ try (MongoClient client = MongoClients.create(clientSettings)) {
+ MongoDatabase database = client.getDatabase(getDefaultDatabaseName());
+ MongoCollection collection = database.getCollection("test");
+ collection.find().first();
+
+ // Assert no query.text tag is emitted
+ assertTrue(
+ inMemoryOtel.getFinishedSpans().get(0).getTags().entrySet().stream()
+ .noneMatch(entry -> entry.getKey().equals(QUERY_TEXT.asString())),
+ "Tag " + QUERY_TEXT.asString() + " should not exist."
+ );
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void setEnv(String key, String value) throws Exception {
+ // Get the unmodifiable Map from System.getenv()
+ Map env = System.getenv();
+
+ // Use reflection to get the class of the unmodifiable map
+ Class> unmodifiableMapClass = env.getClass();
+
+ // Get the 'm' field which holds the actual modifiable map
+ Field mField = unmodifiableMapClass.getDeclaredField("m");
+ mField.setAccessible(true);
+
+ // Get the modifiable map from the 'm' field
+ Map modifiableEnv = (Map) mField.get(env);
+
+ // Modify the map
+ if (value == null) {
+ modifiableEnv.remove(key);
+ } else {
+ modifiableEnv.put(key, value);
+ }
+ }
+}
From 39483e4a0038deae96eac3a6a466fbdadc322aef Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Mon, 6 Oct 2025 23:30:56 +0100
Subject: [PATCH 09/14] Fixing test dependencies
---
driver-kotlin-coroutine/build.gradle.kts | 2 +-
driver-kotlin-sync/build.gradle.kts | 2 +-
driver-reactive-streams/build.gradle.kts | 2 +-
.../com/mongodb/client/tracing/MicrometerProseTest.java | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/driver-kotlin-coroutine/build.gradle.kts b/driver-kotlin-coroutine/build.gradle.kts
index 49e94bf7e2c..8880c67aad8 100644
--- a/driver-kotlin-coroutine/build.gradle.kts
+++ b/driver-kotlin-coroutine/build.gradle.kts
@@ -38,7 +38,7 @@ dependencies {
integrationTestImplementation(project(path = ":bson", configuration = "testArtifacts"))
integrationTestImplementation(project(path = ":driver-sync", configuration = "testArtifacts"))
integrationTestImplementation(project(path = ":driver-core", configuration = "testArtifacts"))
- integrationTestImplementation(libs.micrometer.observation)
+ testImplementation(libs.micrometer.tracing.integration.test) { exclude(group = "org.junit.jupiter") }
}
configureMavenPublication {
diff --git a/driver-kotlin-sync/build.gradle.kts b/driver-kotlin-sync/build.gradle.kts
index 1a510220351..74f9b37c219 100644
--- a/driver-kotlin-sync/build.gradle.kts
+++ b/driver-kotlin-sync/build.gradle.kts
@@ -32,7 +32,7 @@ dependencies {
integrationTestImplementation(project(path = ":bson", configuration = "testArtifacts"))
integrationTestImplementation(project(path = ":driver-sync", configuration = "testArtifacts"))
integrationTestImplementation(project(path = ":driver-core", configuration = "testArtifacts"))
- integrationTestImplementation(libs.micrometer.observation)
+ testImplementation(libs.micrometer.tracing.integration.test) { exclude(group = "org.junit.jupiter") }
}
configureMavenPublication {
diff --git a/driver-reactive-streams/build.gradle.kts b/driver-reactive-streams/build.gradle.kts
index 12c461b3257..8f4454d4f4c 100644
--- a/driver-reactive-streams/build.gradle.kts
+++ b/driver-reactive-streams/build.gradle.kts
@@ -46,7 +46,7 @@ dependencies {
testImplementation(libs.reactive.streams.tck)
// Tracing
- testImplementation(libs.micrometer.observation)
+ testImplementation(libs.micrometer.tracing.integration.test) { exclude(group = "org.junit.jupiter") }
}
configureMavenPublication {
diff --git a/driver-sync/src/test/functional/com/mongodb/client/tracing/MicrometerProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/tracing/MicrometerProseTest.java
index 257f49a49b8..72cee301d6b 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/tracing/MicrometerProseTest.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/tracing/MicrometerProseTest.java
@@ -139,7 +139,7 @@ void testControlCommandPayloadViaEnvironmentVariable() throws Exception {
}
@SuppressWarnings("unchecked")
- private void setEnv(String key, String value) throws Exception {
+ private void setEnv(final String key, final String value) throws Exception {
// Get the unmodifiable Map from System.getenv()
Map env = System.getenv();
From 0ecfb195bc76eec664c35adac04ddcde06151cc5 Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Wed, 8 Oct 2025 11:33:05 +0100
Subject: [PATCH 10/14] Skipping non-compliant tests
---
.../internal/connection/CommandHelperSpecification.groovy | 2 ++
.../com/mongodb/client/unified/UnifiedTestModifications.java | 4 ++++
2 files changed, 6 insertions(+)
diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy
index 83ce94f7075..ecc9d3f64a5 100644
--- a/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy
+++ b/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy
@@ -25,6 +25,7 @@ import com.mongodb.connection.SocketSettings
import com.mongodb.internal.connection.netty.NettyStreamFactory
import org.bson.BsonDocument
import org.bson.BsonInt32
+import spock.lang.Ignore
import spock.lang.Specification
import java.util.concurrent.CountDownLatch
@@ -54,6 +55,7 @@ class CommandHelperSpecification extends Specification {
connection?.close()
}
+ @Ignore("5982")
def 'should execute command asynchronously'() {
when:
BsonDocument receivedDocument = null
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 f658b7c5e01..8f746e28f0c 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
@@ -80,6 +80,10 @@ public static void applyCustomizations(final TestDef def) {
.test("client-side-operations-timeout", "WaitQueueTimeoutError does not clear the pool",
"WaitQueueTimeoutError does not clear the pool");
+ // There are more than 44 tests using 'awaitMinPoolSizeMS' this will be fixed in JAVA-5957
+ def.skipJira("https://jira.mongodb.org/browse/JAVA-5957")
+ .directory("client-side-operations-timeout");
+
// TODO-JAVA-5712
// collection-management
From fb1f1afaa01c7f5e4ee600f052715fdbae16ff27 Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Wed, 8 Oct 2025 14:51:15 +0100
Subject: [PATCH 11/14] Fixing test deps for Scala
---
.../test/kotlin/com/mongodb/kotlin/client/MongoIterableTest.kt | 3 ++-
driver-scala/build.gradle.kts | 3 +++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoIterableTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoIterableTest.kt
index ab16dd08b24..17bcc3c1a12 100644
--- a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoIterableTest.kt
+++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoIterableTest.kt
@@ -19,6 +19,7 @@ import com.mongodb.Function
import com.mongodb.client.MongoCursor as JMongoCursor
import com.mongodb.client.MongoIterable as JMongoIterable
import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
import org.bson.Document
import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatchers
@@ -90,7 +91,7 @@ class MongoIterableTest {
whenever(cursor.next()).thenReturn(documents[0], documents[1], documents[2])
whenever(delegate.cursor()).doReturn(cursor)
- assertContentEquals(documents.subList(0, 2), iterable.use { it.take(2) }.toList())
+ iterable.use { it.take(2).forEachIndexed { index, document -> assertEquals(documents[index], document) } }
verify(delegate, times(1)).cursor()
verify(cursor, times(2)).hasNext()
diff --git a/driver-scala/build.gradle.kts b/driver-scala/build.gradle.kts
index 68187889629..c1e0365829b 100644
--- a/driver-scala/build.gradle.kts
+++ b/driver-scala/build.gradle.kts
@@ -36,6 +36,9 @@ dependencies {
// Encryption testing
integrationTestImplementation(project(path = ":mongodb-crypt", configuration = "default"))
+
+ // Tracing
+ testImplementation(libs.micrometer.tracing.integration.test) { exclude(group = "org.junit.jupiter") }
}
configureMavenPublication {
From 0628386af4508d749b018674181ce19e049f0c8f Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Wed, 8 Oct 2025 17:07:45 +0100
Subject: [PATCH 12/14] Refactored some type visibility per PR feedback
---
driver-core/src/main/com/mongodb/MongoClientSettings.java | 6 +++---
.../internal/connection/InternalStreamConnection.java | 2 +-
.../com/mongodb/internal/connection/OperationContext.java | 2 +-
.../main/com/mongodb/internal/tracing/TracingManager.java | 3 +++
.../main/com/mongodb/internal/tracing/TransactionSpan.java | 2 ++
.../src/main/com/mongodb/tracing/MicrometerTracer.java | 3 ---
.../src/main/com/mongodb/{internal => }/tracing/Span.java | 2 +-
.../com/mongodb/{internal => }/tracing/TraceContext.java | 2 +-
.../src/main/com/mongodb/{internal => }/tracing/Tracer.java | 2 +-
driver-sync/src/main/com/mongodb/client/ClientSession.java | 2 +-
.../main/com/mongodb/client/internal/MongoClusterImpl.java | 4 ++--
.../com/mongodb/client/tracing/MicrometerProseTest.java | 2 +-
12 files changed, 17 insertions(+), 15 deletions(-)
rename driver-core/src/main/com/mongodb/{internal => }/tracing/Span.java (99%)
rename driver-core/src/main/com/mongodb/{internal => }/tracing/TraceContext.java (95%)
rename driver-core/src/main/com/mongodb/{internal => }/tracing/Tracer.java (98%)
diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java
index 2f51dd4527e..69cb8233690 100644
--- a/driver-core/src/main/com/mongodb/MongoClientSettings.java
+++ b/driver-core/src/main/com/mongodb/MongoClientSettings.java
@@ -30,7 +30,7 @@
import com.mongodb.connection.SslSettings;
import com.mongodb.connection.TransportSettings;
import com.mongodb.event.CommandListener;
-import com.mongodb.internal.tracing.Tracer;
+import com.mongodb.tracing.Tracer;
import com.mongodb.lang.Nullable;
import com.mongodb.spi.dns.DnsClient;
import com.mongodb.spi.dns.InetAddressResolver;
@@ -736,7 +736,7 @@ Builder heartbeatSocketTimeoutMS(final int heartbeatSocketTimeoutMS) {
* @param tracer the tracer
* @see com.mongodb.tracing.MicrometerTracer
* @return this
- * @since 5.6
+ * @since 5.7
*/
@Alpha(Reason.CLIENT)
public Builder tracer(final Tracer tracer) {
@@ -1065,7 +1065,7 @@ public ContextProvider getContextProvider() {
* Get the tracer to create Spans for operations, commands and transactions.
*
* @return the configured Tracer
- * @since 5.6
+ * @since 5.7
*/
public Tracer getTracer() {
return tracer;
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 ae37acf249a..f53ed538dab 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java
@@ -53,7 +53,7 @@
import com.mongodb.internal.logging.StructuredLogger;
import com.mongodb.internal.session.SessionContext;
import com.mongodb.internal.time.Timeout;
-import com.mongodb.internal.tracing.Span;
+import com.mongodb.tracing.Span;
import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import io.micrometer.common.KeyValues;
diff --git a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
index bc4a785d545..b9da8bfc145 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java
@@ -27,7 +27,7 @@
import com.mongodb.internal.TimeoutSettings;
import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.session.SessionContext;
-import com.mongodb.internal.tracing.Span;
+import com.mongodb.tracing.Span;
import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.lang.Nullable;
import com.mongodb.selector.ServerSelector;
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
index e3f1f247a48..84cf91decd8 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
+++ b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java
@@ -18,6 +18,9 @@
import com.mongodb.MongoNamespace;
import com.mongodb.lang.Nullable;
+import com.mongodb.tracing.Span;
+import com.mongodb.tracing.TraceContext;
+import com.mongodb.tracing.Tracer;
import static com.mongodb.tracing.MongodbObservation.LowCardinalityKeyNames.SYSTEM;
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java b/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java
index 789f259e82c..d3133a8238b 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java
+++ b/driver-core/src/main/com/mongodb/internal/tracing/TransactionSpan.java
@@ -17,6 +17,8 @@
package com.mongodb.internal.tracing;
import com.mongodb.lang.Nullable;
+import com.mongodb.tracing.Span;
+import com.mongodb.tracing.TraceContext;
/**
* State class for transaction tracing.
diff --git a/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java b/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
index ae5a708ef96..30d81351ef3 100644
--- a/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
+++ b/driver-core/src/main/com/mongodb/tracing/MicrometerTracer.java
@@ -17,9 +17,6 @@
package com.mongodb.tracing;
import com.mongodb.MongoNamespace;
-import com.mongodb.internal.tracing.Span;
-import com.mongodb.internal.tracing.TraceContext;
-import com.mongodb.internal.tracing.Tracer;
import com.mongodb.lang.Nullable;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Span.java b/driver-core/src/main/com/mongodb/tracing/Span.java
similarity index 99%
rename from driver-core/src/main/com/mongodb/internal/tracing/Span.java
rename to driver-core/src/main/com/mongodb/tracing/Span.java
index 0549a194c6c..25efc1d42c0 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/Span.java
+++ b/driver-core/src/main/com/mongodb/tracing/Span.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.mongodb.internal.tracing;
+package com.mongodb.tracing;
import com.mongodb.MongoNamespace;
import com.mongodb.lang.Nullable;
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/TraceContext.java b/driver-core/src/main/com/mongodb/tracing/TraceContext.java
similarity index 95%
rename from driver-core/src/main/com/mongodb/internal/tracing/TraceContext.java
rename to driver-core/src/main/com/mongodb/tracing/TraceContext.java
index cb2f6ef1020..06763f96111 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/TraceContext.java
+++ b/driver-core/src/main/com/mongodb/tracing/TraceContext.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.mongodb.internal.tracing;
+package com.mongodb.tracing;
@SuppressWarnings("InterfaceIsType")
public interface TraceContext {
diff --git a/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java b/driver-core/src/main/com/mongodb/tracing/Tracer.java
similarity index 98%
rename from driver-core/src/main/com/mongodb/internal/tracing/Tracer.java
rename to driver-core/src/main/com/mongodb/tracing/Tracer.java
index 3bdaa4f6293..cc88e82b5ac 100644
--- a/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java
+++ b/driver-core/src/main/com/mongodb/tracing/Tracer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.mongodb.internal.tracing;
+package com.mongodb.tracing;
import com.mongodb.MongoNamespace;
import com.mongodb.lang.Nullable;
diff --git a/driver-sync/src/main/com/mongodb/client/ClientSession.java b/driver-sync/src/main/com/mongodb/client/ClientSession.java
index 6af2b4be664..abf0ff33fbd 100644
--- a/driver-sync/src/main/com/mongodb/client/ClientSession.java
+++ b/driver-sync/src/main/com/mongodb/client/ClientSession.java
@@ -131,7 +131,7 @@ public interface ClientSession extends com.mongodb.session.ClientSession {
* Get the transaction span (if started).
*
* @return the transaction span
- * @since 5.6
+ * @since 5.7
*/
@Nullable
TransactionSpan getTransactionSpan();
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 54f476e8a35..0907ed951b2 100644
--- a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java
+++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java
@@ -59,8 +59,8 @@
import com.mongodb.internal.operation.ReadOperation;
import com.mongodb.internal.operation.WriteOperation;
import com.mongodb.internal.session.ServerSessionPool;
-import com.mongodb.internal.tracing.Span;
-import com.mongodb.internal.tracing.TraceContext;
+import com.mongodb.tracing.Span;
+import com.mongodb.tracing.TraceContext;
import com.mongodb.internal.tracing.TracingManager;
import com.mongodb.internal.tracing.TransactionSpan;
import com.mongodb.lang.Nullable;
diff --git a/driver-sync/src/test/functional/com/mongodb/client/tracing/MicrometerProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/tracing/MicrometerProseTest.java
index 72cee301d6b..5b0947ac3eb 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/tracing/MicrometerProseTest.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/tracing/MicrometerProseTest.java
@@ -92,7 +92,7 @@ void testControlOtelInstrumentationViaEnvironmentVariable() throws Exception {
@Test
void testControlCommandPayloadViaEnvironmentVariable() throws Exception {
- MicrometerTracer tracer = new MicrometerTracer(observationRegistry); // don't enable command payload by default
+ MicrometerTracer tracer = new MicrometerTracer(observationRegistry, true);
setEnv("OTEL_JAVA_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH", "42");
MongoClientSettings clientSettings = Fixture.getMongoClientSettingsBuilder()
From d3f2d62cf3efecc79b6316045f4c741289924783 Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Wed, 8 Oct 2025 22:15:07 +0100
Subject: [PATCH 13/14] Fixing javadoc & Scala test
---
driver-core/src/main/com/mongodb/tracing/Span.java | 1 -
.../scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala | 5 ++++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/driver-core/src/main/com/mongodb/tracing/Span.java b/driver-core/src/main/com/mongodb/tracing/Span.java
index 25efc1d42c0..b1b9ed30c49 100644
--- a/driver-core/src/main/com/mongodb/tracing/Span.java
+++ b/driver-core/src/main/com/mongodb/tracing/Span.java
@@ -34,7 +34,6 @@
* Operation Spans: Trace higher-level operations, which may include multiple commands or internal steps.
* Transaction Spans: Trace the lifecycle of a transaction, including all operations and commands within it.
*
- *
*
* @since 5.7
*/
diff --git a/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala b/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala
index 4e93a331776..a15229411ae 100644
--- a/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala
+++ b/driver-scala/src/test/scala/org/mongodb/scala/ApiAliasAndCompanionSpec.scala
@@ -95,7 +95,10 @@ class ApiAliasAndCompanionSpec extends BaseSpec {
"BaseClientUpdateOptions",
"BaseClientDeleteOptions",
"MongoBaseInterfaceAssertions",
- "MicrometerTracer"
+ "MicrometerTracer",
+ "TraceContext",
+ "Span",
+ "Tracer"
)
val scalaExclusions = Set(
"BuildInfo",
From c4901548930db81e3bcc581b5a3a09aef318d966 Mon Sep 17 00:00:00 2001
From: Nabil Hachicha
Date: Thu, 9 Oct 2025 09:51:34 +0100
Subject: [PATCH 14/14] using JVM option behind a flag
---
buildSrc/src/main/kotlin/project/Companion.kt | 2 +-
driver-sync/build.gradle.kts | 6 +++++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/buildSrc/src/main/kotlin/project/Companion.kt b/buildSrc/src/main/kotlin/project/Companion.kt
index b4b9650031a..04c39a6dcfe 100644
--- a/buildSrc/src/main/kotlin/project/Companion.kt
+++ b/buildSrc/src/main/kotlin/project/Companion.kt
@@ -23,4 +23,4 @@ import org.gradle.kotlin.dsl.getByType
internal val Project.libs: LibrariesForLibs
get() = extensions.getByType()
-internal const val DEFAULT_JAVA_VERSION = 17
+const val DEFAULT_JAVA_VERSION = 17
diff --git a/driver-sync/build.gradle.kts b/driver-sync/build.gradle.kts
index ab9bc5c01f4..2a5ae5d3b64 100644
--- a/driver-sync/build.gradle.kts
+++ b/driver-sync/build.gradle.kts
@@ -15,6 +15,7 @@
*/
import ProjectExtensions.configureJarManifest
import ProjectExtensions.configureMavenPublication
+import project.DEFAULT_JAVA_VERSION
plugins {
id("project.java")
@@ -47,7 +48,10 @@ dependencies {
tasks.withType {
// Needed for MicrometerProseTest to set env variable programmatically (calls
// `field.setAccessible(true)`)
- jvmArgs("--add-opens=java.base/java.util=ALL-UNNAMED")
+ val testJavaVersion: Int = findProperty("javaVersion")?.toString()?.toInt() ?: DEFAULT_JAVA_VERSION
+ if (testJavaVersion >= DEFAULT_JAVA_VERSION) {
+ jvmArgs("--add-opens=java.base/java.util=ALL-UNNAMED")
+ }
}
configureMavenPublication {