Skip to content

Commit 0bc50ec

Browse files
mp911dechristophstrobl
authored andcommitted
Clean up Observation from context after command completion.
We now restore the previous Observation parent after completing the Observation to avoid unintentional nesting of observations. Previously, a command became the parent of an earlier command on the same thread regardless of whether the earlier command has been finished or whether it was active. Observation nesting now considers only commands that are still active restoring their parent after completion to avoid invalid nesting and with that, to not allocate observation graphs that allocate infinite memory. Closes: #5064 Original Pull Request: #5067
1 parent 394fa50 commit 0bc50ec

File tree

3 files changed

+61
-15
lines changed

3 files changed

+61
-15
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MapRequestContext.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,14 @@ public MapRequestContext(Map<Object, Object> context) {
4141
}
4242

4343
@Override
44+
@SuppressWarnings("unchecked")
4445
public <T> T get(Object key) {
4546
return (T) map.get(key);
4647
}
4748

4849
@Override
4950
public boolean hasKey(Object key) {
50-
return map.containsKey(key);
51+
return map.get(key) != null;
5152
}
5253

5354
@Override

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/observability/MongoObservationCommandListener.java

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,6 @@ public void commandStarted(CommandStartedEvent event) {
113113

114114
Observation parent = observationFromContext(requestContext);
115115

116-
if (log.isDebugEnabled()) {
117-
log.debug("Found the following observation passed from the mongo context [" + parent + "]");
118-
}
119-
120116
MongoHandlerContext observationContext = new MongoHandlerContext(connectionString, event, requestContext);
121117
observationContext.setRemoteServiceName("mongo");
122118

@@ -141,22 +137,20 @@ public void commandStarted(CommandStartedEvent event) {
141137
@Override
142138
public void commandSucceeded(CommandSucceededEvent event) {
143139

144-
doInObservation(event.getRequestContext(), (observation, context) -> {
140+
stopObservation(event.getRequestContext(), (observation, context) -> {
145141

146142
context.setCommandSucceededEvent(event);
147143

148144
if (log.isDebugEnabled()) {
149145
log.debug("Command succeeded - will stop observation [" + observation + "]");
150146
}
151-
152-
observation.stop();
153147
});
154148
}
155149

156150
@Override
157151
public void commandFailed(CommandFailedEvent event) {
158152

159-
doInObservation(event.getRequestContext(), (observation, context) -> {
153+
stopObservation(event.getRequestContext(), (observation, context) -> {
160154

161155
context.setCommandFailedEvent(event);
162156

@@ -165,18 +159,17 @@ public void commandFailed(CommandFailedEvent event) {
165159
}
166160

167161
observation.error(event.getThrowable());
168-
observation.stop();
169162
});
170163
}
171164

172165
/**
173-
* Performs the given action for the {@link Observation} and {@link MongoHandlerContext} if there is an ongoing Mongo
174-
* Observation. Exceptions thrown by the action are relayed to the caller.
166+
* Stops the {@link Observation} after applying {@code action} given {@link MongoHandlerContext} if there is an
167+
* ongoing Mongo Observation. Exceptions thrown by the action are relayed to the caller.
175168
*
176169
* @param requestContext the context to extract the Observation from.
177170
* @param action the action to invoke.
178171
*/
179-
private void doInObservation(@Nullable RequestContext requestContext,
172+
private void stopObservation(@Nullable RequestContext requestContext,
180173
BiConsumer<Observation, MongoHandlerContext> action) {
181174

182175
if (requestContext == null) {
@@ -188,7 +181,18 @@ private void doInObservation(@Nullable RequestContext requestContext,
188181
return;
189182
}
190183

191-
action.accept(observation, context);
184+
try {
185+
action.accept(observation, context);
186+
} finally {
187+
188+
observation.stop();
189+
190+
if (log.isDebugEnabled()) {
191+
log.debug(
192+
"Restoring parent observation [" + observation + "] for Mongo instrumentation and put it in Mongo context");
193+
}
194+
requestContext.put(ObservationThreadLocalAccessor.KEY, observation.getContext().getParentObservation());
195+
}
192196
}
193197

194198
/**
@@ -211,7 +215,7 @@ private static Observation observationFromContext(RequestContext context) {
211215
}
212216

213217
if (log.isDebugEnabled()) {
214-
log.debug("No observation was found - will not create any child observations");
218+
log.debug("No observation was found: Creating a new root observation");
215219
}
216220

217221
return null;

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/MongoObservationCommandListenerTests.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.micrometer.observation.ObservationRegistry;
2828
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
2929

30+
import org.assertj.core.api.Assertions;
3031
import org.bson.BsonDocument;
3132
import org.bson.BsonString;
3233
import org.junit.jupiter.api.BeforeEach;
@@ -251,6 +252,46 @@ public String getName() {
251252
assertThat(meterRegistry).hasMeterWithName("custom.name.active");
252253
}
253254

255+
@Test // GH-5064
256+
void completionRestoresParentObservation() {
257+
258+
// given
259+
Observation parent = Observation.start("name", observationRegistry);
260+
observationRegistry.setCurrentObservationScope(parent.openScope());
261+
RequestContext traceRequestContext = getContext();
262+
263+
// when
264+
listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, 0, null, "database", "insert",
265+
new BsonDocument("collection", new BsonString("user"))));
266+
267+
Assertions.assertThat((Observation) traceRequestContext.get(ObservationThreadLocalAccessor.KEY)).isNotNull()
268+
.isNotEqualTo(parent);
269+
270+
listener.commandSucceeded(new CommandSucceededEvent(traceRequestContext, 0, 0, null, "insert", null, null, 0));
271+
272+
Assertions.assertThat((Observation) traceRequestContext.get(ObservationThreadLocalAccessor.KEY)).isEqualTo(parent);
273+
}
274+
275+
@Test // GH-5064
276+
void failureRestoresParentObservation() {
277+
278+
// given
279+
Observation parent = Observation.start("name", observationRegistry);
280+
observationRegistry.setCurrentObservationScope(parent.openScope());
281+
RequestContext traceRequestContext = getContext();
282+
283+
// when
284+
listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, 0, null, "database", "insert",
285+
new BsonDocument("collection", new BsonString("user"))));
286+
287+
Assertions.assertThat((Observation) traceRequestContext.get(ObservationThreadLocalAccessor.KEY)).isNotNull()
288+
.isNotEqualTo(parent);
289+
290+
listener.commandFailed(new CommandFailedEvent(traceRequestContext, 0, 0, null, "insert", null, 0, null));
291+
292+
Assertions.assertThat((Observation) traceRequestContext.get(ObservationThreadLocalAccessor.KEY)).isEqualTo(parent);
293+
}
294+
254295
private RequestContext getContext() {
255296
return ((SynchronousContextProvider) ContextProviderFactory.create(observationRegistry)).getContext();
256297
}

0 commit comments

Comments
 (0)