Skip to content

Commit 92b0afc

Browse files
committed
Start spring webmvc update
1 parent 9ea03e9 commit 92b0afc

File tree

6 files changed

+120
-7
lines changed

6 files changed

+120
-7
lines changed

instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/build.gradle.kts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ dependencies {
3838
testLibrary("org.springframework.boot:spring-boot-starter-web:3.0.0")
3939
testLibrary("org.springframework.boot:spring-boot-starter-security:3.0.0")
4040

41-
// tests don't work with spring boot 4 yet
42-
latestDepTestLibrary("org.springframework.boot:spring-boot-starter-test:3.+") // documented limitation
43-
latestDepTestLibrary("org.springframework.boot:spring-boot-starter-web:3.+") // documented limitation
44-
latestDepTestLibrary("org.springframework.boot:spring-boot-starter-security:3.+") // documented limitation
41+
// // tests don't work with spring boot 4 yet
42+
// latestDepTestLibrary("org.springframework.boot:spring-boot-starter-test:3.+") // documented limitation
43+
// latestDepTestLibrary("org.springframework.boot:spring-boot-starter-web:3.+") // documented limitation
44+
// latestDepTestLibrary("org.springframework.boot:spring-boot-starter-security:3.+") // documented limitation
4545
}
4646

4747
// spring 6 requires java 17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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.named;
11+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
12+
13+
import io.opentelemetry.context.Context;
14+
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
15+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
16+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
17+
import javax.annotation.Nullable;
18+
import net.bytebuddy.asm.Advice;
19+
import net.bytebuddy.description.type.TypeDescription;
20+
import net.bytebuddy.matcher.ElementMatcher;
21+
22+
public class WebAsyncManagerInstrumentation implements TypeInstrumentation {
23+
24+
@Override
25+
public ElementMatcher<ClassLoader> classLoaderOptimization() {
26+
return hasClassesNamed("org.springframework.web.context.request.async.DeferredResult");
27+
}
28+
29+
@Override
30+
public ElementMatcher<TypeDescription> typeMatcher() {
31+
return named("org.springframework.web.context.request.async.DeferredResult");
32+
}
33+
34+
@Override
35+
public void transform(TypeTransformer transformer) {
36+
// Instrument setResult to capture the context when the async result is set
37+
// This is called from async code (e.g., in TestBean.asyncCall()) and we need to
38+
// capture the context that includes the async work span so it can be used for the redispatch
39+
transformer.applyAdviceToMethod(
40+
isMethod().and(named("setResult")).and(takesArguments(1)),
41+
WebAsyncManagerInstrumentation.class.getName() + "$SetResultAdvice");
42+
}
43+
44+
@SuppressWarnings("unused")
45+
public static class SetResultAdvice {
46+
47+
@Advice.OnMethodEnter(suppress = Throwable.class)
48+
public static void onEnter() {
49+
// Capture the current context when DeferredResult.setResult() is called
50+
// This is the context that includes the async work (e.g., the async-call-child span)
51+
// and should be used when the handler is redispatched
52+
Context currentContext = Java8BytecodeBridge.currentContext();
53+
WebAsyncManagerContext.setViaRequestContextHolder(currentContext);
54+
}
55+
}
56+
57+
// Helper class to store and retrieve context for async processing
58+
public static class WebAsyncManagerContext {
59+
public static final String CONTEXT_ATTRIBUTE =
60+
"io.opentelemetry.javaagent.spring.webmvc.async.Context";
61+
62+
private WebAsyncManagerContext() {}
63+
64+
static void setViaRequestContextHolder(Context context) {
65+
// Use Spring's RequestContextHolder to access the current request and store the context
66+
try {
67+
Class<?> requestContextHolderClass =
68+
Class.forName("org.springframework.web.context.request.RequestContextHolder");
69+
Object requestAttributes =
70+
requestContextHolderClass.getMethod("getRequestAttributes").invoke(null);
71+
if (requestAttributes != null) {
72+
requestAttributes
73+
.getClass()
74+
.getMethod("setAttribute", String.class, Object.class, int.class)
75+
.invoke(requestAttributes, CONTEXT_ATTRIBUTE, context, 0); // 0 = REQUEST_SCOPE
76+
}
77+
} catch (Exception e) {
78+
// Ignore - best effort
79+
}
80+
}
81+
82+
@Nullable
83+
public static Context getFromRequest(Object request) {
84+
try {
85+
return (Context)
86+
request
87+
.getClass()
88+
.getMethod("getAttribute", String.class)
89+
.invoke(request, CONTEXT_ATTRIBUTE);
90+
} catch (Exception e) {
91+
// Ignore - best effort
92+
return null;
93+
}
94+
}
95+
}
96+
}

instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/filter/ServletFilterConfig.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
@Configuration
2525
class ServletFilterConfig {
2626

27+
private static final boolean testLatestDeps = Boolean.getBoolean("testLatestDeps");
28+
2729
@Bean
2830
Filter servletFilter() {
2931
return new Filter() {
@@ -54,6 +56,9 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
5456
} else if (endpoint == ServerEndpoint.ERROR) {
5557
resp.sendError(endpoint.getStatus(), endpoint.getBody());
5658
} else if (endpoint == ServerEndpoint.EXCEPTION) {
59+
if (testLatestDeps) {
60+
throw new IllegalArgumentException(endpoint.getBody());
61+
}
5762
throw new IllegalStateException(endpoint.getBody());
5863
} else if (endpoint == ServerEndpoint.INDEXED_CHILD) {
5964
INDEXED_CHILD.collectSpanAttributes(req::getParameter);

instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/filter/ServletFilterTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ protected Class<?> filterConfigClass() {
5050
protected ConfigurableApplicationContext setupServer() {
5151
SpringApplication app =
5252
new SpringApplication(FilteredAppConfig.class, securityConfigClass(), filterConfigClass());
53-
app.setDefaultProperties(Map.of("server.port", port, "server.error.include-message", "always"));
53+
app.setDefaultProperties(
54+
Map.of("server.port", port, "spring.web.error.include-message", "always"));
5455
return app.run();
5556
}
5657

@@ -96,5 +97,9 @@ protected SpanDataAssert assertResponseSpan(
9697
protected void configure(HttpServerTestOptions options) {
9798
super.configure(options);
9899
options.setResponseCodeOnNonStandardHttpMethod(400);
100+
if (testLatestDeps) {
101+
options.setExpectedException(
102+
new IllegalArgumentException(ServerEndpoint.EXCEPTION.getBody()));
103+
}
99104
}
100105
}

instrumentation/spring/spring-webmvc/spring-webmvc-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/filter/FilteredAppConfig.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,8 @@ public void configurePathMatch(PathMatchConfigurer configurer) {}
4545
@Override
4646
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
4747
configurer
48-
.favorPathExtension(false)
4948
.favorParameter(true)
5049
.ignoreAcceptHeader(true)
51-
.useJaf(false)
5250
.defaultContentTypeStrategy(webRequest -> Collections.singletonList(MediaType.TEXT_PLAIN));
5351
}
5452

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,19 @@ public void configure(IgnoredTypesBuilder builder) {
105105
.allowClass("org.springframework.boot.logging.logback.")
106106
.allowClass("org.springframework.boot.web.filter.")
107107
.allowClass("org.springframework.boot.web.servlet.")
108+
.allowClass("org.springframework.boot.servlet.filter.")
109+
.allowClass("org.springframework.boot.web.server.servlet.context.")
108110
.allowClass("org.springframework.boot.web.embedded.netty.GracefulShutdown$$Lambda")
109111
.allowClass("org.springframework.boot.web.embedded.tomcat.GracefulShutdown$$Lambda")
112+
.allowClass("org.springframework.boot.tomcat.GracefulShutdown$$Lambda")
113+
.allowClass("org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory$$Lambda")
110114
.allowClass(
111115
"org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory$$Lambda")
112116
.allowClass(
113117
"org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter$$Lambda")
114118
.allowClass("org.springframework.boot.autoconfigure.BackgroundPreinitializer$")
119+
.allowClass(
120+
"org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializingApplicationListener$")
115121
.allowClass(
116122
"org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration$$Lambda")
117123
.allowClass("org.springframework.boot.autoconfigure.condition.OnClassCondition$")
@@ -121,6 +127,9 @@ public void configure(IgnoredTypesBuilder builder) {
121127
"org.springframework.boot.autoconfigure.web.WebProperties$Resources$Cache$Cachecontrol$$Lambda")
122128
.allowClass("org.springframework.boot.web.embedded.netty.NettyWebServer$")
123129
.allowClass("org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext$$Lambda")
130+
.allowClass("org.springframework.boot.tomcat.TomcatEmbeddedContext$$Lambda")
131+
.allowClass("org.springframework.boot.tomcat.TomcatWebServer$")
132+
.allowClass("org.springframework.boot.tomcat.TomcatEmbeddedWebappClassLoader")
124133
.allowClass(
125134
"org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer$")
126135
.allowClass(

0 commit comments

Comments
 (0)