Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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)
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,19 @@

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.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 +49,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,
@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 +81,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 +94,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 +125,43 @@ private static class MicrometerSpan implements Span {
@Nullable
private final MongoNamespace namespace;
private final int queryTextLength;
@Nullable
private Observation.Scope scope;

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

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

@Override
public void tagLowCardinality(final KeyValues keyValues) {
observation.lowCardinalityKeyValues(keyValues);
public void closeScope() {
if (scope != null) {
scope.close();
scope = null;
}
}

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

@Override
Expand All @@ -176,11 +171,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 +186,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