Skip to content

Commit cf4660c

Browse files
committed
Open and close observation scopes for sync driver context propagation
The driver called observation.start()/stop() but never openScope()/ closeScope(). Without scopes, registry.getCurrentObservation() returned null during MongoDB operations, breaking context propagation for any downstream code (Spring interceptors, user observations, MDC logging). For example, in withTransaction, a user observation created inside the callback would attach to the Spring HTTP parent instead of the MongoDB transaction span, because the transaction observation was never made "current" on the thread. Added openScope()/closeScope() to the Span interface with scope lifecycle management in MongoClusterImpl (operation spans), InternalStreamConnection (command spans), and TransactionSpan.
1 parent da4a3c1 commit cf4660c

6 files changed

Lines changed: 62 additions & 1 deletion

File tree

driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,9 @@ private <T> T sendAndReceiveInternal(final CommandMessage message, final Decoder
454454
() -> getDescription().getServerAddress(),
455455
() -> getDescription().getConnectionId()
456456
);
457+
if (tracingSpan != null) {
458+
tracingSpan.openScope();
459+
}
457460

458461
boolean isLoggingCommandNeeded = isLoggingCommandNeeded();
459462
boolean isTracingCommandPayloadNeeded = tracingSpan != null && operationContext.getTracingManager().isCommandPayloadEnabled();
@@ -481,6 +484,8 @@ private <T> T sendAndReceiveInternal(final CommandMessage message, final Decoder
481484
} catch (Exception e) {
482485
if (tracingSpan != null) {
483486
tracingSpan.error(e);
487+
tracingSpan.closeScope();
488+
tracingSpan.end();
484489
}
485490
commandEventSender.sendFailedEvent(e);
486491
throw e;
@@ -492,6 +497,7 @@ private <T> T sendAndReceiveInternal(final CommandMessage message, final Decoder
492497
} else {
493498
commandEventSender.sendSucceededEventForOneWayCommand();
494499
if (tracingSpan != null) {
500+
tracingSpan.closeScope();
495501
tracingSpan.end();
496502
}
497503
return null;
@@ -595,6 +601,7 @@ private <T> T receiveCommandMessageResponse(final Decoder<T> decoder, final Comm
595601
throw e;
596602
} finally {
597603
if (tracingSpan != null) {
604+
tracingSpan.closeScope();
598605
tracingSpan.end();
599606
}
600607
}

driver-core/src/main/com/mongodb/internal/observability/micrometer/MicrometerTracer.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ private static class MicrometerSpan implements Span {
122122
@Nullable
123123
private final MongoNamespace namespace;
124124
private final int queryTextLength;
125+
@Nullable
126+
private Observation.Scope scope;
125127

126128
/**
127129
* Constructs a new {@link MicrometerSpan} instance with an associated Observation and MongoDB namespace.
@@ -136,6 +138,19 @@ private static class MicrometerSpan implements Span {
136138
this.queryTextLength = queryTextLength;
137139
}
138140

141+
@Override
142+
public void openScope() {
143+
this.scope = observation.openScope();
144+
}
145+
146+
@Override
147+
public void closeScope() {
148+
if (scope != null) {
149+
scope.close();
150+
scope = null;
151+
}
152+
}
153+
139154
@Override
140155
public void setQueryText(final BsonDocument commandDocument) {
141156
MongodbContext ctx = getMongodbContext();

driver-core/src/main/com/mongodb/internal/observability/micrometer/Span.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ public interface Span {
4545
* </p>
4646
*/
4747
Span EMPTY = new Span() {
48+
@Override
49+
public void openScope() {
50+
}
51+
52+
@Override
53+
public void closeScope() {
54+
}
55+
4856
@Override
4957
public void setQueryText(final BsonDocument commandDocument) {
5058
}
@@ -79,6 +87,17 @@ public MongodbContext getMongodbContext() {
7987
}
8088
};
8189

90+
/**
91+
* Opens a scope for this span, making it the current observation on the thread.
92+
* Must be paired with {@link #closeScope()} in a try-finally block.
93+
*/
94+
void openScope();
95+
96+
/**
97+
* Closes the scope previously opened by {@link #openScope()}, restoring the previous observation.
98+
*/
99+
void closeScope();
100+
82101
/**
83102
* Sets the query text on the observation context from the given command document.
84103
* The document is converted to a JSON string and may be truncated based on configuration.

driver-core/src/main/com/mongodb/internal/observability/micrometer/TransactionSpan.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public class TransactionSpan {
3131

3232
public TransactionSpan(final TracingManager tracingManager) {
3333
this.span = tracingManager.addTransactionSpan();
34+
this.span.openScope();
3435
}
3536

3637
/**
@@ -54,6 +55,7 @@ public void handleTransactionSpanError(final Throwable e) {
5455
}
5556

5657
if (!isConvenientTransaction) {
58+
span.closeScope();
5759
span.end();
5860
}
5961
}
@@ -67,6 +69,7 @@ public void finalizeTransactionSpan(final String status) {
6769
span.event(status);
6870
// clear previous commit error if any
6971
if (!isConvenientTransaction) {
72+
span.closeScope();
7073
span.end();
7174
}
7275
reportedError = null; // clear previous commit error if any
@@ -82,6 +85,7 @@ public void spanFinalizing(final boolean cleanupTransactionContext) {
8285
if (reportedError != null) {
8386
span.error(reportedError);
8487
}
88+
span.closeScope();
8589
span.end();
8690
reportedError = null;
8791
// Don't clean up transaction context if we're still retrying (we want the retries to fold under the original transaction span)

driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,11 @@ public <T> T execute(final ReadOperation<T, ?> operation, final ReadPreference r
426426
.withSessionContext(new ClientSessionBinding.SyncClientSessionContext(actualClientSession, readConcern, implicitSession));
427427
Span span = operationContext.getTracingManager().createOperationSpan(
428428
actualClientSession.getTransactionSpan(), operationContext, operation.getCommandName(), operation.getNamespace());
429+
if (span != null) {
430+
span.openScope();
431+
}
429432
ReadBinding binding = getReadBinding(readPreference, actualClientSession, implicitSession);
430433

431-
432434
try {
433435
if (actualClientSession.hasActiveTransaction() && !binding.getReadPreference().equals(primary())) {
434436
throw new MongoClientException("Read preference in a transaction must be primary");
@@ -445,6 +447,7 @@ public <T> T execute(final ReadOperation<T, ?> operation, final ReadPreference r
445447
} finally {
446448
binding.release();
447449
if (span != null) {
450+
span.closeScope();
448451
span.end();
449452
}
450453
}
@@ -462,6 +465,9 @@ public <T> T execute(final WriteOperation<T> operation, final ReadConcern readCo
462465
.withSessionContext(new ClientSessionBinding.SyncClientSessionContext(actualClientSession, readConcern, isImplicitSession(session)));
463466
Span span = operationContext.getTracingManager().createOperationSpan(
464467
actualClientSession.getTransactionSpan(), operationContext, operation.getCommandName(), operation.getNamespace());
468+
if (span != null) {
469+
span.openScope();
470+
}
465471
WriteBinding binding = getWriteBinding(actualClientSession, isImplicitSession(session));
466472

467473
try {
@@ -477,6 +483,7 @@ public <T> T execute(final WriteOperation<T> operation, final ReadConcern readCo
477483
} finally {
478484
binding.release();
479485
if (span != null) {
486+
span.closeScope();
480487
span.end();
481488
}
482489
}

driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import com.mongodb.lang.Nullable;
5050
import com.mongodb.logging.TestLoggingInterceptor;
5151
import com.mongodb.observability.ObservabilitySettings;
52+
import io.micrometer.observation.Observation;
5253
import io.micrometer.observation.ObservationRegistry;
5354
import io.micrometer.tracing.test.reporter.inmemory.InMemoryOtelSetup;
5455
import org.bson.BsonArray;
@@ -113,6 +114,7 @@ public final class Entities {
113114
private final Map<String, TestLoggingInterceptor> clientLoggingInterceptors = new HashMap<>();
114115
private final Map<String, InMemoryOtelSetup.Builder.OtelBuildingBlocks> clientTracing = new HashMap<>();
115116
private final Set<InMemoryOtelSetup> inMemoryOTelInstances = new HashSet<>();
117+
private final Set<Observation.Scope> observationScopes = new HashSet<>();
116118
private final Map<String, TestConnectionPoolListener> clientConnectionPoolListeners = new HashMap<>();
117119
private final Map<String, TestServerListener> clientServerListeners = new HashMap<>();
118120
private final Map<String, TestClusterListener> clientClusterListeners = new HashMap<>();
@@ -593,6 +595,12 @@ private void initClient(final BsonDocument entity, final String id,
593595
.observabilitySettings(ObservabilitySettings.micrometerBuilder()
594596
.observationRegistry(observationRegistry)
595597
.enableCommandPayloadTracing(enableCommandPayload).build());
598+
599+
// Simulate what Spring Boot's observation does
600+
// open a parent observation's scope before running the MongoDB operation
601+
Observation parentObservation = Observation.createNotStarted("http.request", observationRegistry)
602+
.start();
603+
observationScopes.add(parentObservation.openScope());
596604
}
597605

598606
MongoClientSettings clientSettings = clientSettingsBuilder.build();
@@ -816,5 +824,6 @@ public void close() {
816824
clientLoggingInterceptors.values().forEach(TestLoggingInterceptor::close);
817825
threads.values().forEach(ExecutorService::shutdownNow);
818826
inMemoryOTelInstances.forEach(InMemoryOtelSetup::close);
827+
observationScopes.forEach(Observation.Scope::close);
819828
}
820829
}

0 commit comments

Comments
 (0)