diff --git a/instrumentation/jetty/jetty-11.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/Jetty11HandlerAdvice.java b/instrumentation/jetty/jetty-11.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/Jetty11HandlerAdvice.java index 60a368077314..10ec9456e05d 100644 --- a/instrumentation/jetty/jetty-11.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/Jetty11HandlerAdvice.java +++ b/instrumentation/jetty/jetty-11.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/Jetty11HandlerAdvice.java @@ -45,7 +45,7 @@ public static void onEnter( scope = context.makeCurrent(); // Must be set here since Jetty handlers can use startAsync outside of servlet scope. - helper().setAsyncListenerResponse(request, response); + helper().setAsyncListenerResponse(context, response); HttpServerResponseCustomizerHolder.getCustomizer() .customize(context, response, Jetty11ResponseMutator.INSTANCE); diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Helper.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Helper.java index d17aba9c6d00..4f65840f1d5f 100644 --- a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Helper.java +++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Helper.java @@ -8,7 +8,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge; -import io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper; +import io.opentelemetry.javaagent.bootstrap.servlet.ServletAsyncContext; import javax.annotation.Nullable; import org.eclipse.jetty.server.HttpStream; import org.eclipse.jetty.server.Request; @@ -54,7 +54,7 @@ public void end(Context context, Request request, Response response, @Nullable T error = AppServerBridge.getException(context); } if (error == null) { - error = (Throwable) request.getAttribute(ServletHelper.ASYNC_EXCEPTION_ATTRIBUTE); + error = ServletAsyncContext.getAsyncException(context); } instrumenter.end(context, request, response, error); diff --git a/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/Jetty8HandlerAdvice.java b/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/Jetty8HandlerAdvice.java index a72e957f26c8..158154f670a7 100644 --- a/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/Jetty8HandlerAdvice.java +++ b/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/Jetty8HandlerAdvice.java @@ -45,7 +45,7 @@ public static void onEnter( scope = context.makeCurrent(); // Must be set here since Jetty handlers can use startAsync outside of servlet scope. - helper().setAsyncListenerResponse(request, response); + helper().setAsyncListenerResponse(context, response); HttpServerResponseCustomizerHolder.getCustomizer() .customize(context, response, Jetty8ResponseMutator.INSTANCE); diff --git a/instrumentation/jetty/jetty-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/common/JettyHelper.java b/instrumentation/jetty/jetty-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/common/JettyHelper.java index ebbd5ad93b38..134576b3c2c3 100644 --- a/instrumentation/jetty/jetty-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/common/JettyHelper.java +++ b/instrumentation/jetty/jetty-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/common/JettyHelper.java @@ -41,7 +41,7 @@ public void end( } ServletResponseContext responseContext = new ServletResponseContext<>(response); - if (throwable != null || mustEndOnHandlerMethodExit(request)) { + if (throwable != null || mustEndOnHandlerMethodExit(context)) { instrumenter.end(context, requestContext, responseContext, throwable); } } diff --git a/instrumentation/liberty/liberty-20.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyHelper.java b/instrumentation/liberty/liberty-20.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyHelper.java index 0688e70ba098..a434ced601f4 100644 --- a/instrumentation/liberty/liberty-20.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyHelper.java +++ b/instrumentation/liberty/liberty-20.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyHelper.java @@ -40,7 +40,7 @@ public void end( } ServletResponseContext responseContext = new ServletResponseContext<>(response); - if (throwable != null || mustEndOnHandlerMethodExit(request)) { + if (throwable != null || mustEndOnHandlerMethodExit(context)) { instrumenter.end(context, requestContext, responseContext, throwable); } } diff --git a/instrumentation/liberty/liberty-20.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyWebAppInstrumentation.java b/instrumentation/liberty/liberty-20.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyWebAppInstrumentation.java index 26935edcfd81..b7f118d44b16 100644 --- a/instrumentation/liberty/liberty-20.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyWebAppInstrumentation.java +++ b/instrumentation/liberty/liberty-20.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyWebAppInstrumentation.java @@ -125,7 +125,7 @@ public static void onEnter() { // Must be set here since Liberty RequestProcessors can use startAsync outside of servlet // scope. - helper().setAsyncListenerResponse(requestInfo.getRequest(), requestInfo.getResponse()); + helper().setAsyncListenerResponse(context, requestInfo.getResponse()); HttpServerResponseCustomizerHolder.getCustomizer() .customize(context, requestInfo.getResponse(), Servlet3Accessor.INSTANCE); diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java index 1461bf8fd78f..1a6d5bd636f4 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java @@ -60,7 +60,7 @@ public static void onEnter( requestContext = new ServletRequestContext<>(httpServletRequest, servletOrFilter); if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) { context = helper().start(currentContext, requestContext); - helper().setAsyncListenerResponse(httpServletRequest, (HttpServletResponse) response); + helper().setAsyncListenerResponse(context, (HttpServletResponse) response); contextToUpdate = context; } else if (attachedContext != null diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3AsyncContextStartAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3AsyncContextStartAdvice.java index fcc8cb995104..0117cbbbce4d 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3AsyncContextStartAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3AsyncContextStartAdvice.java @@ -7,21 +7,13 @@ import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; -import javax.servlet.AsyncContext; -import javax.servlet.ServletRequest; -import javax.servlet.http.HttpServletRequest; import net.bytebuddy.asm.Advice; @SuppressWarnings("unused") public class Servlet3AsyncContextStartAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static void start( - @Advice.This AsyncContext asyncContext, - @Advice.Argument(value = 0, readOnly = false) Runnable runnable) { - ServletRequest request = asyncContext.getRequest(); - if (request instanceof HttpServletRequest) { - runnable = helper().wrapAsyncRunnable((HttpServletRequest) request, runnable); - } + public static void start(@Advice.Argument(value = 0, readOnly = false) Runnable runnable) { + runnable = helper().wrapAsyncRunnable(runnable); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3AsyncStartAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3AsyncStartAdvice.java index c1d55c1cb940..1314c6a815bf 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3AsyncStartAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3AsyncStartAdvice.java @@ -8,6 +8,7 @@ import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; import io.opentelemetry.javaagent.bootstrap.CallDepth; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; import javax.servlet.AsyncContext; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; @@ -36,9 +37,7 @@ public static void startAsyncExit( if (servletRequest instanceof HttpServletRequest) { HttpServletRequest request = (HttpServletRequest) servletRequest; - if (!helper().isAsyncListenerAttached(request)) { - helper().attachAsyncListener(request); - } + helper().attachAsyncListener(request, Java8BytecodeBridge.currentContext()); } } } diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/async/AsyncContextStartAdvice.java b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/async/AsyncContextStartAdvice.java index dbde8946adcd..0edbc2eacfad 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/async/AsyncContextStartAdvice.java +++ b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/async/AsyncContextStartAdvice.java @@ -7,21 +7,13 @@ import static io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Singletons.helper; -import jakarta.servlet.AsyncContext; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.http.HttpServletRequest; import net.bytebuddy.asm.Advice; @SuppressWarnings("unused") public class AsyncContextStartAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static void start( - @Advice.This AsyncContext asyncContext, - @Advice.Argument(value = 0, readOnly = false) Runnable runnable) { - ServletRequest request = asyncContext.getRequest(); - if (request instanceof HttpServletRequest) { - runnable = helper().wrapAsyncRunnable((HttpServletRequest) request, runnable); - } + public static void start(@Advice.Argument(value = 0, readOnly = false) Runnable runnable) { + runnable = helper().wrapAsyncRunnable(runnable); } } diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/async/AsyncStartAdvice.java b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/async/AsyncStartAdvice.java index 4cc66a2876d8..7b11c2d01085 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/async/AsyncStartAdvice.java +++ b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/async/AsyncStartAdvice.java @@ -8,6 +8,7 @@ import static io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Singletons.helper; import io.opentelemetry.javaagent.bootstrap.CallDepth; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; import jakarta.servlet.AsyncContext; import jakarta.servlet.http.HttpServletRequest; import net.bytebuddy.asm.Advice; @@ -34,9 +35,7 @@ public static void startAsyncExit( } if (request != null) { - if (!helper().isAsyncListenerAttached(request)) { - helper().attachAsyncListener(request); - } + helper().attachAsyncListener(request, Java8BytecodeBridge.currentContext()); } } } diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/service/JakartaServletServiceAdvice.java b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/service/JakartaServletServiceAdvice.java index 7fc8c2326806..6e76913e558d 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/service/JakartaServletServiceAdvice.java +++ b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/service/JakartaServletServiceAdvice.java @@ -63,7 +63,7 @@ public static void onEnter( requestContext = new ServletRequestContext<>(httpServletRequest, servletOrFilter); if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) { context = helper().start(currentContext, requestContext); - helper().setAsyncListenerResponse(httpServletRequest, (HttpServletResponse) response); + helper().setAsyncListenerResponse(context, (HttpServletResponse) response); contextToUpdate = context; } else if (attachedContext != null diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/AppServerBridge.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/AppServerBridge.java index a9b926bbdcba..a12ee4c68fbf 100644 --- a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/AppServerBridge.java +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/AppServerBridge.java @@ -125,7 +125,9 @@ public Builder captureServletAttributes() { * @return new context with AppServerBridge attached. */ public Context init(Context context) { - return context.with(AppServerBridge.CONTEXT_KEY, new AppServerBridge(this)); + Context result = context.with(AppServerBridge.CONTEXT_KEY, new AppServerBridge(this)); + result = ServletAsyncContext.init(result); + return result; } } } diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ServletAsyncContext.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ServletAsyncContext.java new file mode 100644 index 000000000000..8bbf5bc1d262 --- /dev/null +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ServletAsyncContext.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.bootstrap.servlet; + +import static io.opentelemetry.context.ContextKey.named; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import io.opentelemetry.context.ImplicitContextKeyed; +import javax.annotation.Nullable; + +public class ServletAsyncContext implements ImplicitContextKeyed { + private static final ContextKey CONTEXT_KEY = + named("opentelemetry-servlet-async-context"); + + private boolean isAsyncListenerAttached; + private Throwable throwable; + private Object response; + + public static Context init(Context context) { + if (context.get(CONTEXT_KEY) != null) { + return context; + } + return context.with(new ServletAsyncContext()); + } + + @Nullable + public static ServletAsyncContext get(@Nullable Context context) { + return context != null ? context.get(CONTEXT_KEY) : null; + } + + public static boolean isAsyncListenerAttached(@Nullable Context context) { + ServletAsyncContext servletAsyncContext = get(context); + return servletAsyncContext != null && servletAsyncContext.isAsyncListenerAttached; + } + + public static void setAsyncListenerAttached(@Nullable Context context, boolean value) { + ServletAsyncContext servletAsyncContext = get(context); + if (servletAsyncContext != null) { + servletAsyncContext.isAsyncListenerAttached = value; + } + } + + public static Throwable getAsyncException(@Nullable Context context) { + ServletAsyncContext servletAsyncContext = get(context); + return servletAsyncContext != null ? servletAsyncContext.throwable : null; + } + + public static void recordAsyncException(@Nullable Context context, Throwable throwable) { + ServletAsyncContext servletAsyncContext = get(context); + if (servletAsyncContext != null) { + servletAsyncContext.throwable = throwable; + } + } + + public static Object getAsyncListenerResponse(@Nullable Context context) { + ServletAsyncContext servletAsyncContext = get(context); + return servletAsyncContext != null ? servletAsyncContext.response : null; + } + + public static void setAsyncListenerResponse(@Nullable Context context, Object response) { + ServletAsyncContext servletAsyncContext = get(context); + if (servletAsyncContext != null) { + servletAsyncContext.response = response; + } + } + + @Override + public Context storeInContext(Context context) { + return context.with(CONTEXT_KEY, this); + } +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRequestCompletionListener.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRequestCompletionListener.java index 1314ac9fee71..b941e2575662 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRequestCompletionListener.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRequestCompletionListener.java @@ -33,7 +33,7 @@ public AsyncRequestCompletionListener( public void onComplete(RESPONSE response) { if (responseHandled.compareAndSet(false, true)) { ServletResponseContext responseContext = new ServletResponseContext<>(response); - Throwable throwable = servletHelper.getAsyncException(requestContext.request()); + Throwable throwable = servletHelper.getAsyncException(context); instrumenter.end(context, requestContext, responseContext, throwable); } } @@ -41,10 +41,10 @@ public void onComplete(RESPONSE response) { @Override public void onTimeout(long timeout) { if (responseHandled.compareAndSet(false, true)) { - RESPONSE response = servletHelper.getAsyncListenerResponse(requestContext.request()); + RESPONSE response = servletHelper.getAsyncListenerResponse(context); ServletResponseContext responseContext = new ServletResponseContext<>(response); responseContext.setTimeout(timeout); - Throwable throwable = servletHelper.getAsyncException(requestContext.request()); + Throwable throwable = servletHelper.getAsyncException(context); instrumenter.end(context, requestContext, responseContext, throwable); } } diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRunnableWrapper.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRunnableWrapper.java index 307fe5a5cd7c..31aafe5a6214 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRunnableWrapper.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRunnableWrapper.java @@ -5,24 +5,24 @@ package io.opentelemetry.javaagent.instrumentation.servlet; +import io.opentelemetry.context.Context; + public class AsyncRunnableWrapper implements Runnable { private final ServletHelper helper; - private final REQUEST request; private final Runnable runnable; + private final Context context; - private AsyncRunnableWrapper( - ServletHelper helper, REQUEST request, Runnable runnable) { + private AsyncRunnableWrapper(ServletHelper helper, Runnable runnable) { this.helper = helper; - this.request = request; this.runnable = runnable; + this.context = Context.current(); } - public static Runnable wrap( - ServletHelper helper, REQUEST request, Runnable runnable) { + public static Runnable wrap(ServletHelper helper, Runnable runnable) { if (runnable == null || runnable instanceof AsyncRunnableWrapper) { return runnable; } - return new AsyncRunnableWrapper<>(helper, request, runnable); + return new AsyncRunnableWrapper<>(helper, runnable); } @Override @@ -30,7 +30,7 @@ public void run() { try { runnable.run(); } catch (Throwable throwable) { - helper.recordAsyncException(request, throwable); + helper.recordAsyncException(context, throwable); throw throwable; } } diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/BaseServletHelper.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/BaseServletHelper.java index 6e4489dcd15c..8de96ba3a002 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/BaseServletHelper.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/BaseServletHelper.java @@ -17,6 +17,7 @@ import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig; import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge; import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver; +import io.opentelemetry.javaagent.bootstrap.servlet.ServletAsyncContext; import io.opentelemetry.javaagent.bootstrap.servlet.ServletContextPath; import io.opentelemetry.semconv.incubating.EnduserIncubatingAttributes; import java.security.Principal; @@ -61,6 +62,7 @@ public Context start(Context parentContext, ServletRequestContext reque accessor.setRequestAttribute(request, "span_id", spanContext.getSpanId()); context = addServletContextPath(context, request); + context = addAsyncContext(context); attachServerContext(context, request); @@ -71,6 +73,10 @@ protected Context addServletContextPath(Context context, REQUEST request) { return ServletContextPath.init(context, contextPathExtractor, request); } + protected Context addAsyncContext(Context context) { + return ServletAsyncContext.init(context); + } + public Context getServerContext(REQUEST request) { Object context = accessor.getRequestAttribute(request, ServletHelper.CONTEXT_ATTRIBUTE); return context instanceof Context ? (Context) context : null; @@ -87,6 +93,8 @@ public void recordException(Context context, Throwable throwable) { public Context updateContext( Context context, REQUEST request, MappingResolver mappingResolver, boolean servlet) { Context result = addServletContextPath(context, request); + result = addAsyncContext(result); + if (mappingResolver != null) { HttpServerRoute.update( result, servlet ? SERVER : SERVER_FILTER, spanNameProvider, mappingResolver, request); @@ -125,7 +133,7 @@ private void captureRequestParameters(Span serverSpan, REQUEST request) { return; } - parameterExtractor.setAttributes(request, (key, value) -> serverSpan.setAttribute(key, value)); + parameterExtractor.setAttributes(request, serverSpan::setAttribute); } /** diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHelper.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHelper.java index 5df62a1f626d..a43e324e82b4 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHelper.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHelper.java @@ -8,14 +8,9 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.bootstrap.servlet.ServletAsyncContext; public class ServletHelper extends BaseServletHelper { - private static final String ASYNC_LISTENER_ATTRIBUTE = - ServletHelper.class.getName() + ".AsyncListener"; - private static final String ASYNC_LISTENER_RESPONSE_ATTRIBUTE = - ServletHelper.class.getName() + ".AsyncListenerResponse"; - public static final String ASYNC_EXCEPTION_ATTRIBUTE = - ServletHelper.class.getName() + ".AsyncException"; public static final String CONTEXT_ATTRIBUTE = ServletHelper.class.getName() + ".Context"; public ServletHelper( @@ -43,10 +38,10 @@ public void end( // instrumentation and we have an uncaught throwable. Let's add it to the current span. if (throwable != null) { recordException(currentContext, throwable); - if (!mustEndOnHandlerMethodExit(request)) { + if (!mustEndOnHandlerMethodExit(currentContext)) { // We could be inside async dispatch. Unlike tomcat jetty does not call // ServletAsyncListener.onError when exception is thrown inside async dispatch. - recordAsyncException(request, throwable); + recordAsyncException(currentContext, throwable); } } // also capture request parameters as servlet attributes @@ -58,7 +53,7 @@ public void end( } ServletResponseContext responseContext = new ServletResponseContext<>(response); - if (throwable != null || mustEndOnHandlerMethodExit(request)) { + if (throwable != null || mustEndOnHandlerMethodExit(context)) { instrumenter.end(context, requestContext, responseContext, throwable); } } @@ -68,8 +63,8 @@ public void end( * that started a span must also end it, even if no error was detected. Extracted as a separate * method to avoid duplicating the comments on the logic behind this choice. */ - public boolean mustEndOnHandlerMethodExit(REQUEST request) { - if (isAsyncListenerAttached(request)) { + public boolean mustEndOnHandlerMethodExit(Context context) { + if (isAsyncListenerAttached(context)) { // This request is handled asynchronously and startAsync instrumentation has already attached // the listener. return false; @@ -82,54 +77,47 @@ public boolean mustEndOnHandlerMethodExit(REQUEST request) { } /** - * Response object must be attached to a request prior to {@link - * #attachAsyncListener(ServletRequestContext)} being called, as otherwise in some environments it - * is not possible to access response from async event in listeners. + * Response object must be attached to a request prior to {@link #attachAsyncListener(REQUEST, + * Context)} being called, as otherwise in some environments it is not possible to access response + * from async event in listeners. */ - public void setAsyncListenerResponse(REQUEST request, RESPONSE response) { - accessor.setRequestAttribute(request, ASYNC_LISTENER_RESPONSE_ATTRIBUTE, response); + public void setAsyncListenerResponse(Context context, RESPONSE response) { + ServletAsyncContext.setAsyncListenerResponse(context, response); } - public RESPONSE getAsyncListenerResponse(REQUEST request) { - @SuppressWarnings("unchecked") - RESPONSE response = - (RESPONSE) accessor.getRequestAttribute(request, ASYNC_LISTENER_RESPONSE_ATTRIBUTE); - return response; + @SuppressWarnings("unchecked") + public RESPONSE getAsyncListenerResponse(Context context) { + return (RESPONSE) ServletAsyncContext.getAsyncListenerResponse(context); } - public void attachAsyncListener(REQUEST request) { - ServletRequestContext requestContext = new ServletRequestContext<>(request, null); - attachAsyncListener(requestContext); - } - - private void attachAsyncListener(ServletRequestContext requestContext) { - REQUEST request = requestContext.request(); - Context context = getServerContext(request); + public void attachAsyncListener(REQUEST request, Context context) { + if (isAsyncListenerAttached(context)) { + return; + } - if (context != null) { - Object response = getAsyncListenerResponse(request); + Object response = getAsyncListenerResponse(context); - accessor.addRequestAsyncListener( - request, - new AsyncRequestCompletionListener<>(this, instrumenter, requestContext, context), - response); - accessor.setRequestAttribute(request, ASYNC_LISTENER_ATTRIBUTE, true); - } + ServletRequestContext requestContext = new ServletRequestContext<>(request, null); + accessor.addRequestAsyncListener( + request, + new AsyncRequestCompletionListener<>(this, instrumenter, requestContext, context), + response); + ServletAsyncContext.setAsyncListenerAttached(context, true); } - public boolean isAsyncListenerAttached(REQUEST request) { - return accessor.getRequestAttribute(request, ASYNC_LISTENER_ATTRIBUTE) != null; + private static boolean isAsyncListenerAttached(Context context) { + return ServletAsyncContext.isAsyncListenerAttached(context); } - public Runnable wrapAsyncRunnable(REQUEST request, Runnable runnable) { - return AsyncRunnableWrapper.wrap(this, request, runnable); + public Runnable wrapAsyncRunnable(Runnable runnable) { + return AsyncRunnableWrapper.wrap(this, runnable); } - public void recordAsyncException(REQUEST request, Throwable throwable) { - accessor.setRequestAttribute(request, ASYNC_EXCEPTION_ATTRIBUTE, throwable); + public void recordAsyncException(Context context, Throwable throwable) { + ServletAsyncContext.recordAsyncException(context, throwable); } - public Throwable getAsyncException(REQUEST request) { - return (Throwable) accessor.getRequestAttribute(request, ASYNC_EXCEPTION_ATTRIBUTE); + public Throwable getAsyncException(Context context) { + return ServletAsyncContext.getAsyncException(context); } } diff --git a/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10AttachResponseAdvice.java b/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10AttachResponseAdvice.java index 086c49662200..92e28ba3e157 100644 --- a/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10AttachResponseAdvice.java +++ b/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10AttachResponseAdvice.java @@ -7,8 +7,8 @@ import static io.opentelemetry.javaagent.instrumentation.tomcat.v10_0.Tomcat10Singletons.helper; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; import net.bytebuddy.asm.Advice; -import org.apache.coyote.Request; import org.apache.coyote.Response; @SuppressWarnings("unused") @@ -16,12 +16,10 @@ public class Tomcat10AttachResponseAdvice { @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void attachResponse( - @Advice.Argument(0) Request request, - @Advice.Argument(2) Response response, - @Advice.Return boolean success) { + @Advice.Argument(2) Response response, @Advice.Return boolean success) { if (success) { - helper().attachResponseToRequest(request, response); + helper().attachResponseToRequest(Java8BytecodeBridge.currentContext(), response); } } } diff --git a/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7AttachResponseAdvice.java b/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7AttachResponseAdvice.java index ed7f49bd9752..a9e44639370f 100644 --- a/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7AttachResponseAdvice.java +++ b/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7AttachResponseAdvice.java @@ -7,8 +7,8 @@ import static io.opentelemetry.javaagent.instrumentation.tomcat.v7_0.Tomcat7Singletons.helper; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; import net.bytebuddy.asm.Advice; -import org.apache.coyote.Request; import org.apache.coyote.Response; @SuppressWarnings("unused") @@ -16,12 +16,10 @@ public class Tomcat7AttachResponseAdvice { @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void attachResponse( - @Advice.Argument(0) Request request, - @Advice.Argument(2) Response response, - @Advice.Return boolean success) { + @Advice.Argument(2) Response response, @Advice.Return boolean success) { if (success) { - helper().attachResponseToRequest(request, response); + helper().attachResponseToRequest(Java8BytecodeBridge.currentContext(), response); } } } diff --git a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatHelper.java b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatHelper.java index 15d8cd292b2c..0a59cea11620 100644 --- a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatHelper.java +++ b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatHelper.java @@ -49,22 +49,16 @@ public void end( throwable = AppServerBridge.getException(context); } - if (throwable != null || mustEndOnHandlerMethodExit(request)) { + if (throwable != null || servletHelper.mustEndOnHandlerMethodExit(context)) { instrumenter.end(context, request, response, throwable); } } - private boolean mustEndOnHandlerMethodExit(Request request) { - REQUEST servletRequest = servletEntityProvider.getServletRequest(request); - return servletRequest != null && servletHelper.mustEndOnHandlerMethodExit(servletRequest); - } - - public void attachResponseToRequest(Request request, Response response) { - REQUEST servletRequest = servletEntityProvider.getServletRequest(request); + public void attachResponseToRequest(Context context, Response response) { RESPONSE servletResponse = servletEntityProvider.getServletResponse(response); - if (servletRequest != null && servletResponse != null) { - servletHelper.setAsyncListenerResponse(servletRequest, servletResponse); + if (servletResponse != null) { + servletHelper.setAsyncListenerResponse(context, servletResponse); } }