Skip to content

Commit 4b184e7

Browse files
authored
make servlet indy-ready (#14200)
1 parent 298319f commit 4b184e7

23 files changed

+485
-337
lines changed

instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Advice.java

Lines changed: 88 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55

66
package io.opentelemetry.javaagent.instrumentation.servlet.v2_2;
77

8+
import static io.opentelemetry.javaagent.instrumentation.servlet.v2_2.Servlet2Singletons.RESPONSE_STATUS;
89
import static io.opentelemetry.javaagent.instrumentation.servlet.v2_2.Servlet2Singletons.helper;
910

1011
import io.opentelemetry.context.Context;
1112
import io.opentelemetry.context.Scope;
12-
import io.opentelemetry.instrumentation.api.util.VirtualField;
1313
import io.opentelemetry.javaagent.bootstrap.CallDepth;
14-
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
1514
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
1615
import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge;
1716
import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext;
17+
import javax.annotation.Nullable;
1818
import javax.servlet.ServletRequest;
1919
import javax.servlet.ServletResponse;
2020
import javax.servlet.http.HttpServletRequest;
@@ -25,93 +25,109 @@
2525
@SuppressWarnings("unused")
2626
public class Servlet2Advice {
2727

28-
@Advice.OnMethodEnter(suppress = Throwable.class)
29-
public static void onEnter(
30-
@Advice.Argument(0) ServletRequest request,
31-
@Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC) ServletResponse response,
32-
@Advice.Local("otelCallDepth") CallDepth callDepth,
33-
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
34-
@Advice.Local("otelContext") Context context,
35-
@Advice.Local("otelScope") Scope scope) {
28+
public static class AdviceScope {
29+
30+
private final CallDepth callDepth;
31+
private final ServletRequestContext<HttpServletRequest> requestContext;
32+
private final Context context;
33+
private final Scope scope;
34+
35+
public AdviceScope(
36+
CallDepth callDepth, HttpServletRequest request, HttpServletResponse response) {
37+
this.callDepth = callDepth;
38+
callDepth.getAndIncrement();
39+
40+
Context serverContext = helper().getServerContext(request);
41+
if (serverContext != null) {
42+
Context updatedContext = helper().updateContext(serverContext, request);
43+
if (updatedContext != serverContext) {
44+
// updateContext updated context, need to re-scope
45+
scope = updatedContext.makeCurrent();
46+
} else {
47+
scope = null;
48+
}
49+
requestContext = null;
50+
context = null;
51+
return;
52+
}
3653

37-
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
38-
return;
54+
Context parentContext = Context.current();
55+
requestContext = new ServletRequestContext<>(request);
56+
57+
if (!helper().shouldStart(parentContext, requestContext)) {
58+
context = null;
59+
scope = null;
60+
return;
61+
}
62+
63+
context = helper().start(parentContext, requestContext);
64+
scope = context.makeCurrent();
65+
// reset response status from previous request
66+
// (some servlet containers reuse response objects to reduce memory allocations)
67+
RESPONSE_STATUS.set(response, null);
68+
69+
HttpServerResponseCustomizerHolder.getCustomizer()
70+
.customize(context, response, Servlet2Accessor.INSTANCE);
3971
}
4072

41-
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
73+
public void exit(
74+
@Nullable Throwable throwable, HttpServletRequest request, HttpServletResponse response) {
4275

43-
callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey());
44-
callDepth.getAndIncrement();
76+
if (scope != null) {
77+
scope.close();
78+
}
4579

46-
Context serverContext = helper().getServerContext(httpServletRequest);
47-
if (serverContext != null) {
48-
Context updatedContext = helper().updateContext(serverContext, httpServletRequest);
49-
if (updatedContext != serverContext) {
50-
// updateContext updated context, need to re-scope
51-
scope = updatedContext.makeCurrent();
80+
boolean topLevel = callDepth.decrementAndGet() == 0;
81+
if (context == null && topLevel) {
82+
Context currentContext = Context.current();
83+
// Something else is managing the context, we're in the outermost level of Servlet
84+
// instrumentation and we have an uncaught throwable. Let's add it to the current span.
85+
if (throwable != null) {
86+
helper().recordException(currentContext, throwable);
87+
}
88+
// also capture request parameters as servlet attributes
89+
helper().captureServletAttributes(currentContext, request);
5290
}
53-
return;
54-
}
5591

56-
Context parentContext = Java8BytecodeBridge.currentContext();
57-
requestContext = new ServletRequestContext<>(httpServletRequest);
92+
if (scope == null || context == null) {
93+
return;
94+
}
5895

59-
if (!helper().shouldStart(parentContext, requestContext)) {
60-
return;
61-
}
96+
int responseStatusCode = HttpServletResponse.SC_OK;
97+
Integer responseStatus = RESPONSE_STATUS.get(response);
98+
if (responseStatus != null) {
99+
responseStatusCode = responseStatus;
100+
}
62101

63-
context = helper().start(parentContext, requestContext);
64-
scope = context.makeCurrent();
65-
// reset response status from previous request
66-
// (some servlet containers reuse response objects to reduce memory allocations)
67-
VirtualField.find(ServletResponse.class, Integer.class).set(response, null);
102+
helper().end(context, requestContext, response, responseStatusCode, throwable);
103+
}
104+
}
68105

69-
HttpServerResponseCustomizerHolder.getCustomizer()
70-
.customize(context, (HttpServletResponse) response, Servlet2Accessor.INSTANCE);
106+
@Nullable
107+
@Advice.OnMethodEnter(suppress = Throwable.class)
108+
public static AdviceScope onEnter(
109+
@Advice.Argument(0) ServletRequest request,
110+
@Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC) ServletResponse response) {
111+
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
112+
return null;
113+
}
114+
return new AdviceScope(
115+
CallDepth.forClass(AppServerBridge.getCallDepthKey()),
116+
(HttpServletRequest) request,
117+
(HttpServletResponse) response);
71118
}
72119

