From 41c3b2502508330e75e8278b1ca1175c459a89ce Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 27 Oct 2025 14:14:20 +0100 Subject: [PATCH] Remove parent observation key instead of setting null. When completing a MongoObservation without a parent observation, we now remove the parent observation key from the request context instead of setting it to null. In reactive usage, the context map is a ConcurrentHashMap that does not allow null values. Closes #5082 Original pull request #5083 --- .../MongoObservationCommandListener.java | 9 +++++++- .../MongoObservationCommandListenerTests.java | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservationCommandListener.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservationCommandListener.java index 45a2ba701..7eb0eec1f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservationCommandListener.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservationCommandListener.java @@ -17,6 +17,7 @@ package org.springframework.data.mongodb.observability; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.ObservationView; import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; import java.util.function.BiConsumer; @@ -191,7 +192,13 @@ public class MongoObservationCommandListener implements CommandListener { log.debug( "Restoring parent observation [" + observation + "] for Mongo instrumentation and put it in Mongo context"); } - requestContext.put(ObservationThreadLocalAccessor.KEY, observation.getContext().getParentObservation()); + ObservationView parentObservation = observation.getContext().getParentObservation(); + + if (parentObservation == null) { + requestContext.delete(ObservationThreadLocalAccessor.KEY); + } else { + requestContext.put(ObservationThreadLocalAccessor.KEY, parentObservation); + } } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerTests.java index dadb98ce2..dc97e0aed 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerTests.java @@ -26,6 +26,7 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; +import reactor.core.publisher.BaseSubscriber; import org.assertj.core.api.Assertions; import org.bson.BsonDocument; @@ -43,6 +44,7 @@ import com.mongodb.connection.ServerId; import com.mongodb.event.CommandFailedEvent; import com.mongodb.event.CommandStartedEvent; import com.mongodb.event.CommandSucceededEvent; +import com.mongodb.reactivestreams.client.ReactiveContextProvider; /** * Series of test cases exercising {@link MongoObservationCommandListener}. @@ -113,6 +115,25 @@ class MongoObservationCommandListenerTests { Tags.of("db.mongodb.collection", "none")); } + @Test // GH-5082 + void reactiveContextCompletesNormally() { + + ReactiveContextProvider rcp = (ReactiveContextProvider) ContextProviderFactory.create(observationRegistry); + RequestContext context = rcp.getContext(new BaseSubscriber<>() {}); + + listener.commandStarted(new CommandStartedEvent(context, 0, 0, // + new ConnectionDescription( // + new ServerId( // + new ClusterId("description"), // + new ServerAddress("localhost", 1234))), + "database", "insert", // + new BsonDocument("collection", new BsonString("user")))); + listener.commandSucceeded(new CommandSucceededEvent(context, 0, 0, null, "insert", null, null, 0)); + + // then + assertThatTimerRegisteredWithTags(); + } + @Test void successfullyCompletedCommandShouldCreateTimerWhenParentSampleInRequestContext() {