make servlet indy-ready (#14200)

This commit is contained in:
SylvainJuge 2025-07-15 04:48:56 +02:00 committed by GitHub
parent 298319f7b0
commit 4b184e7569
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 487 additions and 339 deletions

View File

@ -5,16 +5,16 @@
package io.opentelemetry.javaagent.instrumentation.servlet.v2_2;
import static io.opentelemetry.javaagent.instrumentation.servlet.v2_2.Servlet2Singletons.RESPONSE_STATUS;
import static io.opentelemetry.javaagent.instrumentation.servlet.v2_2.Servlet2Singletons.helper;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.util.VirtualField;
import io.opentelemetry.javaagent.bootstrap.CallDepth;
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge;
import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext;
import javax.annotation.Nullable;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
@ -25,93 +25,109 @@ import net.bytebuddy.implementation.bytecode.assign.Assigner;
@SuppressWarnings("unused")
public class Servlet2Advice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) ServletRequest request,
@Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC) ServletResponse response,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
public static class AdviceScope {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
return;
}
private final CallDepth callDepth;
private final ServletRequestContext<HttpServletRequest> requestContext;
private final Context context;
private final Scope scope;
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
public AdviceScope(
CallDepth callDepth, HttpServletRequest request, HttpServletResponse response) {
this.callDepth = callDepth;
callDepth.getAndIncrement();
callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey());
callDepth.getAndIncrement();
Context serverContext = helper().getServerContext(httpServletRequest);
if (serverContext != null) {
Context updatedContext = helper().updateContext(serverContext, httpServletRequest);
if (updatedContext != serverContext) {
// updateContext updated context, need to re-scope
scope = updatedContext.makeCurrent();
Context serverContext = helper().getServerContext(request);
if (serverContext != null) {
Context updatedContext = helper().updateContext(serverContext, request);
if (updatedContext != serverContext) {
// updateContext updated context, need to re-scope
scope = updatedContext.makeCurrent();
} else {
scope = null;
}
requestContext = null;
context = null;
return;
}
return;
Context parentContext = Context.current();
requestContext = new ServletRequestContext<>(request);
if (!helper().shouldStart(parentContext, requestContext)) {
context = null;
scope = null;
return;
}
context = helper().start(parentContext, requestContext);
scope = context.makeCurrent();
// reset response status from previous request
// (some servlet containers reuse response objects to reduce memory allocations)
RESPONSE_STATUS.set(response, null);
HttpServerResponseCustomizerHolder.getCustomizer()
.customize(context, response, Servlet2Accessor.INSTANCE);
}
Context parentContext = Java8BytecodeBridge.currentContext();
requestContext = new ServletRequestContext<>(httpServletRequest);
public void exit(
@Nullable Throwable throwable, HttpServletRequest request, HttpServletResponse response) {
if (!helper().shouldStart(parentContext, requestContext)) {
return;
if (scope != null) {
scope.close();
}
boolean topLevel = callDepth.decrementAndGet() == 0;
if (context == null && topLevel) {
Context currentContext = Context.current();
// Something else is managing the context, we're in the outermost level of Servlet
// instrumentation and we have an uncaught throwable. Let's add it to the current span.
if (throwable != null) {
helper().recordException(currentContext, throwable);
}
// also capture request parameters as servlet attributes
helper().captureServletAttributes(currentContext, request);
}
if (scope == null || context == null) {
return;
}
int responseStatusCode = HttpServletResponse.SC_OK;
Integer responseStatus = RESPONSE_STATUS.get(response);
if (responseStatus != null) {
responseStatusCode = responseStatus;
}
helper().end(context, requestContext, response, responseStatusCode, throwable);
}
}
context = helper().start(parentContext, requestContext);
scope = context.makeCurrent();
// reset response status from previous request
// (some servlet containers reuse response objects to reduce memory allocations)
VirtualField.find(ServletResponse.class, Integer.class).set(response, null);
HttpServerResponseCustomizerHolder.getCustomizer()
.customize(context, (HttpServletResponse) response, Servlet2Accessor.INSTANCE);
@Nullable
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AdviceScope onEnter(
@Advice.Argument(0) ServletRequest request,
@Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC) ServletResponse response) {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
return null;
}
return new AdviceScope(
CallDepth.forClass(AppServerBridge.getCallDepthKey()),
(HttpServletRequest) request,
(HttpServletResponse) response);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Argument(0) ServletRequest request,
@Advice.Argument(1) ServletResponse response,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
@Advice.Thrown @Nullable Throwable throwable,
@Advice.Enter @Nullable AdviceScope adviceScope) {
if (adviceScope == null
|| !(request instanceof HttpServletRequest)
|| !(response instanceof HttpServletResponse)) {
return;
}
if (scope != null) {
scope.close();
}
boolean topLevel = callDepth.decrementAndGet() == 0;
if (context == null && topLevel) {
Context currentContext = Java8BytecodeBridge.currentContext();
// Something else is managing the context, we're in the outermost level of Servlet
// instrumentation and we have an uncaught throwable. Let's add it to the current span.
if (throwable != null) {
helper().recordException(currentContext, throwable);
}
// also capture request parameters as servlet attributes
helper().captureServletAttributes(currentContext, (HttpServletRequest) request);
}
if (scope == null || context == null) {
return;
}
int responseStatusCode = HttpServletResponse.SC_OK;
Integer responseStatus = VirtualField.find(ServletResponse.class, Integer.class).get(response);
if (responseStatus != null) {
responseStatusCode = responseStatus;
}
helper()
.end(
context, requestContext, (HttpServletResponse) response, responseStatusCode, throwable);
adviceScope.exit(throwable, (HttpServletRequest) request, (HttpServletResponse) response);
}
}