73120
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
74121
public static void stopSpan(
75122
@Advice.Argument(0) ServletRequest request,
76123
@Advice.Argument(1) ServletResponse response,
77-
@Advice.Thrown Throwable throwable,
78-
@Advice.Local("otelCallDepth") CallDepth callDepth,
79-
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
80-
@Advice.Local("otelContext") Context context,
81-
@Advice.Local("otelScope") Scope scope) {
82-
83-
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
124+
@Advice.Thrown @Nullable Throwable throwable,
125+
@Advice.Enter @Nullable AdviceScope adviceScope) {
126+
if (adviceScope == null
127+
|| !(request instanceof HttpServletRequest)
128+
|| !(response instanceof HttpServletResponse)) {
84129
return;
85130
}
86-
87-
if (scope != null) {
88-
scope.close();
89-
}
90-
91-
boolean topLevel = callDepth.decrementAndGet() == 0;
92-
if (context == null && topLevel) {
93-
Context currentContext = Java8BytecodeBridge.currentContext();
94-
// Something else is managing the context, we're in the outermost level of Servlet
95-
// instrumentation and we have an uncaught throwable. Let's add it to the current span.
96-
if (throwable != null) {
97-
helper().recordException(currentContext, throwable);
98-
}
99-
// also capture request parameters as servlet attributes
100-
helper().captureServletAttributes(currentContext, (HttpServletRequest) request);
101-
}
102-
103-
if (scope == null || context == null) {
104-
return;
105-
}
106-
107-
int responseStatusCode = HttpServletResponse.SC_OK;
108-
Integer responseStatus = VirtualField.find(ServletResponse.class, Integer.class).get(response);
109-
if (responseStatus != null) {
110-
responseStatusCode = responseStatus;
111-
}
112-
113-
helper()
114-
.end(
115-
context, requestContext, (HttpServletResponse) response, responseStatusCode, throwable);
131+
adviceScope.exit(throwable, (HttpServletRequest) request, (HttpServletResponse) response);
116132
}
117133
}

instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2HttpServletResponseInstrumentation.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77

88
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
99
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
10+
import static io.opentelemetry.javaagent.instrumentation.servlet.v2_2.Servlet2Singletons.RESPONSE_STATUS;
1011
import static net.bytebuddy.matcher.ElementMatchers.named;
1112
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
1213

1314
import io.opentelemetry.context.Context;
1415
import io.opentelemetry.context.Scope;
15-
import io.opentelemetry.instrumentation.api.util.VirtualField;
1616
import io.opentelemetry.javaagent.bootstrap.CallDepth;
1717
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1818
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
@@ -63,7 +63,7 @@ public static class Servlet2ResponseRedirectAdvice {
6363

6464
@Advice.OnMethodEnter(suppress = Throwable.class)
6565
public static void onEnter(@Advice.This HttpServletResponse response) {
66-
VirtualField.find(ServletResponse.class, Integer.class).set(response, 302);
66+
RESPONSE_STATUS.set(response, 302);
6767
}
6868
}
6969

@@ -73,7 +73,7 @@ public static class Servlet2ResponseStatusAdvice {
7373
@Advice.OnMethodEnter(suppress = Throwable.class)
7474
public static void onEnter(
7575
@Advice.This HttpServletResponse response, @Advice.Argument(0) Integer status) {
76-
VirtualField.find(ServletResponse.class, Integer.class).set(response, status);
76+
RESPONSE_STATUS.set(response, status);
7777
}
7878
}
7979
}

instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2InstrumentationModule.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@
1111
import com.google.auto.service.AutoService;
1212
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1313
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
1415
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseInstrumentation;
1516
import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterInstrumentation;
1617
import java.util.Arrays;
1718
import java.util.List;
1819
import net.bytebuddy.matcher.ElementMatcher;
1920

