Skip to content

Commit 8e1f5e1

Browse files
committed
more trying
1 parent 89d6ac9 commit 8e1f5e1

File tree

2 files changed

+103
-1
lines changed

2 files changed

+103
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
9+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
10+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
11+
import static net.bytebuddy.matcher.ElementMatchers.named;
12+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
13+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
14+
15+
import io.opentelemetry.context.Context;
16+
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
17+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
18+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
19+
import jakarta.servlet.http.HttpServletRequest;
20+
import net.bytebuddy.asm.Advice;
21+
import net.bytebuddy.description.type.TypeDescription;
22+
import net.bytebuddy.matcher.ElementMatcher;
23+
24+
/**
25+
* Instruments DeferredResult.setResult() to capture the context at the moment the result is set.
26+
* This is necessary because Spring's AsyncContext.dispatch() may be called asynchronously after the
27+
* span that set the result has already ended, leading to incorrect context propagation.
28+
*
29+
* <p>The captured context is stored directly in the ServletRequest attribute, overwriting whatever
30+
* context was there before. This ensures that when AsyncContext.dispatch() is called and
31+
* AsyncDispatchAdvice runs, it will use the correct context (the one that was active when
32+
* setResult() was called, which includes any async work spans).
33+
*/
34+
public class DeferredResultInstrumentation implements TypeInstrumentation {
35+
36+
@Override
37+
public ElementMatcher<ClassLoader> classLoaderOptimization() {
38+
return hasClassesNamed("org.springframework.web.context.request.async.DeferredResult");
39+
}
40+
41+
@Override
42+
public ElementMatcher<TypeDescription> typeMatcher() {
43+
return named("org.springframework.web.context.request.async.DeferredResult");
44+
}
45+
46+
@Override
47+
public void transform(TypeTransformer transformer) {
48+
transformer.applyAdviceToMethod(
49+
isMethod()
50+
.and(isPublic())
51+
.and(named("setResult"))
52+
.and(takesArguments(1))
53+
.and(takesArgument(0, Object.class)),
54+
DeferredResultInstrumentation.class.getName() + "$SetResultAdvice");
55+
}
56+
57+
@SuppressWarnings("unused")
58+
public static class SetResultAdvice {
59+
60+
@Advice.OnMethodEnter(suppress = Throwable.class)
61+
public static void onEnter() {
62+
// Capture the current context at the moment setResult() is called.
63+
// This context includes any spans that are currently active (e.g., async work spans).
64+
Context currentContext = Java8BytecodeBridge.currentContext();
65+
66+
// Try to get the current HttpServletRequest from Spring's RequestContextHolder
67+
// and update its context attribute. This ensures that when AsyncContext.dispatch()
68+
// is called (possibly after this method returns), AsyncDispatchAdvice will use
69+
// the correct context.
70+
try {
71+
Class<?> requestContextHolderClass =
72+
Class.forName(
73+
"org.springframework.web.context.request.RequestContextHolder", false, null);
74+
Object requestAttributes =
75+
requestContextHolderClass.getMethod("getRequestAttributes").invoke(null);
76+
77+
if (requestAttributes != null) {
78+
Class<?> servletRequestAttributesClass =
79+
Class.forName(
80+
"org.springframework.web.context.request.ServletRequestAttributes", false, null);
81+
if (servletRequestAttributesClass.isInstance(requestAttributes)) {
82+
HttpServletRequest request =
83+
(HttpServletRequest)
84+
servletRequestAttributesClass.getMethod("getRequest").invoke(requestAttributes);
85+
if (request != null) {
86+
// Update the servlet request attribute with the current context
87+
request.setAttribute(
88+
"io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper.Context",
89+
currentContext);
90+
}
91+
}
92+
}
93+
} catch (Exception ignored) {
94+
// If we can't get the request, silently fail. The existing instrumentation
95+
// will still work, just without the improved async context propagation.
96+
}
97+
}
98+
}
99+
}

instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/SpringWebMvcInstrumentationModule.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ public String getModuleGroup() {
5151

5252
@Override
5353
public List<TypeInstrumentation> typeInstrumentations() {
54-
return asList(new DispatcherServletInstrumentation(), new HandlerAdapterInstrumentation());
54+
return asList(
55+
new DispatcherServletInstrumentation(),
56+
new HandlerAdapterInstrumentation(),
57+
new DeferredResultInstrumentation());
5558
}
5659

5760
@Override

0 commit comments

Comments
 (0)