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 c1b12f9f18a..fa9403242ec 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; @@ -75,8 +78,8 @@ import static com.mongodb.assertions.Assertions.assertNull; import static com.mongodb.assertions.Assertions.isTrue; import static com.mongodb.assertions.Assertions.notNull; -import static com.mongodb.internal.async.AsyncRunnable.beginAsync; import static com.mongodb.internal.TimeoutContext.createMongoTimeoutException; +import static com.mongodb.internal.async.AsyncRunnable.beginAsync; import static com.mongodb.internal.async.ErrorHandlingResultCallback.errorHandlingCallback; import static com.mongodb.internal.connection.Authenticator.shouldAuthenticate; import static com.mongodb.internal.connection.CommandHelper.HELLO; @@ -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 bf29ebc051b..07a000bc1a2 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; @@ -49,10 +50,11 @@ public class OperationContext { private final TimeoutContext timeoutContext; @Nullable private final ServerApi serverApi; + private final TracingManager tracingManager; public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext, - @Nullable final ServerApi serverApi) { - this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, new ServerDeprioritization(), serverApi); + @Nullable final ServerApi serverApi, final TracingManager tracingManager) { + this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, new ServerDeprioritization(), serverApi, tracingManager); } public static OperationContext simpleOperationContext( @@ -61,7 +63,8 @@ public static OperationContext simpleOperationContext( IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE, new TimeoutContext(timeoutSettings), - serverApi); + serverApi, + TracingManager.NO_OP); } public static OperationContext simpleOperationContext(final TimeoutContext timeoutContext) { @@ -69,21 +72,26 @@ public static OperationContext simpleOperationContext(final TimeoutContext timeo IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE, timeoutContext, - null); + null, + TracingManager.NO_OP); } public OperationContext withSessionContext(final SessionContext sessionContext) { - return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, serverApi); + return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, serverApi, tracingManager); } public OperationContext withTimeoutContext(final TimeoutContext timeoutContext) { - return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, serverApi); + return new OperationContext(id, requestContext, sessionContext, timeoutContext, serverDeprioritization, serverApi, tracingManager); } public long getId() { return id; } + public TracingManager getTracingManager() { + return tracingManager; + } + public SessionContext getSessionContext() { return sessionContext; } @@ -107,13 +115,15 @@ public OperationContext(final long id, final SessionContext sessionContext, final TimeoutContext timeoutContext, final ServerDeprioritization serverDeprioritization, - @Nullable final ServerApi serverApi) { + @Nullable final ServerApi serverApi, + final TracingManager tracingManager) { this.id = id; this.serverDeprioritization = serverDeprioritization; this.requestContext = requestContext; this.sessionContext = sessionContext; this.timeoutContext = timeoutContext; this.serverApi = serverApi; + this.tracingManager = tracingManager; } @VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE) @@ -121,13 +131,15 @@ public OperationContext(final long id, final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext, - @Nullable final ServerApi serverApi) { + @Nullable final ServerApi serverApi, + final TracingManager tracingManager) { this.id = id; this.serverDeprioritization = new ServerDeprioritization(); this.requestContext = requestContext; this.sessionContext = sessionContext; this.timeoutContext = timeoutContext; this.serverApi = serverApi; + this.tracingManager = tracingManager; } 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 4f834bee349..28dc5f4f9e2 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; @@ -290,13 +292,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) { @@ -304,7 +311,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 @@ -469,9 +485,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 f0004cd9e03..78526014654 100644 --- a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java +++ b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java @@ -70,6 +70,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; @@ -194,7 +195,8 @@ public static ServerVersion getServerVersion() { IgnorableRequestContext.INSTANCE, new ReadConcernAwareNoOpSessionContext(ReadConcern.DEFAULT), new TimeoutContext(TIMEOUT_SETTINGS), - getServerApi()); + getServerApi(), + TracingManager.NO_OP); public static final InternalOperationContextFactory OPERATION_CONTEXT_FACTORY = new InternalOperationContextFactory(TIMEOUT_SETTINGS, getServerApi()); @@ -204,7 +206,8 @@ public static OperationContext createOperationContext(final TimeoutSettings time IgnorableRequestContext.INSTANCE, new ReadConcernAwareNoOpSessionContext(ReadConcern.DEFAULT), new TimeoutContext(timeoutSettings), - getServerApi()); + getServerApi(), + TracingManager.NO_OP); } private static ServerVersion getVersion(final BsonDocument buildInfoResult) { @@ -386,7 +389,8 @@ public static OperationContext createNewOperationContext(final TimeoutSettings t OPERATION_CONTEXT.getRequestContext(), OPERATION_CONTEXT.getSessionContext(), new TimeoutContext(timeoutSettings), - OPERATION_CONTEXT.getServerApi()); + OPERATION_CONTEXT.getServerApi(), + TracingManager.NO_OP); } private static ReadWriteBinding getBinding(final Cluster cluster, 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 21323a40604..121dcef65a8 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; @@ -860,7 +861,7 @@ private void cleanCursors() { while ((cur = orphanedCursors.poll()) != null) { ReadWriteBinding binding = new SingleServerBinding(delegate.getCluster(), cur.serverCursor.getAddress(), new OperationContext(IgnorableRequestContext.INSTANCE, NoOpSessionContext.INSTANCE, - new TimeoutContext(getTimeoutSettings()), options.getServerApi())); + new TimeoutContext(getTimeoutSettings()), options.getServerApi(), TracingManager.NO_OP)); try { ConnectionSource source = binding.getReadConnectionSource(); try { 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 0a4b0318d1c..904d3d4f246 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 @@ -201,7 +201,8 @@ private OperationContext getOperationContext(final RequestContext requestContext requestContext, new ReadConcernAwareNoOpSessionContext(readConcern), createTimeoutContext(session, timeoutSettings), - mongoClient.getSettings().getServerApi()); + mongoClient.getSettings().getServerApi(), + mongoClient.getSettings().getTracingManager()); } private ReadPreference getReadPreferenceForBinding(final ReadPreference readPreference, @Nullable final ClientSession session) { 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 cf9ca2a3b7d..98d862aebcb 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java @@ -104,7 +104,8 @@ private 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 = createClientMetadataDocument(settings.getApplicationName(), mongoDriverInformation); 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 9c0033e42a7..da417ed3e60 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 @@ -494,7 +499,8 @@ private OperationContext getOperationContext(final ClientSession session, final getRequestContext(), new ReadConcernAwareNoOpSessionContext(readConcern), createTimeoutContext(session, executorTimeoutSettings), - serverApi); + serverApi, + tracingManager); } private RequestContext getRequestContext() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eab637a8b41..c87578b1484 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" @@ -92,6 +93,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" }