2021
@AutoService(InstrumentationModule.class)
21-
public class Servlet2InstrumentationModule extends InstrumentationModule {
22+
public class Servlet2InstrumentationModule extends InstrumentationModule
23+
implements ExperimentalInstrumentationModule {
2224
private static final String BASE_PACKAGE = "javax.servlet";
2325

2426
public Servlet2InstrumentationModule() {
@@ -43,4 +45,9 @@ public List<TypeInstrumentation> typeInstrumentations() {
4345
private static String adviceClassName(String suffix) {
4446
return Servlet2InstrumentationModule.class.getPackage().getName() + suffix;
4547
}
48+
49+
@Override
50+
public boolean isIndyReady() {
51+
return true;
52+
}
4653
}

instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2ResponseSendAdvice.java

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,47 +12,61 @@
1212
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
1313
import io.opentelemetry.javaagent.bootstrap.CallDepth;
1414
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseAdviceHelper;
15+
import javax.annotation.Nullable;
1516
import javax.servlet.http.HttpServletResponse;
1617
import net.bytebuddy.asm.Advice;
1718

1819
@SuppressWarnings("unused")
1920
public class Servlet2ResponseSendAdvice {
2021

21-
@Advice.OnMethodEnter(suppress = Throwable.class)
22-
public static void start(
23-
@Advice.Origin("#t") Class<?> declaringClass,
24-
@Advice.Origin("#m") String methodName,
25-
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
26-
@Advice.Local("otelContext") Context context,
27-
@Advice.Local("otelScope") Scope scope,
28-
@Advice.Local("otelCallDepth") CallDepth callDepth) {
29-
callDepth = CallDepth.forClass(HttpServletResponse.class);
30-
if (callDepth.getAndIncrement() > 0) {
31-
return;
22+
public static class AdviceScope {
23+
private final CallDepth callDepth;
24+
private final ClassAndMethod classAndMethod;
25+
private final Context context;
26+
private final Scope scope;
27+
28+
public AdviceScope(CallDepth callDepth, Class<?> declaringClass, String methodName) {
29+
this.callDepth = callDepth;
30+
if (callDepth.getAndIncrement() > 0) {
31+
this.classAndMethod = null;
32+
this.context = null;
33+
this.scope = null;
34+
return;
35+
}
36+
37+
HttpServletResponseAdviceHelper.StartResult result =
38+
HttpServletResponseAdviceHelper.startSpan(
39+
responseInstrumenter(), declaringClass, methodName);
40+
if (result == null) {
41+
this.classAndMethod = null;
42+
this.context = null;
43+
this.scope = null;
44+
return;
45+
}
46+
this.classAndMethod = result.getClassAndMethod();
47+
this.context = result.getContext();
48+
this.scope = result.getScope();
3249
}
3350

34-
HttpServletResponseAdviceHelper.StartResult result =
35-
HttpServletResponseAdviceHelper.startSpan(
36-
responseInstrumenter(), declaringClass, methodName);
37-
if (result != null) {
38-
classAndMethod = result.getClassAndMethod();
39-
context = result.getContext();
40-
scope = result.getScope();
51+
public void exit(@Nullable Throwable throwable) {
52+
if (callDepth.decrementAndGet() > 0) {
53+
return;
54+
}
55+
HttpServletResponseAdviceHelper.stopSpan(
56+
responseInstrumenter(), throwable, context, scope, classAndMethod);
4157
}
4258
}
4359

60+
@Advice.OnMethodEnter(suppress = Throwable.class)
61+
public static AdviceScope start(
62+
@Advice.Origin("#t") Class<?> declaringClass, @Advice.Origin("#m") String methodName) {
63+
return new AdviceScope(
64+
CallDepth.forClass(HttpServletResponse.class), declaringClass, methodName);
65+
}
66+
4467
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
4568
public static void stopSpan(
46-
@Advice.Thrown Throwable throwable,
47-
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
48-
@Advice.Local("otelContext") Context context,
49-
@Advice.Local("otelScope") Scope scope,
50-
@Advice.Local("otelCallDepth") CallDepth callDepth) {
51-
if (callDepth.decrementAndGet() > 0) {
52-
return;
53-
}
54-
55-
HttpServletResponseAdviceHelper.stopSpan(
56-
responseInstrumenter(), throwable, context, scope, classAndMethod);
69+
@Advice.Thrown @Nullable Throwable throwable, @Advice.Enter AdviceScope adviceScope) {
70+
adviceScope.exit(throwable);
5771
}
5872
}

instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Singletons.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,21 @@
88
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
99
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
1010
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
11+
import io.opentelemetry.instrumentation.api.util.VirtualField;
1112
import io.opentelemetry.javaagent.instrumentation.servlet.ServletInstrumenterBuilder;
1213
import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext;
1314
import io.opentelemetry.javaagent.instrumentation.servlet.ServletResponseContext;
1415
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.ResponseInstrumenterFactory;
16+
import javax.servlet.ServletResponse;
1517
import javax.servlet.http.HttpServletRequest;
1618
import javax.servlet.http.HttpServletResponse;
1719

1820
public final class Servlet2Singletons {
1921
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.servlet-2.2";
2022

23+
public static final VirtualField<ServletResponse, Integer> RESPONSE_STATUS =
24+
VirtualField.find(ServletResponse.class, Integer.class);
25+
2126
private static final Servlet2Helper HELPER;
2227
private static final Instrumenter<ClassAndMethod, Void> RESPONSE_INSTRUMENTER;
2328

0 commit comments

Comments
 (0)