+ * This class enables integration of MongoDB driver tracing with Micrometer-based tracing systems. + * It provides integration with Micrometer to propagate observations into tracing API. + *
+ * + * @since 5.7 + */ +public class MicrometerTracer implements Tracer { + private final ObservationRegistry observationRegistry; + private final boolean allowCommandPayload; + private final int textMaxLength; + private static final String QUERY_TEXT_LENGTH_CONTEXT_KEY = "QUERY_TEXT_MAX_LENGTH"; + + /** + * Constructs a new {@link MicrometerTracer} instance. + * + * @param observationRegistry The Micrometer {@link ObservationRegistry} to delegate tracing operations to. + */ + public MicrometerTracer(final ObservationRegistry observationRegistry) { + this(observationRegistry, false, 0); + } + + /** + * Constructs a new {@link MicrometerTracer} instance with an option to allow command payloads. + * + * @param observationRegistry The Micrometer {@link ObservationRegistry} to delegate tracing operations to. + * @param allowCommandPayload Whether to allow command payloads in the trace context. + */ + public MicrometerTracer(final ObservationRegistry observationRegistry, final boolean allowCommandPayload, final int textMaxLength) { + this.allowCommandPayload = allowCommandPayload; + this.observationRegistry = observationRegistry; + this.textMaxLength = ofNullable(getenv(ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH)) + .map(Integer::parseInt) + .orElse(textMaxLength); + } + + @Override + public Span nextSpan(final String name, @Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) { + Observation observation = getObservation(name); + + if (parent instanceof MicrometerTraceContext) { + Observation parentObservation = ((MicrometerTraceContext) parent).observation; + if (parentObservation != null) { + observation.parentObservation(parentObservation); + } + } + + return new MicrometerSpan(observation.start(), namespace); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean includeCommandPayload() { + return allowCommandPayload; + } + + private Observation getObservation(final String name) { + Observation observation = MONGODB_OBSERVATION.observation(observationRegistry, + () -> new SenderContext<>((carrier, key, value) -> {}, Kind.CLIENT)) + .contextualName(name); + observation.getContext().put(QUERY_TEXT_LENGTH_CONTEXT_KEY, textMaxLength); + return observation; + } + /** + * Represents a Micrometer-based trace context. + */ + private static class MicrometerTraceContext implements TraceContext { + private final Observation observation; + + /** + * Constructs a new {@link MicrometerTraceContext} instance with an associated Observation. + * + * @param observation The Micrometer {@link Observation}, or null if none exists. + */ + MicrometerTraceContext(@Nullable final Observation observation) { + this.observation = observation; + } + } + + /** + * Represents a Micrometer-based span. + */ + private static class MicrometerSpan implements Span { + private final Observation observation; + @Nullable + private final MongoNamespace namespace; + private final int queryTextLength; + + /** + * 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; + this.queryTextLength = ofNullable(observation.getContext().get(QUERY_TEXT_LENGTH_CONTEXT_KEY)) + .filter(Integer.class::isInstance) + .map(Integer.class::cast) + .orElse(Integer.MAX_VALUE); + } + + @Override + public void tagLowCardinality(final KeyValue keyValue) { + observation.lowCardinalityKeyValue(keyValue); + } + + @Override + public void tagLowCardinality(final KeyValues keyValues) { + observation.lowCardinalityKeyValues(keyValues); + } + + @Override + public void tagHighCardinality(final String keyName, final BsonDocument value) { + observation.highCardinalityKeyValue(keyName, + (queryTextLength < Integer.MAX_VALUE) // truncate values that are too long + ? getTruncatedBsonDocument(value) + : value.toString()); + } + + @Override + public void event(final String event) { + observation.event(() -> event); + } + + @Override + public void error(final Throwable throwable) { + observation.lowCardinalityKeyValues(KeyValues.of( + EXCEPTION_MESSAGE.withValue(throwable.getMessage()), + EXCEPTION_TYPE.withValue(throwable.getClass().getName()), + EXCEPTION_STACKTRACE.withValue(getStackTraceAsString(throwable)) + )); + observation.error(throwable); + } + + @Override + public void end() { + observation.stop(); + } + + @Override + public TraceContext context() { + return new MicrometerTraceContext(observation); + } + + @Override + @Nullable + public MongoNamespace getNamespace() { + return namespace; + } + + private String getStackTraceAsString(final Throwable throwable) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + return sw.toString(); + } + + private String getTruncatedBsonDocument(final BsonDocument commandDocument) { + StringWriter writer = new StringWriter(); + + try (BsonReader bsonReader = commandDocument.asBsonReader()) { + JsonWriter jsonWriter = new JsonWriter(writer, + JsonWriterSettings.builder().outputMode(JsonMode.RELAXED) + .maxLength(queryTextLength) + .build()); + + jsonWriter.pipe(bsonReader); + + if (jsonWriter.isTruncated()) { + writer.append(" ..."); + } + + return writer.toString(); + } + } + } +} diff --git a/driver-core/src/main/com/mongodb/internal/tracing/MongodbObservation.java b/driver-core/src/main/com/mongodb/internal/tracing/MongodbObservation.java new file mode 100644 index 00000000000..4a25487841b --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/tracing/MongodbObservation.java @@ -0,0 +1,181 @@ +/* + * 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 io.micrometer.common.docs.KeyName; +import io.micrometer.observation.Observation; +import io.micrometer.observation.docs.ObservationDocumentation; + +/** + * A MongoDB-based {@link 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"; + } + }, + 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/internal/tracing/Span.java b/driver-core/src/main/com/mongodb/internal/tracing/Span.java new file mode 100644 index 00000000000..dd1d42a3be0 --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/tracing/Span.java @@ -0,0 +1,141 @@ +/* + * 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.MongoNamespace; +import com.mongodb.lang.Nullable; +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.bson.BsonDocument; + +/** + * 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: + *
+ * 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 tagLowCardinality(final KeyValue tag) { + } + + @Override + public void tagLowCardinality(final KeyValues keyValues) { + } + + @Override + public void tagHighCardinality(final String keyName, final BsonDocument 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; + } + + @Override + @Nullable + public MongoNamespace getNamespace() { + return null; + } + }; + + /** + * 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 keyValues The key-value pairs representing the tags. + */ + void tagLowCardinality(KeyValues keyValues); + + /** + * Adds a high-cardinality (highly variable values) tag to the span with a BSON document value. + * + * @param keyName The name of the tag. + * @param value The BSON document representing the value of the tag. + */ + void tagHighCardinality(String keyName, BsonDocument 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(); + + /** + * 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/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/Tracer.java b/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java new file mode 100644 index 00000000000..1ddfc8f2087 --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/tracing/Tracer.java @@ -0,0 +1,71 @@ +/* + * 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.MongoNamespace; +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.7 + */ +public interface Tracer { + Tracer NO_OP = new Tracer() { + @Override + public Span nextSpan(final String name, @Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) { + return Span.EMPTY; + } + + @Override + public boolean isEnabled() { + return false; + } + + @Override + public boolean includeCommandPayload() { + return false; + } + }; + + /** + * 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, @Nullable MongoNamespace namespace); + + /** + * Indicates whether tracing is enabled. + * + * @return {@code true} if tracing is enabled, {@code false} otherwise. + */ + boolean isEnabled(); + + /** + * Indicates whether command payloads are included in the trace context. + * + * @return {@code true} if command payloads are allowed, {@code false} otherwise. + */ + 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 new file mode 100644 index 00000000000..b4a71039c6a --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/tracing/TracingManager.java @@ -0,0 +1,259 @@ +/* + * 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.MongoNamespace; +import com.mongodb.ServerAddress; +import com.mongodb.UnixServerAddress; +import com.mongodb.connection.ConnectionId; +import com.mongodb.internal.connection.CommandMessage; +import com.mongodb.internal.connection.OperationContext; +import com.mongodb.internal.session.SessionContext; +import com.mongodb.lang.Nullable; +import com.mongodb.observability.MicrometerObservabilitySettings; +import com.mongodb.observability.ObservabilitySettings; +import io.micrometer.common.KeyValues; +import io.micrometer.observation.ObservationRegistry; +import org.bson.BsonDocument; + +import java.util.function.Predicate; +import java.util.function.Supplier; + +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.CLIENT_CONNECTION_ID; +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.COLLECTION; +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.COMMAND_NAME; +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.CURSOR_ID; +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.NAMESPACE; +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.NETWORK_TRANSPORT; +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.QUERY_SUMMARY; +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.SERVER_ADDRESS; +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.SERVER_CONNECTION_ID; +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.SERVER_PORT; +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.SESSION_ID; +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.SYSTEM; +import static com.mongodb.internal.tracing.MongodbObservation.LowCardinalityKeyNames.TRANSACTION_NUMBER; +import static com.mongodb.observability.MicrometerObservabilitySettings.ENV_OBSERVABILITY_ENABLED; +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(null); + private final Tracer tracer; + private final boolean enableCommandPayload; + + /** + * Constructs a new TracingManager with the specified observation registry. + * @param observationRegistry The observation registry to use for tracing operations, may be null. + */ + public TracingManager(@Nullable final ObservabilitySettings observabilitySettings) { + if (observabilitySettings == null) { + tracer = Tracer.NO_OP; + enableCommandPayload = false; + + } else { + MicrometerObservabilitySettings settings; + if (observabilitySettings instanceof MicrometerObservabilitySettings) { + settings = (MicrometerObservabilitySettings) observabilitySettings; + } else { + throw new IllegalArgumentException("Only Micrometer based observability is currently supported"); + } + + String envOtelInstrumentationEnabled = getenv(ENV_OBSERVABILITY_ENABLED); + boolean enableTracing = true; + if (envOtelInstrumentationEnabled != null) { + enableTracing = Boolean.parseBoolean(envOtelInstrumentationEnabled); + } + + ObservationRegistry observationRegistry = settings.getObservationRegistry(); + tracer = enableTracing && observationRegistry != null + ? new MicrometerTracer(observationRegistry, settings.isEnableCommandPayloadTracing(), settings.getMaxQueryTextLength()) + : Tracer.NO_OP; + + this.enableCommandPayload = tracer.includeCommandPayload(); + } + } + + /** + * 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, 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); + } + + /** + * Creates a new transaction span for the specified server session. + * + * @return The created transaction span. + */ + public Span addTransactionSpan() { + Span span = tracer.nextSpan("transaction", null, null); + span.tagLowCardinality(SYSTEM.withValue("mongodb")); + return span; + } + + /** + * Checks whether tracing is enabled. + * + * @return True if tracing is enabled, false otherwise. + */ + public boolean isEnabled() { + return tracer.isEnabled(); + } + + /** + * Checks whether command payload tracing is enabled. + * + * @return True if command payload tracing is enabled, false otherwise. + */ + public boolean isCommandPayloadEnabled() { + return enableCommandPayload; + } + + + /** Create 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.
+     * 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 commandDocumentSupplier a supplier that provides the command document when needed
+     * @param isSensitiveCommand a predicate that determines if a command is security-sensitive based on its name
+     * @param serverAddressSupplier a supplier that provides the server address when needed
+     * @param connectionIdSupplier a supplier that provides the connection ID when needed
+     * @return the created {@link Span}, or {@code null} if tracing is not enabled or the command is security-sensitive
+     */
+    @Nullable
+    public Span createTracingSpan(final CommandMessage message,
+            final OperationContext operationContext,
+            final Supplier 
+     * 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
new file mode 100644
index 00000000000..e7dd3311143
--- /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 tracing
+ */
+@NonNullApi
+package com.mongodb.internal.tracing;
+
+import com.mongodb.lang.NonNullApi;
diff --git a/driver-core/src/main/com/mongodb/observability/MicrometerObservabilitySettings.java b/driver-core/src/main/com/mongodb/observability/MicrometerObservabilitySettings.java
new file mode 100644
index 00000000000..8624cc8c140
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/observability/MicrometerObservabilitySettings.java
@@ -0,0 +1,201 @@
+/*
+ * 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.observability;
+
+import com.mongodb.annotations.Alpha;
+import com.mongodb.annotations.Immutable;
+import com.mongodb.annotations.Internal;
+import com.mongodb.annotations.NotThreadSafe;
+import com.mongodb.annotations.Reason;
+import com.mongodb.lang.Nullable;
+import io.micrometer.observation.ObservationRegistry;
+
+import java.util.Objects;
+
+import static com.mongodb.assertions.Assertions.notNull;
+
+/**
+ * The Micrometer Observation settings for tracing operations, commands and transactions.
+ *
+ *  If tracing is configured by supplying an {@code observationRegistry} then setting the environment variable
+ * {@value ENV_OBSERVABILITY_ENABLED} is used to enable or disable the creation of tracing spans.
+ *
+ *   If set the environment variable {@value ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH} will be used to determine the maximum length
+ * of command payloads captured in tracing spans. If the environment variable is not set, the entire command payloads is
+ * captured (unless a {@code maxQueryTextLength} is specified via the Builder).
+ *
+ * @since 5.7
+ */
+@Alpha(Reason.CLIENT)
+@Immutable
+public final class MicrometerObservabilitySettings extends ObservabilitySettings {
+
+    @Internal
+    // If set, this will enable/disable tracing even when an observationRegistry has been passed.
+    public static final String ENV_OBSERVABILITY_ENABLED = "OBSERVABILITY_MONGODB_ENABLED";
+    @Internal
+    // If set, this will truncate the command payload captured in the tracing span to the specified length.
+    public static final String ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH = "OBSERVABILITY_MONGODB_QUERY_TEXT_MAX_LENGTH";
+    @Nullable
+    private final ObservationRegistry observationRegistry;
+    private final int maxQueryTextLength;
+    private final boolean enableCommandPayloadTracing;
+
+    /**
+     * Convenience method to create a Builder.
+     *
+     * @return a builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Convenience method to create a from an existing {@code TracingSettings}.
+     *
+     * @param settings create a builder from existing settings
+     * @return a builder
+     */
+    public static Builder builder(final MicrometerObservabilitySettings settings) {
+        return new Builder(settings);
+    }
+
+    /**
+     * @return the observation registry or null
+     */
+    @Nullable
+    public ObservationRegistry getObservationRegistry() {
+        return observationRegistry;
+    }
+
+    /**
+     * @return true if command payload tracing is enabled
+     */
+    public boolean isEnableCommandPayloadTracing() {
+        return enableCommandPayloadTracing;
+    }
+
+    /**
+     * @return the maximum length of command payloads captured in tracing spans.
+     */
+    public int getMaxQueryTextLength() {
+        return maxQueryTextLength;
+    }
+
+    /**
+     * A builder for {@code TracingSettings}
+     */
+    @NotThreadSafe
+    public static final class Builder {
+        @Nullable
+        private ObservationRegistry observationRegistry;
+        private boolean enableCommandPayloadTracing;
+        private int maxQueryTextLength = Integer.MAX_VALUE;
+
+        private Builder() {}
+        private Builder(final MicrometerObservabilitySettings settings) {
+            this.observationRegistry = settings.observationRegistry;
+            this.enableCommandPayloadTracing = settings.enableCommandPayloadTracing;
+            this.maxQueryTextLength = settings.maxQueryTextLength;
+        }
+
+        /**
+         * Applies the tracingSettings to the builder
+         *
+         *  Note: Overwrites all existing settings