Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4912105
Split Micrometer observation into separate operation and command types
nhachicha Apr 13, 2026
815a250
Move high-cardinality tags from low-cardinality to high-cardinality
nhachicha Apr 13, 2026
27b3a97
Remove QUERY_TEXT_MAX_LENGTH from Observation.Context
nhachicha Apr 13, 2026
52efa25
Use custom MongodbContext instead of generic SenderContext
nhachicha Apr 13, 2026
bf91627
Add DefaultMongodbObservationConvention and move tag production out o…
nhachicha Apr 13, 2026
da4a3c1
Fixes JAVA-6094
nhachicha Apr 14, 2026
cf4660c
Open and close observation scopes for sync driver context propagation
nhachicha Apr 14, 2026
db5387c
Fixing tests
nhachicha Apr 16, 2026
32014c2
Stop modifying parent operation span after it is stopped
nhachicha Apr 16, 2026
31c4ce2
Bumping Micrometer APIs to @Beta
nhachicha Apr 17, 2026
bf22adc
Fix scope leaks and update stale Javadoc from PR review
nhachicha Apr 17, 2026
04935f5
- Move MongodbContext to public API and allow custom ObservationConve…
nhachicha Apr 17, 2026
777c326
Add new public observability classes to Scala API test exclusions
nhachicha Apr 17, 2026
be3108e
Fix null-safety in DefaultMongodbObservationConvention and MongodbObs…
nhachicha Apr 17, 2026
441820c
fix span lifecycle, move MongodbObservation to public API, add transa…
nhachicha Apr 20, 2026
257516e
moved span creation after getReadBinding/getWriteBinding. Simpler tha…
nhachicha Apr 20, 2026
01650ef
Update driver-core/src/main/com/mongodb/observability/micrometer/Mong…
nhachicha Apr 29, 2026
a227fa8
Update driver-core/src/main/com/mongodb/observability/micrometer/Defa…
nhachicha Apr 29, 2026
a04b968
Fix `com.mongodb.client.FailPoint.enable` (#1931)
stIncMale Apr 9, 2026
37079f0
Add stack-safe async loop support with trampoline pattern (#1905)
vbabanin Apr 16, 2026
2d740d8
JsonBsonEncoder: fix parsing of JsonPrimitive numbers (#1937)
berlix Apr 21, 2026
11ce2ed
build(deps): bump testing/resources/specifications from `c3c82b6` to …
dependabot[bot] Apr 23, 2026
78ad78f
JAVA-6171 upgrade libcrypt to 1.17.3 (#1947)
strogiyotec Apr 23, 2026
fec5d3f
Fix Kotlin bson decoding of optionals (#1941)
rozza Apr 28, 2026
4ca6265
PR feedback
nhachicha Apr 29, 2026
2ed64ff
Merge remote-tracking branch 'origin/main' into nh/micrometer_spring_…
nhachicha Apr 29, 2026
786b1ae
Remove @Beta annotation
nhachicha Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/checkstyle/suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@

<!-- Allow printStackTrace in this file -->
<suppress checks="Regexp" files="CallbackResultHolder"/>
<suppress checks="Regexp" files="MicrometerTracer"/>
<suppress checks="Regexp" files="DefaultMongodbObservationConvention"/>

<!--Do not check documentation tests classes -->
<suppress checks="Javadoc*" files=".*documentation.*"/>
Expand Down
3 changes: 2 additions & 1 deletion driver-core/src/main/com/mongodb/MongoClientSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.mongodb;

import com.mongodb.annotations.Alpha;
import com.mongodb.annotations.Beta;
import com.mongodb.annotations.Immutable;
import com.mongodb.annotations.NotThreadSafe;
import com.mongodb.annotations.Reason;
Expand Down Expand Up @@ -517,7 +518,7 @@ public Builder transportSettings(final TransportSettings transportSettings) {
* @see #getObservabilitySettings()
* @since 5.7
*/
@Alpha(Reason.CLIENT)
@Beta(Reason.CLIENT)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that Spring won't enable Micrometer by default while this remains Alpha or Beta.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

public Builder observabilitySettings(final ObservabilitySettings observabilitySettings) {
this.observabilitySettings = notNull("observabilitySettings", observabilitySettings);
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import com.mongodb.internal.diagnostics.logging.Logger;
import com.mongodb.internal.diagnostics.logging.Loggers;
import com.mongodb.internal.logging.StructuredLogger;
import com.mongodb.observability.micrometer.MongodbObservationContext;
import com.mongodb.internal.observability.micrometer.Span;
import com.mongodb.internal.session.SessionContext;
import com.mongodb.internal.time.Timeout;
Expand Down Expand Up @@ -94,8 +95,7 @@
import static com.mongodb.internal.connection.ProtocolHelper.getSnapshotTimestamp;
import static com.mongodb.internal.connection.ProtocolHelper.isCommandOk;
import static com.mongodb.internal.logging.LogMessage.Level.DEBUG;
import static com.mongodb.internal.observability.micrometer.MongodbObservation.HighCardinalityKeyNames.QUERY_TEXT;
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.RESPONSE_STATUS_CODE;

import static com.mongodb.internal.thread.InterruptionUtil.translateInterruptedException;
import static java.util.Arrays.asList;

Expand Down Expand Up @@ -454,7 +454,6 @@ private <T> T sendAndReceiveInternal(final CommandMessage message, final Decoder
() -> getDescription().getServerAddress(),
() -> getDescription().getConnectionId()
);

boolean isLoggingCommandNeeded = isLoggingCommandNeeded();
boolean isTracingCommandPayloadNeeded = tracingSpan != null && operationContext.getTracingManager().isCommandPayloadEnabled();

Expand All @@ -473,14 +472,19 @@ private <T> T sendAndReceiveInternal(final CommandMessage message, final Decoder
commandEventSender = new NoOpCommandEventSender();
}
if (isTracingCommandPayloadNeeded) {
tracingSpan.tagHighCardinality(QUERY_TEXT.asString(), commandDocument);
tracingSpan.setQueryText(commandDocument);
}
if (tracingSpan != null) {
tracingSpan.openScope();
}

try {
sendCommandMessage(message, bsonOutput, operationContext);
} catch (Exception e) {
if (tracingSpan != null) {
tracingSpan.error(e);
tracingSpan.closeScope();
tracingSpan.end();
}
commandEventSender.sendFailedEvent(e);
throw e;
Expand All @@ -492,6 +496,7 @@ private <T> T sendAndReceiveInternal(final CommandMessage message, final Decoder
} else {
commandEventSender.sendSucceededEventForOneWayCommand();
if (tracingSpan != null) {
tracingSpan.closeScope();
tracingSpan.end();
}
return null;
Expand Down Expand Up @@ -585,13 +590,17 @@ private <T> T receiveCommandMessageResponse(final Decoder<T> decoder, final Comm
}
if (tracingSpan != null) {
if (e instanceof MongoCommandException) {
tracingSpan.tagLowCardinality(RESPONSE_STATUS_CODE.withValue(String.valueOf(((MongoCommandException) e).getErrorCode())));
MongodbObservationContext ctx = tracingSpan.getMongodbObservationContext();
if (ctx != null) {
ctx.setResponseStatusCode(String.valueOf(((MongoCommandException) e).getErrorCode()));
}
}
tracingSpan.error(e);
}
throw e;
} finally {
if (tracingSpan != null) {
tracingSpan.closeScope();
tracingSpan.end();
}
}
Expand Down Expand Up @@ -639,16 +648,18 @@ private <T> void sendAndReceiveAsyncInternal(final CommandMessage message, final
commandEventSender = new NoOpCommandEventSender();
}
if (isTracingCommandPayloadNeeded) {
tracingSpan.tagHighCardinality(QUERY_TEXT.asString(), commandDocument);
tracingSpan.setQueryText(commandDocument);
}

final Span commandSpan = tracingSpan;
SingleResultCallback<T> tracingCallback = commandSpan == null ? callback : (result, t) -> {
try {
if (t != null) {
if (t instanceof MongoCommandException) {
commandSpan.tagLowCardinality(
RESPONSE_STATUS_CODE.withValue(String.valueOf(((MongoCommandException) t).getErrorCode())));
MongodbObservationContext ctx = commandSpan.getMongodbObservationContext();
if (ctx != null) {
ctx.setResponseStatusCode(String.valueOf(((MongoCommandException) t).getErrorCode()));
}
}
commandSpan.error(t);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,20 @@

import com.mongodb.MongoNamespace;
import com.mongodb.lang.Nullable;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import com.mongodb.observability.micrometer.DefaultMongodbObservationConvention;
import com.mongodb.observability.micrometer.MongodbObservation;
import com.mongodb.observability.micrometer.MongodbObservationContext;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.transport.Kind;
import io.micrometer.observation.transport.SenderContext;
import org.bson.BsonDocument;
import org.bson.BsonReader;
import org.bson.json.JsonMode;
import org.bson.json.JsonWriter;
import org.bson.json.JsonWriterSettings;

import java.io.PrintWriter;
import java.io.StringWriter;

import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_MESSAGE;
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_STACKTRACE;
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_TYPE;
import static com.mongodb.internal.observability.micrometer.MongodbObservation.MONGODB_OBSERVATION;
import static com.mongodb.internal.observability.micrometer.TracingManager.ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH;
import static java.lang.System.getenv;
import static java.util.Optional.ofNullable;
Expand All @@ -55,34 +50,30 @@ 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";
private final ObservationConvention<MongodbObservationContext> convention;

/**
* Constructs a new {@link MicrometerTracer} instance.
*
* @param observationRegistry The Micrometer {@link ObservationRegistry} to delegate tracing operations to.
*/
public MicrometerTracer(final ObservationRegistry observationRegistry) {
this(observationRegistry, false, 0);
}

/**
* Constructs a new {@link MicrometerTracer} instance with an option to allow command payloads.
*
* @param observationRegistry The Micrometer {@link ObservationRegistry} to delegate tracing operations to.
* @param allowCommandPayload Whether to allow command payloads in the trace context.
* @param textMaxLength The maximum length for query text truncation.
* @param customConvention A custom observation convention, or null to use the default.
*/
public MicrometerTracer(final ObservationRegistry observationRegistry, final boolean allowCommandPayload, final int textMaxLength) {
public MicrometerTracer(final ObservationRegistry observationRegistry, final boolean allowCommandPayload,
final int textMaxLength, @Nullable final ObservationConvention<MongodbObservationContext> customConvention) {
this.allowCommandPayload = allowCommandPayload;
this.observationRegistry = observationRegistry;
this.textMaxLength = ofNullable(getenv(ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH))
.map(Integer::parseInt)
.orElse(textMaxLength);
this.convention = customConvention != null ? customConvention : new DefaultMongodbObservationConvention();
}

@Override
public Span nextSpan(final String name, @Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) {
Observation observation = getObservation(name);
public Span nextSpan(final MongodbObservation observationType, final String name,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leaks the internal MongodbObservation which means we should either have an alternative enum or we move that enum into the public API.

Can that be made public?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by moving MongodbObservation to com.mongodb.observability.micrometer.

@Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) {
Observation observation = getObservation(observationType, name);

if (parent instanceof MicrometerTraceContext) {
Observation parentObservation = ((MicrometerTraceContext) parent).observation;
Expand All @@ -91,7 +82,7 @@ public Span nextSpan(final String name, @Nullable final TraceContext parent, @Nu
}
}

return new MicrometerSpan(observation.start(), namespace);
return new MicrometerSpan(observation.start(), namespace, textMaxLength);
}

@Override
Expand All @@ -104,12 +95,12 @@ public boolean includeCommandPayload() {
return allowCommandPayload;
}

private Observation getObservation(final String name) {
Observation observation = MONGODB_OBSERVATION.observation(observationRegistry,
() -> new SenderContext<>((carrier, key, value) -> {}, Kind.CLIENT))
.contextualName(name);
observation.getContext().put(QUERY_TEXT_LENGTH_CONTEXT_KEY, textMaxLength);
return observation;
private Observation getObservation(final MongodbObservation observationType, final String name) {
return observationType.observation(observationRegistry, () -> {
MongodbObservationContext ctx = new MongodbObservationContext();
ctx.setObservationType(observationType);
return ctx;
}).observationConvention(convention).contextualName(name);
}
/**
* Represents a Micrometer-based trace context.
Expand All @@ -135,38 +126,43 @@ private static class MicrometerSpan implements Span {
@Nullable
private final MongoNamespace namespace;
private final int queryTextLength;
@Nullable
private Observation.Scope scope;

/**
* Constructs a new {@link MicrometerSpan} instance with an associated Observation and MongoDB namespace.
*
* @param observation The Micrometer {@link Observation}, or null if none exists.
* @param namespace The MongoDB namespace associated with the span.
* @param observation The Micrometer {@link Observation}, or null if none exists.
* @param namespace The MongoDB namespace associated with the span.
* @param queryTextLength The maximum length for query text truncation.
*/
MicrometerSpan(final Observation observation, @Nullable final MongoNamespace namespace) {
MicrometerSpan(final Observation observation, @Nullable final MongoNamespace namespace, final int queryTextLength) {
this.namespace = namespace;
this.observation = observation;
this.queryTextLength = ofNullable(observation.getContext().get(QUERY_TEXT_LENGTH_CONTEXT_KEY))
.filter(Integer.class::isInstance)
.map(Integer.class::cast)
.orElse(Integer.MAX_VALUE);
this.queryTextLength = queryTextLength;
}

@Override
public void tagLowCardinality(final KeyValue keyValue) {
observation.lowCardinalityKeyValue(keyValue);
public void openScope() {
this.scope = observation.openScope();
}

@Override
public void tagLowCardinality(final KeyValues keyValues) {
observation.lowCardinalityKeyValues(keyValues);
public void closeScope() {
if (scope != null) {
scope.close();
scope = null;
}
}
Comment thread
nhachicha marked this conversation as resolved.

@Override
public void tagHighCardinality(final String keyName, final BsonDocument value) {
observation.highCardinalityKeyValue(keyName,
(queryTextLength < Integer.MAX_VALUE) // truncate values that are too long
? getTruncatedBsonDocument(value)
: value.toString());
public void setQueryText(final BsonDocument commandDocument) {
MongodbObservationContext ctx = getMongodbObservationContext();
if (ctx != null) {
ctx.setQueryText((queryTextLength < Integer.MAX_VALUE)
? getTruncatedBsonDocument(commandDocument)
: commandDocument.toString());
}
}

@Override
Expand All @@ -176,11 +172,6 @@ public void event(final String event) {

@Override
public void error(final Throwable throwable) {
observation.lowCardinalityKeyValues(KeyValues.of(
EXCEPTION_MESSAGE.withValue(throwable.getMessage()),
EXCEPTION_TYPE.withValue(throwable.getClass().getName()),
EXCEPTION_STACKTRACE.withValue(getStackTraceAsString(throwable))
));
observation.error(throwable);
}

Expand All @@ -196,15 +187,17 @@ public TraceContext context() {

@Override
@Nullable
public MongoNamespace getNamespace() {
return namespace;
public MongodbObservationContext getMongodbObservationContext() {
if (observation.getContext() instanceof MongodbObservationContext) {
return (MongodbObservationContext) observation.getContext();
}
return null;
}

private String getStackTraceAsString(final Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
return sw.toString();
@Override
@Nullable
public MongoNamespace getNamespace() {
return namespace;
}

private String getTruncatedBsonDocument(final BsonDocument commandDocument) {
Expand Down
Loading