View File

@ -7,12 +7,12 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v2_2;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
import static io.opentelemetry.javaagent.instrumentation.servlet.v2_2.Servlet2Singletons.RESPONSE_STATUS;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.util.VirtualField;
import io.opentelemetry.javaagent.bootstrap.CallDepth;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
@ -63,7 +63,7 @@ public class Servlet2HttpServletResponseInstrumentation implements TypeInstrumen
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(@Advice.This HttpServletResponse response) {
VirtualField.find(ServletResponse.class, Integer.class).set(response, 302);
RESPONSE_STATUS.set(response, 302);
}
}
@ -73,7 +73,7 @@ public class Servlet2HttpServletResponseInstrumentation implements TypeInstrumen
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.This HttpServletResponse response, @Advice.Argument(0) Integer status) {
VirtualField.find(ServletResponse.class, Integer.class).set(response, status);
RESPONSE_STATUS.set(response, status);
}
}
}

View File

@ -11,6 +11,7 @@ import static net.bytebuddy.matcher.ElementMatchers.not;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseInstrumentation;
import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterInstrumentation;
import java.util.Arrays;
@ -18,7 +19,8 @@ import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(InstrumentationModule.class)
public class Servlet2InstrumentationModule extends InstrumentationModule {
public class Servlet2InstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {
private static final String BASE_PACKAGE = "javax.servlet";
public Servlet2InstrumentationModule() {
@ -43,4 +45,9 @@ public class Servlet2InstrumentationModule extends InstrumentationModule {
private static String adviceClassName(String suffix) {
return Servlet2InstrumentationModule.class.getPackage().getName() + suffix;
}
@Override
public boolean isIndyReady() {
return true;
}
}

View File

@ -12,47 +12,61 @@ import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
import io.opentelemetry.javaagent.bootstrap.CallDepth;
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseAdviceHelper;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletResponse;
import net.bytebuddy.asm.Advice;
@SuppressWarnings("unused")
public class Servlet2ResponseSendAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void start(
@Advice.Origin("#t") Class<?> declaringClass,
@Advice.Origin("#m") String methodName,
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Local("otelCallDepth") CallDepth callDepth) {
callDepth = CallDepth.forClass(HttpServletResponse.class);
if (callDepth.getAndIncrement() > 0) {
return;
public static class AdviceScope {
private final CallDepth callDepth;
private final ClassAndMethod classAndMethod;
private final Context context;
private final Scope scope;
public AdviceScope(CallDepth callDepth, Class<?> declaringClass, String methodName) {
this.callDepth = callDepth;
if (callDepth.getAndIncrement() > 0) {
this.classAndMethod = null;
this.context = null;
this.scope = null;
return;
}
HttpServletResponseAdviceHelper.StartResult result =
HttpServletResponseAdviceHelper.startSpan(
responseInstrumenter(), declaringClass, methodName);
if (result == null) {
this.classAndMethod = null;
this.context = null;
this.scope = null;
return;
}
this.classAndMethod = result.getClassAndMethod();
this.context = result.getContext();
this.scope = result.getScope();
}
HttpServletResponseAdviceHelper.StartResult result =
HttpServletResponseAdviceHelper.startSpan(
responseInstrumenter(), declaringClass, methodName);
if (result != null) {
classAndMethod = result.getClassAndMethod();
context = result.getContext();
scope = result.getScope();
public void exit(@Nullable Throwable throwable) {
if (callDepth.decrementAndGet() > 0) {
return;
}
HttpServletResponseAdviceHelper.stopSpan(
responseInstrumenter(), throwable, context, scope, classAndMethod);
}
}
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AdviceScope start(
@Advice.Origin("#t") Class<?> declaringClass, @Advice.Origin("#m") String methodName) {
return new AdviceScope(
CallDepth.forClass(HttpServletResponse.class), declaringClass, methodName);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Local("otelCallDepth") CallDepth callDepth) {
if (callDepth.decrementAndGet() > 0) {
return;
}
HttpServletResponseAdviceHelper.stopSpan(
responseInstrumenter(), throwable, context, scope, classAndMethod);
@Advice.Thrown @Nullable Throwable throwable, @Advice.Enter AdviceScope adviceScope) {
adviceScope.exit(throwable);
}
}

View File

@ -8,16 +8,21 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v2_2;
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.util.VirtualField;
import io.opentelemetry.javaagent.instrumentation.servlet.ServletInstrumenterBuilder;
import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext;
import io.opentelemetry.javaagent.instrumentation.servlet.ServletResponseContext;
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.ResponseInstrumenterFactory;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public final class Servlet2Singletons {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.servlet-2.2";
public static final VirtualField<ServletResponse, Integer> RESPONSE_STATUS =
VirtualField.find(ServletResponse.class, Integer.class);
private static final Servlet2Helper HELPER;
private static final Instrumenter<ClassAndMethod, Void> RESPONSE_INSTRUMENTER;

View File

@ -19,13 +19,11 @@ import net.bytebuddy.asm.Advice;
public class AsyncDispatchAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void enter(
@Advice.This AsyncContext context,
@Advice.AllArguments Object[] args,
@Advice.Local("otelCallDepth") CallDepth callDepth) {
callDepth = CallDepth.forClass(AsyncContext.class);
public static CallDepth enter(
@Advice.This AsyncContext context, @Advice.AllArguments Object[] args) {
CallDepth callDepth = CallDepth.forClass(AsyncContext.class);
if (callDepth.getAndIncrement() > 0) {
return;
return callDepth;
}
ServletRequest request = context.getRequest();
@ -42,10 +40,11 @@ public class AsyncDispatchAdvice {
// processing, and nothing can be done with the request anymore after this
request.setAttribute(CONTEXT_ATTRIBUTE, currentContext);
}
return callDepth;
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void exit(@Advice.Local("otelCallDepth") CallDepth callDepth) {
public static void exit(@Advice.Enter CallDepth callDepth) {
callDepth.decrementAndGet();
}
}

View File

@ -11,37 +11,101 @@ import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Si
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.bootstrap.CallDepth;
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge;
import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver;
import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext;
import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Servlet3SnippetInjectingResponseWrapper;
import javax.annotation.Nullable;
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.AssignReturned;
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
@SuppressWarnings("unused")
public class Servlet3Advice {
public static class AdviceScope {
private final CallDepth callDepth;
private final ServletRequestContext<HttpServletRequest> requestContext;
private final Context context;
private final Scope scope;
public AdviceScope(
CallDepth callDepth,
HttpServletRequest request,
HttpServletResponse response,
Object servletOrFilter) {
this.callDepth = callDepth;
this.callDepth.getAndIncrement();
Context currentContext = Context.current();
Context attachedContext = helper().getServerContext(request);
Context contextToUpdate;
requestContext = new ServletRequestContext<>(request, servletOrFilter);
if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) {
context = helper().start(currentContext, requestContext);
helper().setAsyncListenerResponse(context, response);
contextToUpdate = context;
} else if (attachedContext != null
&& helper().needsRescoping(currentContext, attachedContext)) {
// Given request already has a context associated with it.
// see the needsRescoping() javadoc for more explanation
contextToUpdate = attachedContext;
context = null;
} else {
// We are inside nested servlet/filter/app-server span, don't create new span
contextToUpdate = currentContext;
context = null;
}
// Update context with info from current request to ensure that server span gets the best
// possible name.
// In case server span was created by app server instrumentations calling updateContext
// returns a new context that contains servlet context path that is used in other
// instrumentations for naming server span.
MappingResolver mappingResolver = Servlet3Singletons.getMappingResolver(servletOrFilter);
boolean servlet = servletOrFilter instanceof Servlet;
contextToUpdate = helper().updateContext(contextToUpdate, request, mappingResolver, servlet);
scope = contextToUpdate.makeCurrent();
if (context != null) {
// Only trigger response customizer once, so only if server span was created here
HttpServerResponseCustomizerHolder.getCustomizer()
.customize(contextToUpdate, response, Servlet3Accessor.INSTANCE);
}
}
public void exit(
@Nullable Throwable throwable, HttpServletRequest request, HttpServletResponse response) {
boolean topLevel = callDepth.decrementAndGet() == 0;
helper().end(requestContext, request, response, throwable, topLevel, context, scope);
}
}
@AssignReturned.ToArguments({
@ToArgument(value = 0, index = 1),
@ToArgument(value = 1, index = 2)
})
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
public static Object[] onEnter(
@Advice.This(typing = Assigner.Typing.DYNAMIC) Object servletOrFilter,
@Advice.Argument(value = 0, readOnly = false) ServletRequest request,
@Advice.Argument(value = 1, readOnly = false) ServletResponse response,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
@Advice.Argument(0) ServletRequest request,
@Advice.Argument(1) ServletResponse originalResponse) {
ServletResponse response = originalResponse;
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
return;
return new Object[] {null, request, response};
}
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String snippet = getSnippetInjectionHelper().getSnippet();
if (!snippet.isEmpty()
@ -50,70 +114,27 @@ public class Servlet3Advice {
response =
new Servlet3SnippetInjectingResponseWrapper((HttpServletResponse) response, snippet);
}
callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey());
callDepth.getAndIncrement();
Context currentContext = Java8BytecodeBridge.currentContext();
Context attachedContext = helper().getServerContext(httpServletRequest);
Context contextToUpdate;
requestContext = new ServletRequestContext<>(httpServletRequest, servletOrFilter);
if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) {
context = helper().start(currentContext, requestContext);
helper().setAsyncListenerResponse(context, (HttpServletResponse) response);
contextToUpdate = context;
} else if (attachedContext != null
&& helper().needsRescoping(currentContext, attachedContext)) {
// Given request already has a context associated with it.
// see the needsRescoping() javadoc for more explanation
contextToUpdate = attachedContext;
} else {
// We are inside nested servlet/filter/app-server span, don't create new span
contextToUpdate = currentContext;
}
// Update context with info from current request to ensure that server span gets the best
// possible name.
// In case server span was created by app server instrumentations calling updateContext
// returns a new context that contains servlet context path that is used in other
// instrumentations for naming server span.
MappingResolver mappingResolver = Servlet3Singletons.getMappingResolver(servletOrFilter);
boolean servlet = servletOrFilter instanceof Servlet;
contextToUpdate =
helper().updateContext(contextToUpdate, httpServletRequest, mappingResolver, servlet);
scope = contextToUpdate.makeCurrent();
if (context != null) {
// Only trigger response customizer once, so only if server span was created here
HttpServerResponseCustomizerHolder.getCustomizer()
.customize(contextToUpdate, (HttpServletResponse) response, Servlet3Accessor.INSTANCE);
}
AdviceScope adviceScope =
new AdviceScope(
CallDepth.forClass(AppServerBridge.getCallDepthKey()),
(HttpServletRequest) request,
(HttpServletResponse) response,
servletOrFilter);
return new Object[] {adviceScope, request, response};
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Argument(0) ServletRequest request,
@Advice.Argument(1) ServletResponse response,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
@Advice.Thrown @Nullable Throwable throwable,
@Advice.Enter Object[] enterResult) {
AdviceScope adviceScope = (AdviceScope) enterResult[0];
if (adviceScope == null
|| !(request instanceof HttpServletRequest)
|| !(response instanceof HttpServletResponse)) {
return;
}
boolean topLevel = callDepth.decrementAndGet() == 0;
helper()
.end(
requestContext,
(HttpServletRequest) request,
(HttpServletResponse) response,
throwable,
topLevel,
context,
scope);
adviceScope.exit(throwable, (HttpServletRequest) request, (HttpServletResponse) response);
}
}

View File

@ -8,12 +8,15 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.AssignReturned;
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
@SuppressWarnings("unused")
public class Servlet3AsyncContextStartAdvice {
@AssignReturned.ToArguments(@ToArgument(0))
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void start(@Advice.Argument(value = 0, readOnly = false) Runnable runnable) {
runnable = helper().wrapAsyncRunnable(runnable);
public static Runnable start(@Advice.Argument(0) Runnable runnable) {
return helper().wrapAsyncRunnable(runnable);
}
}

View File

@ -18,16 +18,16 @@ import net.bytebuddy.asm.Advice;
public class Servlet3AsyncStartAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void startAsyncEnter(@Advice.Local("otelCallDepth") CallDepth callDepth) {
public static CallDepth startAsyncEnter() {
CallDepth callDepth = CallDepth.forClass(AsyncContext.class);
// This allows to detect the outermost invocation of startAsync in method exit
callDepth = CallDepth.forClass(AsyncContext.class);
callDepth.getAndIncrement();
return callDepth;
}
@Advice.OnMethodExit(suppress = Throwable.class)
public static void startAsyncExit(
@Advice.This ServletRequest servletRequest,
@Advice.Local("otelCallDepth") CallDepth callDepth) {
@Advice.This ServletRequest servletRequest, @Advice.Enter CallDepth callDepth) {
if (callDepth.decrementAndGet() != 0) {
// This is not the outermost invocation, ignore.

View File

@ -5,8 +5,8 @@
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
import io.opentelemetry.instrumentation.api.util.VirtualField;
import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver;
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.FILTER_MAPPING_RESOLVER_FACTORY;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import net.bytebuddy.asm.Advice;
@ -20,7 +20,7 @@ public class Servlet3FilterInitAdvice {
if (filterConfig == null) {
return;
}
VirtualField.find(Filter.class, MappingResolver.Factory.class)
.set(filter, new Servlet3FilterMappingResolverFactory(filterConfig));
FILTER_MAPPING_RESOLVER_FACTORY.set(
filter, new Servlet3FilterMappingResolverFactory(filterConfig));
}
}

View File

@ -5,8 +5,8 @@
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0;
import io.opentelemetry.instrumentation.api.util.VirtualField;
import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver;
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.SERVLET_MAPPING_RESOLVER_FACTORY;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import net.bytebuddy.asm.Advice;
@ -20,7 +20,7 @@ public class Servlet3InitAdvice {
if (servletConfig == null) {
return;
}
VirtualField.find(Servlet.class, MappingResolver.Factory.class)
.set(servlet, new Servlet3MappingResolverFactory(servletConfig));
SERVLET_MAPPING_RESOLVER_FACTORY.set(
servlet, new Servlet3MappingResolverFactory(servletConfig));
}
}

View File

@ -65,4 +65,9 @@ public class Servlet3InstrumentationModule extends InstrumentationModule
private static String adviceClassName(String suffix) {
return Servlet3InstrumentationModule.class.getPackage().getName() + suffix;
}
@Override
public boolean isIndyReady() {
return true;
}
}

View File

@ -12,47 +12,60 @@ import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
import io.opentelemetry.javaagent.bootstrap.CallDepth;
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseAdviceHelper;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletResponse;
import net.bytebuddy.asm.Advice;
@SuppressWarnings("unused")
public class Servlet3ResponseSendAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void start(
@Advice.Origin("#t") Class<?> declaringClass,
@Advice.Origin("#m") String methodName,
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Local("otelCallDepth") CallDepth callDepth) {
callDepth = CallDepth.forClass(HttpServletResponse.class);
if (callDepth.getAndIncrement() > 0) {
return;
public static class AdviceScope {
private final CallDepth callDepth;
private final ClassAndMethod classAndMethod;
private final Context context;
private final Scope scope;
public AdviceScope(CallDepth callDepth, Class<?> declaringClass, String methodName) {
this.callDepth = callDepth;
if (callDepth.getAndIncrement() > 0) {
this.classAndMethod = null;
this.context = null;
this.scope = null;
return;
}
HttpServletResponseAdviceHelper.StartResult result =
HttpServletResponseAdviceHelper.startSpan(
responseInstrumenter(), declaringClass, methodName);
if (result != null) {
classAndMethod = result.getClassAndMethod();
context = result.getContext();
scope = result.getScope();
} else {
classAndMethod = null;
context = null;
scope = null;
}
}
HttpServletResponseAdviceHelper.StartResult result =
HttpServletResponseAdviceHelper.startSpan(
responseInstrumenter(), declaringClass, methodName);
if (result != null) {
classAndMethod = result.getClassAndMethod();
context = result.getContext();
scope = result.getScope();
public void exit(@Nullable Throwable throwable) {
if (callDepth.decrementAndGet() > 0) {
return;
}
HttpServletResponseAdviceHelper.stopSpan(
responseInstrumenter(), throwable, context, scope, classAndMethod);
}
}
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AdviceScope start(
@Advice.Origin("#t") Class<?> declaringClass, @Advice.Origin("#m") String methodName) {
return new AdviceScope(
CallDepth.forClass(HttpServletResponse.class), declaringClass, methodName);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Local("otelCallDepth") CallDepth callDepth) {
if (callDepth.decrementAndGet() > 0) {
return;
}
HttpServletResponseAdviceHelper.stopSpan(
responseInstrumenter(), throwable, context, scope, classAndMethod);
@Advice.Thrown Throwable throwable, @Advice.Enter AdviceScope adviceScope) {
adviceScope.exit(throwable);
}
}

View File

@ -24,6 +24,14 @@ import javax.servlet.http.HttpServletResponse;
public final class Servlet3Singletons {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.servlet-3.0";
public static final VirtualField<Servlet, MappingResolver.Factory>
SERVLET_MAPPING_RESOLVER_FACTORY =
VirtualField.find(Servlet.class, MappingResolver.Factory.class);
public static final VirtualField<Filter, MappingResolver.Factory>
FILTER_MAPPING_RESOLVER_FACTORY =
VirtualField.find(Filter.class, MappingResolver.Factory.class);
private static final Instrumenter<
ServletRequestContext<HttpServletRequest>, ServletResponseContext<HttpServletResponse>>
INSTRUMENTER =

View File

@ -8,6 +8,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v5_0;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import io.opentelemetry.javaagent.instrumentation.servlet.common.async.AsyncContextInstrumentation;
import io.opentelemetry.javaagent.instrumentation.servlet.common.async.AsyncContextStartInstrumentation;
import io.opentelemetry.javaagent.instrumentation.servlet.common.async.AsyncStartInstrumentation;
@ -18,7 +19,8 @@ import java.util.Arrays;
import java.util.List;
@AutoService(InstrumentationModule.class)
public class JakartaServletInstrumentationModule extends InstrumentationModule {
public class JakartaServletInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {
private static final String BASE_PACKAGE = "jakarta.servlet";
public JakartaServletInstrumentationModule() {
@ -50,4 +52,9 @@ public class JakartaServletInstrumentationModule extends InstrumentationModule {
private static String adviceClassName(String suffix) {
return JakartaServletInstrumentationModule.class.getPackage().getName() + suffix;
}
@Override
public boolean isIndyReady() {
return true;
}
}

View File

@ -34,9 +34,9 @@ public final class Servlet5Singletons {
private static final ServletHelper<HttpServletRequest, HttpServletResponse> HELPER =
new ServletHelper<>(INSTRUMENTER, Servlet5Accessor.INSTANCE);
private static final VirtualField<Servlet, MappingResolver.Factory> SERVLET_MAPPING_RESOLVER =
public static final VirtualField<Servlet, MappingResolver.Factory> SERVLET_MAPPING_RESOLVER =
VirtualField.find(Servlet.class, MappingResolver.Factory.class);
private static final VirtualField<Filter, MappingResolver.Factory> FILTER_MAPPING_RESOLVER =
public static final VirtualField<Filter, MappingResolver.Factory> FILTER_MAPPING_RESOLVER =
VirtualField.find(Filter.class, MappingResolver.Factory.class);
private static final Instrumenter<ClassAndMethod, Void> RESPONSE_INSTRUMENTER =

View File

@ -8,12 +8,15 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v5_0.async;
import static io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Singletons.helper;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.AssignReturned;
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
@SuppressWarnings("unused")
public class AsyncContextStartAdvice {
@AssignReturned.ToArguments(@ToArgument(0))
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void start(@Advice.Argument(value = 0, readOnly = false) Runnable runnable) {
runnable = helper().wrapAsyncRunnable(runnable);
public static Runnable start(@Advice.Argument(0) Runnable runnable) {
return helper().wrapAsyncRunnable(runnable);
}
}

View File

@ -19,13 +19,11 @@ import net.bytebuddy.asm.Advice;
public class AsyncDispatchAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void enter(
@Advice.This AsyncContext context,
@Advice.AllArguments Object[] args,
@Advice.Local("otelCallDepth") CallDepth callDepth) {
callDepth = CallDepth.forClass(AsyncContext.class);
public static CallDepth enter(
@Advice.This AsyncContext context, @Advice.AllArguments Object[] args) {
CallDepth callDepth = CallDepth.forClass(AsyncContext.class);
if (callDepth.getAndIncrement() > 0) {
return;
return callDepth;
}
ServletRequest request = context.getRequest();
@ -42,10 +40,11 @@ public class AsyncDispatchAdvice {
// processing, and nothing can be done with the request anymore after this
request.setAttribute(CONTEXT_ATTRIBUTE, currentContext);
}
return callDepth;
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void exit(@Advice.Local("otelCallDepth") CallDepth callDepth) {
public static void exit(@Advice.Enter CallDepth callDepth) {
callDepth.decrementAndGet();
}
}

View File

@ -18,16 +18,17 @@ import net.bytebuddy.implementation.bytecode.assign.Assigner;
public class AsyncStartAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void startAsyncEnter(@Advice.Local("otelCallDepth") CallDepth callDepth) {
public static CallDepth startAsyncEnter() {
// This allows to detect the outermost invocation of startAsync in method exit
callDepth = CallDepth.forClass(AsyncContext.class);
CallDepth callDepth = CallDepth.forClass(AsyncContext.class);
callDepth.getAndIncrement();
return callDepth;
}
@Advice.OnMethodExit(suppress = Throwable.class)
public static void startAsyncExit(
@Advice.This(typing = Assigner.Typing.DYNAMIC) HttpServletRequest request,
@Advice.Local("otelCallDepth") CallDepth callDepth) {
@Advice.Enter CallDepth callDepth) {
if (callDepth.decrementAndGet() != 0) {
// This is not the outermost invocation, ignore.

View File

@ -13,47 +13,71 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMetho
import io.opentelemetry.javaagent.bootstrap.CallDepth;
import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseAdviceHelper;
import jakarta.servlet.http.HttpServletResponse;
import javax.annotation.Nullable;
import net.bytebuddy.asm.Advice;
@SuppressWarnings("unused")
public class ResponseSendAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void start(
@Advice.This Object response,
@Advice.Origin("#t") Class<?> declaringClass,
@Advice.Origin("#m") String methodName,
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Local("otelCallDepth") CallDepth callDepth) {
callDepth = CallDepth.forClass(HttpServletResponse.class);
if (callDepth.getAndIncrement() > 0) {
return;
public static class AdviceScope {
private final ClassAndMethod classAndMethod;
private final CallDepth callDepth;
private final Context context;
private final Scope scope;
public AdviceScope(CallDepth callDepth, Class<?> declaringClass, String methodName) {
this.callDepth = callDepth;
if (callDepth.getAndIncrement() > 0) {
this.classAndMethod = null;
this.context = null;
this.scope = null;
return;
}
HttpServletResponseAdviceHelper.StartResult result =
HttpServletResponseAdviceHelper.startSpan(
responseInstrumenter(), declaringClass, methodName);
if (result == null) {
this.classAndMethod = null;
this.context = null;
this.scope = null;
return;
}
this.classAndMethod = result.getClassAndMethod();
this.context = result.getContext();
this.scope = result.getScope();
}
HttpServletResponseAdviceHelper.StartResult result =
HttpServletResponseAdviceHelper.startSpan(
responseInstrumenter(), declaringClass, methodName);
if (result != null) {
classAndMethod = result.getClassAndMethod();
context = result.getContext();
scope = result.getScope();
public AdviceScope(
CallDepth callDepth, HttpServletResponseAdviceHelper.StartResult startResult) {
this.callDepth = callDepth;
this.classAndMethod = startResult.getClassAndMethod();
this.context = startResult.getContext();
this.scope = startResult.getScope();
}
public void exit(@Nullable Throwable throwable) {
if (callDepth.decrementAndGet() > 0) {
return;
}
HttpServletResponseAdviceHelper.stopSpan(
responseInstrumenter(), throwable, context, scope, classAndMethod);
}
}
@Nullable
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AdviceScope start(
@Advice.This Object response,
@Advice.Origin("#t") Class<?> declaringClass,
@Advice.Origin("#m") String methodName) {
CallDepth callDepth = CallDepth.forClass(HttpServletResponse.class);
return new AdviceScope(callDepth, declaringClass, methodName);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Local("otelCallDepth") CallDepth callDepth) {
if (callDepth.decrementAndGet() > 0) {
return;
}
HttpServletResponseAdviceHelper.stopSpan(
responseInstrumenter(), throwable, context, scope, classAndMethod);
@Advice.Thrown @Nullable Throwable throwable, @Advice.Enter AdviceScope adviceScope) {
adviceScope.exit(throwable);
}
}

View File

@ -5,8 +5,8 @@
package io.opentelemetry.javaagent.instrumentation.servlet.v5_0.service;
import io.opentelemetry.instrumentation.api.util.VirtualField;
import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver;
import static io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Singletons.FILTER_MAPPING_RESOLVER;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterConfig;
import net.bytebuddy.asm.Advice;
@ -20,7 +20,7 @@ public class JakartaServletFilterInitAdvice {
if (filterConfig == null) {
return;
}
VirtualField.find(Filter.class, MappingResolver.Factory.class)
.set(filter, new JakartaServletFilterMappingResolverFactory(filterConfig));
FILTER_MAPPING_RESOLVER.set(
filter, new JakartaServletFilterMappingResolverFactory(filterConfig));
}
}

View File

@ -5,8 +5,8 @@
package io.opentelemetry.javaagent.instrumentation.servlet.v5_0.service;
import io.opentelemetry.instrumentation.api.util.VirtualField;
import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver;
import static io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Singletons.SERVLET_MAPPING_RESOLVER;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletConfig;
import net.bytebuddy.asm.Advice;
@ -20,7 +20,6 @@ public class JakartaServletInitAdvice {
if (servletConfig == null) {
return;
}
VirtualField.find(Servlet.class, MappingResolver.Factory.class)
.set(servlet, new JakartaServletMappingResolverFactory(servletConfig));
SERVLET_MAPPING_RESOLVER.set(servlet, new JakartaServletMappingResolverFactory(servletConfig));
}
}

View File

@ -11,7 +11,6 @@ import static io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Si
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.bootstrap.CallDepth;
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge;
import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver;
@ -24,24 +23,89 @@ import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import javax.annotation.Nullable;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.AssignReturned;
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
@SuppressWarnings("unused")
public class JakartaServletServiceAdvice {
public static class AdviceScope {
private final CallDepth callDepth;
private final ServletRequestContext<HttpServletRequest> requestContext;
private final Context context;
private final Scope scope;
public AdviceScope(
CallDepth callDepth,
Object servletOrFilter,
HttpServletRequest request,
ServletResponse response) {
this.callDepth = callDepth;
this.callDepth.getAndIncrement();
Context currentContext = Context.current();
Context attachedContext = helper().getServerContext(request);
Context contextToUpdate;
requestContext = new ServletRequestContext<>(request, servletOrFilter);
if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) {
context = helper().start(currentContext, requestContext);
helper().setAsyncListenerResponse(context, (HttpServletResponse) response);
contextToUpdate = context;
} else if (attachedContext != null
&& helper().needsRescoping(currentContext, attachedContext)) {
// Given request already has a context associated with it.
// see the needsRescoping() javadoc for more explanation
contextToUpdate = attachedContext;
context = null;
} else {
// We are inside nested servlet/filter/app-server span, don't create new span
contextToUpdate = currentContext;
context = null;
}
// Update context with info from current request to ensure that server span gets the best
// possible name.
// In case server span was created by app server instrumentations calling updateContext
// returns a new context that contains servlet context path that is used in other
// instrumentations for naming server span.
MappingResolver mappingResolver = Servlet5Singletons.getMappingResolver(servletOrFilter);
boolean servlet = servletOrFilter instanceof Servlet;
contextToUpdate = helper().updateContext(contextToUpdate, request, mappingResolver, servlet);
scope = contextToUpdate.makeCurrent();
if (context != null) {
// Only trigger response customizer once, so only if server span was created here
HttpServerResponseCustomizerHolder.getCustomizer()
.customize(contextToUpdate, (HttpServletResponse) response, Servlet5Accessor.INSTANCE);
}
}
public void exit(
HttpServletRequest request, HttpServletResponse response, @Nullable Throwable throwable) {
boolean topLevel = callDepth.decrementAndGet() == 0;
helper().end(requestContext, request, response, throwable, topLevel, context, scope);
}
}
@AssignReturned.ToArguments({
@ToArgument(value = 0, index = 1),
@ToArgument(value = 1, index = 2)
})
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
public static Object[] onEnter(
@Advice.This(typing = Assigner.Typing.DYNAMIC) Object servletOrFilter,
@Advice.Argument(value = 0, readOnly = false) ServletRequest request,
@Advice.Argument(value = 1, readOnly = false) ServletResponse response,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
@Advice.Argument(0) ServletRequest request,
@Advice.Argument(1) ServletResponse originalResponse) {
ServletResponse response = originalResponse;
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
return;
return new Object[] {null, request, response};
}
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
@ -53,45 +117,14 @@ public class JakartaServletServiceAdvice {
new Servlet5SnippetInjectingResponseWrapper((HttpServletResponse) response, snippet);
}
callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey());
callDepth.getAndIncrement();
AdviceScope adviceScope =
new AdviceScope(
CallDepth.forClass(AppServerBridge.getCallDepthKey()),
servletOrFilter,
(HttpServletRequest) request,
response);
Context currentContext = Java8BytecodeBridge.currentContext();
Context attachedContext = helper().getServerContext(httpServletRequest);
Context contextToUpdate;
requestContext = new ServletRequestContext<>(httpServletRequest, servletOrFilter);
if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) {
context = helper().start(currentContext, requestContext);
helper().setAsyncListenerResponse(context, (HttpServletResponse) response);
contextToUpdate = context;
} else if (attachedContext != null
&& helper().needsRescoping(currentContext, attachedContext)) {
// Given request already has a context associated with it.
// see the needsRescoping() javadoc for more explanation
contextToUpdate = attachedContext;
} else {
// We are inside nested servlet/filter/app-server span, don't create new span
contextToUpdate = currentContext;
}
// Update context with info from current request to ensure that server span gets the best
// possible name.
// In case server span was created by app server instrumentations calling updateContext
// returns a new context that contains servlet context path that is used in other
// instrumentations for naming server span.
MappingResolver mappingResolver = Servlet5Singletons.getMappingResolver(servletOrFilter);
boolean servlet = servletOrFilter instanceof Servlet;
contextToUpdate =
helper().updateContext(contextToUpdate, httpServletRequest, mappingResolver, servlet);
scope = contextToUpdate.makeCurrent();
if (context != null) {
// Only trigger response customizer once, so only if server span was created here
HttpServerResponseCustomizerHolder.getCustomizer()
.customize(contextToUpdate, (HttpServletResponse) response, Servlet5Accessor.INSTANCE);
}
return new Object[] {adviceScope, request, response};
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
@ -99,25 +132,16 @@ public class JakartaServletServiceAdvice {
@Advice.Argument(0) ServletRequest request,
@Advice.Argument(1) ServletResponse response,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelRequest") ServletRequestContext<HttpServletRequest> requestContext,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
@Advice.Enter Object[] enterResult) {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
AdviceScope adviceScope = (AdviceScope) enterResult[0];
if (adviceScope == null
|| !(request instanceof HttpServletRequest)
|| !(response instanceof HttpServletResponse)) {
return;
}
boolean topLevel = callDepth.decrementAndGet() == 0;
helper()
.end(
requestContext,
(HttpServletRequest) request,
(HttpServletResponse) response,
throwable,
topLevel,
context,
scope);
adviceScope.exit((HttpServletRequest) request, (HttpServletResponse) response, throwable);
}
}