diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/servlet/AppServerBridge.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/servlet/AppServerBridge.java index 95a8b986d4f2..6fea39a2bd2d 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/servlet/AppServerBridge.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/servlet/AppServerBridge.java @@ -7,6 +7,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Helper container for Context attributes for transferring certain information between servlet @@ -42,6 +43,7 @@ public static Context init(Context ctx, boolean shouldRecordException) { } private final boolean servletShouldRecordException; + private Throwable exception; private AppServerBridge(boolean shouldRecordException) { servletShouldRecordException = shouldRecordException; @@ -53,18 +55,47 @@ private AppServerBridge(boolean shouldRecordException) { * during servlet invocation are propagated to the method where server span is closed and can be * added to server span there and true otherwise. * - * @param ctx server context + * @param context server context * @return true, if servlet integration should record exception thrown during servlet * invocation in server span, or false otherwise. */ - public static boolean shouldRecordException(Context ctx) { - AppServerBridge appServerBridge = ctx.get(AppServerBridge.CONTEXT_KEY); + public static boolean shouldRecordException(Context context) { + AppServerBridge appServerBridge = context.get(AppServerBridge.CONTEXT_KEY); if (appServerBridge != null) { return appServerBridge.servletShouldRecordException; } return true; } + /** + * Record exception that happened during servlet invocation so that app server instrumentation can + * add it to server span. + * + * @param context server context + * @param exception exception that happened during servlet invocation + */ + public static void recordException(Context context, Throwable exception) { + AppServerBridge appServerBridge = context.get(AppServerBridge.CONTEXT_KEY); + if (appServerBridge != null && appServerBridge.servletShouldRecordException) { + appServerBridge.exception = exception; + } + } + + /** + * Get exception that happened during servlet invocation. + * + * @param context server context + * @return exception that happened during servlet invocation + */ + @Nullable + public static Throwable getException(Context context) { + AppServerBridge appServerBridge = context.get(AppServerBridge.CONTEXT_KEY); + if (appServerBridge != null) { + return appServerBridge.exception; + } + return null; + } + /** * Class used as key in CallDepthThreadLocalMap for counting servlet invocation depth in * Servlet3Advice and Servlet2Advice. We can not use helper classes like Servlet3Advice and 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 ef619485ff11..3712133db3a6 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 @@ -5,11 +5,12 @@ package io.opentelemetry.javaagent.instrumentation.jetty.v11_0; -import static io.opentelemetry.javaagent.instrumentation.jetty.v11_0.Jetty11HttpServerTracer.tracer; +import static io.opentelemetry.javaagent.instrumentation.jetty.v11_0.Jetty11Singletons.helper; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.javaagent.instrumentation.jetty.common.JettyHandlerAdviceHelper; +import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import net.bytebuddy.asm.Advice; @@ -22,20 +23,28 @@ public static void onEnter( @Advice.This Object source, @Advice.Argument(2) HttpServletRequest request, @Advice.Argument(3) HttpServletResponse response, + @Advice.Local("otelRequest") ServletRequestContext requestContext, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - Context attachedContext = tracer().getServerContext(request); + Context attachedContext = helper().getServerContext(request); if (attachedContext != null) { // We are inside nested handler, don't create new span return; } - context = tracer().startServerSpan(request); + Context parentContext = Java8BytecodeBridge.currentContext(); + requestContext = new ServletRequestContext<>(request); + + if (!helper().shouldStart(parentContext, requestContext)) { + return; + } + + context = helper().start(parentContext, requestContext); scope = context.makeCurrent(); // Must be set here since Jetty handlers can use startAsync outside of servlet scope. - tracer().setAsyncListenerResponse(request, response); + helper().setAsyncListenerResponse(request, response); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @@ -43,9 +52,10 @@ public static void stopSpan( @Advice.Argument(2) HttpServletRequest request, @Advice.Argument(3) HttpServletResponse response, @Advice.Thrown Throwable throwable, + @Advice.Local("otelRequest") ServletRequestContext requestContext, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - JettyHandlerAdviceHelper.stopSpan(tracer(), request, response, throwable, context, scope); + helper().end(requestContext, request, response, throwable, context, scope); } } diff --git a/instrumentation/jetty/jetty-11.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/Jetty11HttpServerTracer.java b/instrumentation/jetty/jetty-11.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/Jetty11HttpServerTracer.java deleted file mode 100644 index 22ed66e63d0e..000000000000 --- a/instrumentation/jetty/jetty-11.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/Jetty11HttpServerTracer.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.jetty.v11_0; - -import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.servlet.AppServerBridge; -import io.opentelemetry.instrumentation.servlet.jakarta.v5_0.JakartaServletHttpServerTracer; -import jakarta.servlet.http.HttpServletRequest; - -public class Jetty11HttpServerTracer extends JakartaServletHttpServerTracer { - private static final Jetty11HttpServerTracer TRACER = new Jetty11HttpServerTracer(); - - public static Jetty11HttpServerTracer tracer() { - return TRACER; - } - - public Context startServerSpan(HttpServletRequest request) { - return startSpan(request, "HTTP " + request.getMethod(), /* servlet= */ false); - } - - @Override - protected Context customizeContext(Context context, HttpServletRequest request) { - context = super.customizeContext(context, request); - return AppServerBridge.init(context, /* shouldRecordException= */ false); - } - - @Override - protected String getInstrumentationName() { - return "io.opentelemetry.jetty-11.0"; - } -} diff --git a/instrumentation/jetty/jetty-11.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/Jetty11Singletons.java b/instrumentation/jetty/jetty-11.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/Jetty11Singletons.java new file mode 100644 index 000000000000..f435b36e0545 --- /dev/null +++ b/instrumentation/jetty/jetty-11.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/Jetty11Singletons.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jetty.v11_0; + +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.instrumentation.jetty.common.JettyHelper; +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.v5_0.Servlet5Accessor; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public final class Jetty11Singletons { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jetty-11.0"; + + private static final Instrumenter< + ServletRequestContext, ServletResponseContext> + INSTRUMENTER = + ServletInstrumenterBuilder.newInstrumenter( + INSTRUMENTATION_NAME, Servlet5Accessor.INSTANCE); + private static final JettyHelper HELPER = + new JettyHelper<>(INSTRUMENTER, Servlet5Accessor.INSTANCE); + + public static JettyHelper helper() { + return HELPER; + } + + private Jetty11Singletons() {} +} 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 42d60cf08209..62132a6d4a31 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 @@ -5,11 +5,12 @@ package io.opentelemetry.javaagent.instrumentation.jetty.v8_0; -import static io.opentelemetry.javaagent.instrumentation.jetty.v8_0.Jetty8HttpServerTracer.tracer; +import static io.opentelemetry.javaagent.instrumentation.jetty.v8_0.Jetty8Singletons.helper; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.javaagent.instrumentation.jetty.common.JettyHandlerAdviceHelper; +import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.bytebuddy.asm.Advice; @@ -22,20 +23,28 @@ public static void onEnter( @Advice.This Object source, @Advice.Argument(2) HttpServletRequest request, @Advice.Argument(3) HttpServletResponse response, + @Advice.Local("otelRequest") ServletRequestContext requestContext, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - Context attachedContext = tracer().getServerContext(request); + Context attachedContext = helper().getServerContext(request); if (attachedContext != null) { // We are inside nested handler, don't create new span return; } - context = tracer().startServerSpan(request); + Context parentContext = Java8BytecodeBridge.currentContext(); + requestContext = new ServletRequestContext<>(request); + + if (!helper().shouldStart(parentContext, requestContext)) { + return; + } + + context = helper().start(parentContext, requestContext); scope = context.makeCurrent(); // Must be set here since Jetty handlers can use startAsync outside of servlet scope. - tracer().setAsyncListenerResponse(request, response); + helper().setAsyncListenerResponse(request, response); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @@ -43,9 +52,10 @@ public static void stopSpan( @Advice.Argument(2) HttpServletRequest request, @Advice.Argument(3) HttpServletResponse response, @Advice.Thrown Throwable throwable, + @Advice.Local("otelRequest") ServletRequestContext requestContext, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - JettyHandlerAdviceHelper.stopSpan(tracer(), request, response, throwable, context, scope); + helper().end(requestContext, request, response, throwable, context, scope); } } diff --git a/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/Jetty8HttpServerTracer.java b/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/Jetty8HttpServerTracer.java deleted file mode 100644 index c1b7f8be6f48..000000000000 --- a/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/Jetty8HttpServerTracer.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.jetty.v8_0; - -import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.servlet.AppServerBridge; -import io.opentelemetry.instrumentation.servlet.v3_0.Servlet3HttpServerTracer; -import javax.servlet.http.HttpServletRequest; - -public class Jetty8HttpServerTracer extends Servlet3HttpServerTracer { - private static final Jetty8HttpServerTracer TRACER = new Jetty8HttpServerTracer(); - - public static Jetty8HttpServerTracer tracer() { - return TRACER; - } - - public Context startServerSpan(HttpServletRequest request) { - return startSpan(request, "HTTP " + request.getMethod(), /* servlet= */ false); - } - - @Override - protected Context customizeContext(Context context, HttpServletRequest request) { - context = super.customizeContext(context, request); - return AppServerBridge.init(context, /* shouldRecordException= */ false); - } - - @Override - protected String getInstrumentationName() { - return "io.opentelemetry.jetty-8.0"; - } -} diff --git a/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/Jetty8Singletons.java b/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/Jetty8Singletons.java new file mode 100644 index 000000000000..dd0c99c0e246 --- /dev/null +++ b/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/Jetty8Singletons.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jetty.v8_0; + +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.servlet.v3_0.Servlet3Accessor; +import io.opentelemetry.javaagent.instrumentation.jetty.common.JettyHelper; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletInstrumenterBuilder; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletResponseContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public final class Jetty8Singletons { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jetty-8.0"; + + private static final Instrumenter< + ServletRequestContext, ServletResponseContext> + INSTRUMENTER = + ServletInstrumenterBuilder.newInstrumenter( + INSTRUMENTATION_NAME, Servlet3Accessor.INSTANCE); + private static final JettyHelper HELPER = + new JettyHelper<>(INSTRUMENTER, Servlet3Accessor.INSTANCE); + + public static JettyHelper helper() { + return HELPER; + } + + private Jetty8Singletons() {} +} diff --git a/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/package-info.java b/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/package-info.java index edd7e09db1fd..9d6666e26e0d 100644 --- a/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/package-info.java +++ b/instrumentation/jetty/jetty-8.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/package-info.java @@ -9,8 +9,7 @@ * *

As instrumentation points differ between servlet instrumentations and this one, this module * has its own {@code JettyHandlerInstrumentation} and {@code JettyHandlerAdvice}. But this is the - * only difference between two instrumentations, thus {@link - * io.opentelemetry.javaagent.instrumentation.jetty.v8_0.Jetty8HttpServerTracer} is a very thin - * subclass of {@link io.opentelemetry.instrumentation.servlet.v3_0.Servlet3HttpServerTracer}. + * only difference between two instrumentations, thus jetty instrumentation largely reuses servlet + * instrumentation. */ package io.opentelemetry.javaagent.instrumentation.jetty.v8_0; diff --git a/instrumentation/jetty/jetty-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/common/JettyHandlerAdviceHelper.java b/instrumentation/jetty/jetty-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/common/JettyHandlerAdviceHelper.java deleted file mode 100644 index 0af191a78854..000000000000 --- a/instrumentation/jetty/jetty-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/common/JettyHandlerAdviceHelper.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.jetty.common; - -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.servlet.ServletHttpServerTracer; -import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterAdviceHelper; - -public class JettyHandlerAdviceHelper { - /** Shared method exit implementation for Jetty handler advices. */ - public static void stopSpan( - ServletHttpServerTracer tracer, - REQUEST request, - RESPONSE response, - Throwable throwable, - Context context, - Scope scope) { - if (scope == null) { - return; - } - scope.close(); - - if (context == null) { - // an existing span was found - return; - } - - tracer.setPrincipal(context, request); - - // throwable is read-only, copy it to a new local that can be modified - Throwable exception = throwable; - if (exception == null) { - // on jetty versions before 9.4 exceptions from servlet don't propagate to this method - // check from request whether a throwable has been stored there - exception = tracer.errorException(request); - } - if (exception != null) { - tracer.endExceptionally(context, exception, response); - return; - } - - if (ServletAndFilterAdviceHelper.mustEndOnHandlerMethodExit(tracer, request)) { - tracer.end(context, response); - } - } -} 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 new file mode 100644 index 000000000000..47fa49cc4b60 --- /dev/null +++ b/instrumentation/jetty/jetty-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/common/JettyHelper.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jetty.common; + +import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTAINER; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.servlet.AppServerBridge; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletResponseContext; + +public class JettyHelper extends ServletHelper { + + public JettyHelper( + Instrumenter, ServletResponseContext> instrumenter, + ServletAccessor accessor) { + super(instrumenter, accessor); + } + + public Context start(Context parentContext, ServletRequestContext requestContext) { + return start(parentContext, requestContext, CONTAINER); + } + + @Override + protected Context customizeContext(Context context, REQUEST httpServletRequest) { + return AppServerBridge.init(context, /* shouldRecordException= */ false); + } + + public void end( + ServletRequestContext requestContext, + REQUEST request, + RESPONSE response, + Throwable throwable, + Context context, + Scope scope) { + + if (scope == null) { + return; + } + scope.close(); + + if (throwable == null) { + // on jetty versions before 9.4 exceptions from servlet don't propagate to this method + // check from request whether a throwable has been stored there + throwable = errorException(request); + } + + ServletResponseContext responseContext = + new ServletResponseContext<>(response, throwable); + if (throwable != null || mustEndOnHandlerMethodExit(request)) { + instrumenter.end(context, requestContext, responseContext, throwable); + } + } + + private Throwable errorException(REQUEST request) { + Object value = accessor.getRequestAttribute(request, errorExceptionAttributeName()); + + if (value instanceof Throwable) { + return (Throwable) value; + } else { + return null; + } + } + + private static String errorExceptionAttributeName() { + return "javax.servlet.error.exception"; + } +} diff --git a/instrumentation/liberty/liberty/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyHttpServerTracer.java b/instrumentation/liberty/liberty/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyHttpServerTracer.java index 4f3ef89c009f..297fb40fd221 100644 --- a/instrumentation/liberty/liberty/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyHttpServerTracer.java +++ b/instrumentation/liberty/liberty/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyHttpServerTracer.java @@ -6,6 +6,7 @@ package io.opentelemetry.javaagent.instrumentation.liberty; import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.servlet.AppServerBridge; import io.opentelemetry.instrumentation.servlet.v3_0.Servlet3HttpServerTracer; import javax.servlet.http.HttpServletRequest; @@ -20,6 +21,12 @@ public Context startSpan(HttpServletRequest request) { return startSpan(request, "HTTP " + request.getMethod(), /* servlet= */ false); } + @Override + protected Context customizeContext(Context context, HttpServletRequest httpServletRequest) { + context = super.customizeContext(context, httpServletRequest); + return AppServerBridge.init(context); + } + @Override protected String getInstrumentationName() { return "io.opentelemetry.liberty"; diff --git a/instrumentation/liberty/liberty/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyWebAppInstrumentation.java b/instrumentation/liberty/liberty/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyWebAppInstrumentation.java index 437874aac167..66d145e07011 100644 --- a/instrumentation/liberty/liberty/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyWebAppInstrumentation.java +++ b/instrumentation/liberty/liberty/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/LibertyWebAppInstrumentation.java @@ -11,6 +11,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.servlet.AppServerBridge; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterAdviceHelper; @@ -91,8 +92,13 @@ public static void stopSpan( tracer().setPrincipal(context, request); - if (throwable != null) { - tracer().endExceptionally(context, throwable, response); + Throwable error = throwable; + if (error == null) { + error = AppServerBridge.getException(context); + } + + if (error != null) { + tracer().endExceptionally(context, error, response); return; } diff --git a/instrumentation/servlet/servlet-2.2/javaagent/build.gradle.kts b/instrumentation/servlet/servlet-2.2/javaagent/build.gradle.kts index 3607fbff917b..c8e00410f10e 100644 --- a/instrumentation/servlet/servlet-2.2/javaagent/build.gradle.kts +++ b/instrumentation/servlet/servlet-2.2/javaagent/build.gradle.kts @@ -19,8 +19,8 @@ muzzle { dependencies { compileOnly("javax.servlet:servlet-api:2.2") - api(project(":instrumentation:servlet:servlet-2.2:library")) implementation(project(":instrumentation:servlet:servlet-common:javaagent")) + implementation(project(":instrumentation:servlet:servlet-javax-common:library")) testInstrumentation(project(":instrumentation:servlet:servlet-javax-common:javaagent")) diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/HttpServletResponseInstrumentation.java b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/HttpServletResponseInstrumentation.java index 1cefd9f717a5..905b5573a037 100644 --- a/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/HttpServletResponseInstrumentation.java +++ b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/HttpServletResponseInstrumentation.java @@ -16,7 +16,7 @@ import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import io.opentelemetry.javaagent.instrumentation.api.CallDepth; import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext; -import javax.servlet.ServletRequest; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import net.bytebuddy.asm.Advice; @@ -30,9 +30,9 @@ * created span using just response object. * *

This instrumentation intercepts status setting methods from Servlet 2.0 specification and - * stores that status into context store. Then {@link Servlet2Advice#stopSpan(ServletRequest, - * ServletResponse, Throwable, CallDepth, Context, Scope)} can get it from context and set required - * span attribute. + * stores that status into context store. Then {@link Servlet2Advice#stopSpan(ServletResponse, + * Throwable, CallDepth, ServletRequestContext, Context, Scope)} can get it from context and set + * required span attribute. */ public class HttpServletResponseInstrumentation implements TypeInstrumentation { @Override diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Accessor.java b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Accessor.java new file mode 100644 index 000000000000..a30f0714c139 --- /dev/null +++ b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Accessor.java @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v2_2; + +import io.opentelemetry.instrumentation.servlet.ServletAsyncListener; +import io.opentelemetry.instrumentation.servlet.javax.JavaxServletAccessor; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class Servlet2Accessor extends JavaxServletAccessor { + public static final Servlet2Accessor INSTANCE = new Servlet2Accessor(); + + private Servlet2Accessor() {} + + @Override + public Integer getRequestRemotePort(HttpServletRequest httpServletRequest) { + return null; + } + + @Override + public void addRequestAsyncListener( + HttpServletRequest httpServletRequest, + ServletAsyncListener listener, + Object response) { + throw new UnsupportedOperationException(); + } + + @Override + public int getResponseStatus(HttpServletResponse httpServletResponse) { + throw new UnsupportedOperationException(); + } + + @Override + public String getResponseHeader(HttpServletResponse httpServletResponse, String name) { + return null; + } + + @Override + public boolean isResponseCommitted(HttpServletResponse httpServletResponse) { + return httpServletResponse.isCommitted(); + } +} diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Advice.java b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Advice.java index 3a1526e8d8b9..eb90a3d4cc61 100644 --- a/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Advice.java +++ b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Advice.java @@ -5,15 +5,15 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v2_2; -import static io.opentelemetry.instrumentation.servlet.v2_2.Servlet2HttpServerTracer.tracer; +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.servlet.AppServerBridge; -import io.opentelemetry.instrumentation.servlet.v2_2.ResponseWithStatus; import io.opentelemetry.javaagent.instrumentation.api.CallDepth; import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext; import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; @@ -29,6 +29,7 @@ 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 requestContext, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey()); @@ -40,9 +41,9 @@ public static void onEnter( HttpServletRequest httpServletRequest = (HttpServletRequest) request; - Context serverContext = tracer().getServerContext(httpServletRequest); + Context serverContext = helper().getServerContext(httpServletRequest); if (serverContext != null) { - Context updatedContext = tracer().updateContext(serverContext, httpServletRequest); + Context updatedContext = helper().updateContext(serverContext, httpServletRequest); if (updatedContext != serverContext) { // updateContext updated context, need to re-scope scope = updatedContext.makeCurrent(); @@ -50,7 +51,14 @@ public static void onEnter( return; } - context = tracer().startSpan(httpServletRequest); + Context parentContext = Java8BytecodeBridge.currentContext(); + requestContext = new ServletRequestContext<>(httpServletRequest); + + if (!helper().shouldStart(parentContext, requestContext)) { + return; + } + + context = helper().startSpan(parentContext, requestContext); scope = context.makeCurrent(); // reset response status from previous request // (some servlet containers reuse response objects to reduce memory allocations) @@ -59,10 +67,10 @@ public static void onEnter( @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 requestContext, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { @@ -77,17 +85,14 @@ public static void stopSpan( // 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) { - tracer().addUnwrappedThrowable(currentContext, throwable); + helper().recordException(currentContext, throwable); } - tracer().setPrincipal(currentContext, (HttpServletRequest) request); } if (scope == null || context == null) { return; } - tracer().setPrincipal(context, (HttpServletRequest) request); - int responseStatusCode = HttpServletResponse.SC_OK; Integer responseStatus = InstrumentationContext.get(ServletResponse.class, Integer.class).get(response); @@ -95,12 +100,8 @@ public static void stopSpan( responseStatusCode = responseStatus; } - ResponseWithStatus responseWithStatus = - new ResponseWithStatus((HttpServletResponse) response, responseStatusCode); - if (throwable == null) { - tracer().end(context, responseWithStatus); - } else { - tracer().endExceptionally(context, throwable, responseWithStatus); - } + helper() + .stopSpan( + context, requestContext, (HttpServletResponse) response, responseStatusCode, throwable); } } diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Helper.java b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Helper.java new file mode 100644 index 000000000000..27332030f9fa --- /dev/null +++ b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Helper.java @@ -0,0 +1,46 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v2_2; + +import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.SERVLET; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.instrumentation.servlet.BaseServletHelper; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletResponseContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class Servlet2Helper extends BaseServletHelper { + + Servlet2Helper( + Instrumenter< + ServletRequestContext, + ServletResponseContext> + instrumenter) { + super(instrumenter, Servlet2Accessor.INSTANCE); + } + + public Context startSpan( + Context parentContext, ServletRequestContext requestContext) { + return start(parentContext, requestContext, SERVLET); + } + + public void stopSpan( + Context context, + ServletRequestContext requestContext, + HttpServletResponse response, + int statusCode, + Throwable throwable) { + + ServletResponseContext responseContext = + new ServletResponseContext<>(response, throwable); + responseContext.setStatus(statusCode); + + instrumenter.end(context, requestContext, responseContext, throwable); + } +} diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2HttpAttributesExtractor.java b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2HttpAttributesExtractor.java new file mode 100644 index 000000000000..61d32340597b --- /dev/null +++ b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2HttpAttributesExtractor.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v2_2; + +import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletHttpAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletResponseContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class Servlet2HttpAttributesExtractor + extends ServletHttpAttributesExtractor { + public Servlet2HttpAttributesExtractor( + ServletAccessor accessor) { + super(accessor); + } + + @Override + protected @Nullable Integer statusCode( + ServletRequestContext requestContext, + ServletResponseContext responseContext) { + HttpServletResponse response = responseContext.response(); + + if (!accessor.isResponseCommitted(response) && responseContext.error() != null) { + // if response is not committed and there is a throwable set status to 500 / + // INTERNAL_SERVER_ERROR, due to servlet spec + // https://javaee.github.io/servlet-spec/downloads/servlet-4.0/servlet-4_0_FINAL.pdf: + // "If a servlet generates an error that is not handled by the error page mechanism as + // described above, the container must ensure to send a response with status 500." + return 500; + } + if (responseContext.hasStatus()) { + return responseContext.getStatus(); + } + return null; + } +} diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Singletons.java b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Singletons.java new file mode 100644 index 000000000000..18003fa51098 --- /dev/null +++ b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Singletons.java @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v2_2; + +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletInstrumenterBuilder; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletResponseContext; +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"; + + private static final Servlet2Helper HELPER; + + static { + HttpAttributesExtractor< + ServletRequestContext, ServletResponseContext> + httpAttributesExtractor = new Servlet2HttpAttributesExtractor(Servlet2Accessor.INSTANCE); + SpanNameExtractor> spanNameExtractor = + new Servlet2SpanNameExtractor<>(Servlet2Accessor.INSTANCE); + + Instrumenter< + ServletRequestContext, ServletResponseContext> + instrumenter = + ServletInstrumenterBuilder.newInstrumenter( + INSTRUMENTATION_NAME, + Servlet2Accessor.INSTANCE, + spanNameExtractor, + httpAttributesExtractor); + HELPER = new Servlet2Helper(instrumenter); + } + + public static Servlet2Helper helper() { + return HELPER; + } + + private Servlet2Singletons() {} +} diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2SpanNameExtractor.java b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2SpanNameExtractor.java new file mode 100644 index 000000000000..051a939b4824 --- /dev/null +++ b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2SpanNameExtractor.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v2_2; + +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; + +public class Servlet2SpanNameExtractor + implements SpanNameExtractor> { + private final ServletAccessor accessor; + + public Servlet2SpanNameExtractor(ServletAccessor accessor) { + this.accessor = accessor; + } + + @Override + public String extract(ServletRequestContext requestContext) { + REQUEST request = requestContext.request(); + String servletPath = accessor.getRequestServletPath(request); + if (!servletPath.isEmpty()) { + String contextPath = accessor.getRequestContextPath(request); + if (contextPath == null || contextPath.isEmpty() || contextPath.equals("/")) { + return servletPath; + } + + return contextPath + servletPath; + } + + String method = accessor.getRequestMethod(request); + if (method != null) { + return "HTTP " + method; + } + return "HTTP request"; + } +} diff --git a/instrumentation/servlet/servlet-2.2/library/build.gradle.kts b/instrumentation/servlet/servlet-2.2/library/build.gradle.kts deleted file mode 100644 index 2a0dea789b62..000000000000 --- a/instrumentation/servlet/servlet-2.2/library/build.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - id("otel.library-instrumentation") -} - -dependencies { - implementation("org.slf4j:slf4j-api") - - api(project(":instrumentation:servlet:servlet-javax-common:library")) - - compileOnly("javax.servlet:servlet-api:2.2") -} diff --git a/instrumentation/servlet/servlet-2.2/library/src/main/java/io/opentelemetry/instrumentation/servlet/v2_2/ResponseWithStatus.java b/instrumentation/servlet/servlet-2.2/library/src/main/java/io/opentelemetry/instrumentation/servlet/v2_2/ResponseWithStatus.java deleted file mode 100644 index 64bb260b668d..000000000000 --- a/instrumentation/servlet/servlet-2.2/library/src/main/java/io/opentelemetry/instrumentation/servlet/v2_2/ResponseWithStatus.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.servlet.v2_2; - -import javax.servlet.http.HttpServletResponse; - -public class ResponseWithStatus { - - private final HttpServletResponse response; - private final int status; - - public ResponseWithStatus(HttpServletResponse response, int status) { - this.response = response; - this.status = status; - } - - public HttpServletResponse getResponse() { - return response; - } - - public int getStatus() { - return status; - } -} diff --git a/instrumentation/servlet/servlet-2.2/library/src/main/java/io/opentelemetry/instrumentation/servlet/v2_2/Servlet2Accessor.java b/instrumentation/servlet/servlet-2.2/library/src/main/java/io/opentelemetry/instrumentation/servlet/v2_2/Servlet2Accessor.java deleted file mode 100644 index 018a4933e32d..000000000000 --- a/instrumentation/servlet/servlet-2.2/library/src/main/java/io/opentelemetry/instrumentation/servlet/v2_2/Servlet2Accessor.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.servlet.v2_2; - -import io.opentelemetry.instrumentation.servlet.ServletAsyncListener; -import io.opentelemetry.instrumentation.servlet.javax.JavaxServletAccessor; -import javax.servlet.http.HttpServletRequest; - -public class Servlet2Accessor extends JavaxServletAccessor { - public static final Servlet2Accessor INSTANCE = new Servlet2Accessor(); - - private Servlet2Accessor() {} - - @Override - public Integer getRequestRemotePort(HttpServletRequest httpServletRequest) { - return null; - } - - @Override - public void addRequestAsyncListener( - HttpServletRequest request, - ServletAsyncListener listener, - Object response) { - throw new UnsupportedOperationException(); - } - - @Override - public int getResponseStatus(ResponseWithStatus responseWithStatus) { - return responseWithStatus.getStatus(); - } - - @Override - public boolean isResponseCommitted(ResponseWithStatus responseWithStatus) { - return responseWithStatus.getResponse().isCommitted(); - } -} diff --git a/instrumentation/servlet/servlet-2.2/library/src/main/java/io/opentelemetry/instrumentation/servlet/v2_2/Servlet2HttpServerTracer.java b/instrumentation/servlet/servlet-2.2/library/src/main/java/io/opentelemetry/instrumentation/servlet/v2_2/Servlet2HttpServerTracer.java deleted file mode 100644 index 4f62a0e831ad..000000000000 --- a/instrumentation/servlet/servlet-2.2/library/src/main/java/io/opentelemetry/instrumentation/servlet/v2_2/Servlet2HttpServerTracer.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.servlet.v2_2; - -import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.SERVLET; - -import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.servlet.ServerSpanNameSupplier; -import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming; -import io.opentelemetry.instrumentation.servlet.javax.JavaxServletHttpServerTracer; -import javax.servlet.http.HttpServletRequest; - -public class Servlet2HttpServerTracer extends JavaxServletHttpServerTracer { - private static final Servlet2HttpServerTracer TRACER = new Servlet2HttpServerTracer(); - - private final ServerSpanNameSupplier serverSpanName = - (context, request) -> getSpanName(request); - - public Servlet2HttpServerTracer() { - super(Servlet2Accessor.INSTANCE); - } - - public static Servlet2HttpServerTracer tracer() { - return TRACER; - } - - public Context startSpan(HttpServletRequest request) { - return startSpan(request, getSpanName(request), true); - } - - @Override - public Context updateContext(Context context, HttpServletRequest request) { - ServerSpanNaming.updateServerSpanName(context, SERVLET, serverSpanName, request); - return super.updateContext(context, request); - } - - @Override - protected String getInstrumentationName() { - return "io.opentelemetry.servlet-2.2"; - } -} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts b/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts index 15dfadcf13eb..182869d6219e 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts +++ b/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts @@ -18,7 +18,7 @@ muzzle { dependencies { compileOnly("javax.servlet:javax.servlet-api:3.0.1") api(project(":instrumentation:servlet:servlet-3.0:library")) - implementation(project(":instrumentation:servlet:servlet-common:javaagent")) + api(project(":instrumentation:servlet:servlet-common:javaagent")) testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent")) testInstrumentation(project(":instrumentation:servlet:servlet-javax-common:javaagent")) 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 296e349306f0..7d5db4ca5df0 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 @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; -import static io.opentelemetry.instrumentation.servlet.v3_0.Servlet3HttpServerTracer.tracer; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; @@ -15,7 +15,7 @@ import io.opentelemetry.javaagent.instrumentation.api.CallDepth; import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext; import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; -import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterAdviceHelper; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; import javax.servlet.Filter; import javax.servlet.Servlet; import javax.servlet.ServletRequest; @@ -34,6 +34,7 @@ public static void onEnter( @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 requestContext, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { @@ -59,10 +60,10 @@ public static void onEnter( } Context currentContext = Java8BytecodeBridge.currentContext(); - Context attachedContext = tracer().getServerContext(httpServletRequest); - if (attachedContext != null && tracer().needsRescoping(currentContext, attachedContext)) { + Context attachedContext = helper().getServerContext(httpServletRequest); + if (attachedContext != null && helper().needsRescoping(currentContext, attachedContext)) { attachedContext = - tracer().updateContext(attachedContext, httpServletRequest, mappingResolver, servlet); + helper().updateContext(attachedContext, httpServletRequest, mappingResolver, servlet); scope = attachedContext.makeCurrent(); // We are inside nested servlet/filter/app-server span, don't create new span return; @@ -75,7 +76,7 @@ public static void onEnter( // returns a new context that contains servlet context path that is used in other // instrumentations for naming server span. Context updatedContext = - tracer().updateContext(currentContext, httpServletRequest, mappingResolver, servlet); + helper().updateContext(currentContext, httpServletRequest, mappingResolver, servlet); if (currentContext != updatedContext) { // updateContext updated context, need to re-scope scope = updatedContext.makeCurrent(); @@ -84,10 +85,16 @@ public static void onEnter( return; } - context = tracer().startSpan(httpServletRequest, mappingResolver, servlet); + requestContext = new ServletRequestContext<>(httpServletRequest, mappingResolver); + + if (!helper().shouldStart(currentContext, requestContext)) { + return; + } + + context = helper().start(currentContext, requestContext, servlet); scope = context.makeCurrent(); - tracer().setAsyncListenerResponse(httpServletRequest, (HttpServletResponse) response); + helper().setAsyncListenerResponse(httpServletRequest, (HttpServletResponse) response); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @@ -96,22 +103,23 @@ public static void stopSpan( @Advice.Argument(1) ServletResponse response, @Advice.Thrown Throwable throwable, @Advice.Local("otelCallDepth") CallDepth callDepth, + @Advice.Local("otelRequest") ServletRequestContext requestContext, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - boolean topLevel = callDepth.decrementAndGet() == 0; - if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { return; } - ServletAndFilterAdviceHelper.stopSpan( - tracer(), - (HttpServletRequest) request, - (HttpServletResponse) response, - throwable, - topLevel, - context, - scope); + boolean topLevel = callDepth.decrementAndGet() == 0; + helper() + .end( + requestContext, + (HttpServletRequest) request, + (HttpServletResponse) response, + throwable, + topLevel, + context, + scope); } } 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 d1545a4ff30c..9b676aabea58 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 @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; -import static io.opentelemetry.instrumentation.servlet.v3_0.Servlet3HttpServerTracer.tracer; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; import io.opentelemetry.javaagent.instrumentation.api.CallDepth; import javax.servlet.AsyncContext; @@ -36,8 +36,8 @@ public static void startAsyncExit( if (servletRequest instanceof HttpServletRequest) { HttpServletRequest request = (HttpServletRequest) servletRequest; - if (!tracer().isAsyncListenerAttached(request)) { - tracer().attachAsyncListener(request); + if (!helper().isAsyncListenerAttached(request)) { + helper().attachAsyncListener(request); } } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java new file mode 100644 index 000000000000..2c41e64f47dd --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.servlet.v3_0.Servlet3Accessor; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletInstrumenterBuilder; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletResponseContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public final class Servlet3Singletons { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.servlet-3.0"; + + private static final Instrumenter< + ServletRequestContext, ServletResponseContext> + INSTRUMENTER = + ServletInstrumenterBuilder.newInstrumenter( + INSTRUMENTATION_NAME, Servlet3Accessor.INSTANCE); + private static final ServletHelper HELPER = + new ServletHelper<>(INSTRUMENTER, Servlet3Accessor.INSTANCE); + + public static ServletHelper helper() { + return HELPER; + } + + private Servlet3Singletons() {} +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/Servlet3Accessor.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/Servlet3Accessor.java index 5bd5900df897..a3cb44b56f3d 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/Servlet3Accessor.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/Servlet3Accessor.java @@ -39,6 +39,11 @@ public int getResponseStatus(HttpServletResponse response) { return response.getStatus(); } + @Override + public String getResponseHeader(HttpServletResponse response, String name) { + return response.getHeader(name); + } + @Override public boolean isResponseCommitted(HttpServletResponse response) { return response.isCommitted(); diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/Servlet3HttpServerTracer.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/Servlet3HttpServerTracer.java index 801fc6bc855c..46b544960819 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/Servlet3HttpServerTracer.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/Servlet3HttpServerTracer.java @@ -16,6 +16,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +@Deprecated public class Servlet3HttpServerTracer extends JavaxServletHttpServerTracer { private static final Servlet3HttpServerTracer TRACER = new Servlet3HttpServerTracer(); private static final ServletSpanNameProvider SPAN_NAME_PROVIDER = diff --git a/instrumentation/servlet/servlet-5.0/javaagent/build.gradle.kts b/instrumentation/servlet/servlet-5.0/javaagent/build.gradle.kts index 34e703c605b0..a096df262924 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/build.gradle.kts +++ b/instrumentation/servlet/servlet-5.0/javaagent/build.gradle.kts @@ -12,8 +12,8 @@ muzzle { } dependencies { - api(project(":instrumentation:servlet:servlet-5.0:library")) - implementation(project(":instrumentation:servlet:servlet-common:javaagent")) + api(project(":instrumentation:servlet:servlet-common:javaagent")) + implementation(project(":instrumentation:servlet:servlet-common:library")) compileOnly("jakarta.servlet:jakarta.servlet-api:5.0.0") testLibrary("org.eclipse.jetty:jetty-server:11.0.0") diff --git a/instrumentation/servlet/servlet-5.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/jakarta/v5_0/JakartaServletAccessor.java b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/Servlet5Accessor.java similarity index 82% rename from instrumentation/servlet/servlet-5.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/jakarta/v5_0/JakartaServletAccessor.java rename to instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/Servlet5Accessor.java index 690da46fad82..677cddccc516 100644 --- a/instrumentation/servlet/servlet-5.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/jakarta/v5_0/JakartaServletAccessor.java +++ b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/Servlet5Accessor.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.servlet.jakarta.v5_0; +package io.opentelemetry.javaagent.instrumentation.servlet.v5_0; import io.opentelemetry.instrumentation.servlet.ServletAccessor; import io.opentelemetry.instrumentation.servlet.ServletAsyncListener; @@ -13,12 +13,12 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.security.Principal; +import java.util.Collections; -public class JakartaServletAccessor - implements ServletAccessor { - public static final JakartaServletAccessor INSTANCE = new JakartaServletAccessor(); +public class Servlet5Accessor implements ServletAccessor { + public static final Servlet5Accessor INSTANCE = new Servlet5Accessor(); - private JakartaServletAccessor() {} + private Servlet5Accessor() {} @Override public String getRequestContextPath(HttpServletRequest request) { @@ -80,6 +80,11 @@ public String getRequestHeader(HttpServletRequest request, String name) { return request.getHeader(name); } + @Override + public Iterable getRequestHeaderNames(HttpServletRequest httpServletRequest) { + return Collections.list(httpServletRequest.getHeaderNames()); + } + @Override public String getRequestServletPath(HttpServletRequest request) { return request.getServletPath(); @@ -100,6 +105,16 @@ public Integer getRequestRemotePort(HttpServletRequest request) { return request.getRemotePort(); } + @Override + public String getRequestRemoteHost(HttpServletRequest request) { + return request.getRemoteHost(); + } + + @Override + public int getRequestContentLength(HttpServletRequest request) { + return request.getContentLength(); + } + @Override public void addRequestAsyncListener( HttpServletRequest request, @@ -117,6 +132,11 @@ public int getResponseStatus(HttpServletResponse response) { return response.getStatus(); } + @Override + public String getResponseHeader(HttpServletResponse response, String name) { + return response.getHeader(name); + } + @Override public boolean isResponseCommitted(HttpServletResponse response) { return response.isCommitted(); diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/Servlet5Singletons.java b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/Servlet5Singletons.java new file mode 100644 index 000000000000..5381b1c8cc41 --- /dev/null +++ b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/Servlet5Singletons.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v5_0; + +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletInstrumenterBuilder; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletResponseContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public final class Servlet5Singletons { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.servlet-5.0"; + + private static final Instrumenter< + ServletRequestContext, ServletResponseContext> + INSTRUMENTER = + ServletInstrumenterBuilder.newInstrumenter( + INSTRUMENTATION_NAME, Servlet5Accessor.INSTANCE); + private static final ServletHelper HELPER = + new ServletHelper<>(INSTRUMENTER, Servlet5Accessor.INSTANCE); + + public static ServletHelper helper() { + return HELPER; + } + + private Servlet5Singletons() {} +} 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 71f0eab527ae..0da88f03bf94 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 @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v5_0.async; -import static io.opentelemetry.instrumentation.servlet.jakarta.v5_0.JakartaServletHttpServerTracer.tracer; +import static io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Singletons.helper; import io.opentelemetry.javaagent.instrumentation.api.CallDepth; import jakarta.servlet.AsyncContext; @@ -34,8 +34,8 @@ public static void startAsyncExit( } if (request != null) { - if (!tracer().isAsyncListenerAttached(request)) { - tracer().attachAsyncListener(request); + if (!helper().isAsyncListenerAttached(request)) { + helper().attachAsyncListener(request); } } } 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 a36366195d22..3972fdc0caa7 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 @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v5_0.service; -import static io.opentelemetry.instrumentation.servlet.jakarta.v5_0.JakartaServletHttpServerTracer.tracer; +import static io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Singletons.helper; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; @@ -15,7 +15,7 @@ import io.opentelemetry.javaagent.instrumentation.api.CallDepth; import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext; import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; -import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterAdviceHelper; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; import jakarta.servlet.Filter; import jakarta.servlet.Servlet; import jakarta.servlet.ServletRequest; @@ -34,6 +34,7 @@ public static void onEnter( @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 requestContext, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { @@ -59,10 +60,10 @@ public static void onEnter( } Context currentContext = Java8BytecodeBridge.currentContext(); - Context attachedContext = tracer().getServerContext(httpServletRequest); - if (attachedContext != null && tracer().needsRescoping(currentContext, attachedContext)) { + Context attachedContext = helper().getServerContext(httpServletRequest); + if (attachedContext != null && helper().needsRescoping(currentContext, attachedContext)) { attachedContext = - tracer().updateContext(attachedContext, httpServletRequest, mappingResolver, servlet); + helper().updateContext(attachedContext, httpServletRequest, mappingResolver, servlet); scope = attachedContext.makeCurrent(); // We are inside nested servlet/filter/app-server span, don't create new span return; @@ -75,7 +76,7 @@ public static void onEnter( // returns a new context that contains servlet context path that is used in other // instrumentations for naming server span. Context updatedContext = - tracer().updateContext(currentContext, httpServletRequest, mappingResolver, servlet); + helper().updateContext(currentContext, httpServletRequest, mappingResolver, servlet); if (currentContext != updatedContext) { // updateContext updated context, need to re-scope scope = updatedContext.makeCurrent(); @@ -84,10 +85,16 @@ public static void onEnter( return; } - context = tracer().startSpan(httpServletRequest, mappingResolver, servlet); + requestContext = new ServletRequestContext<>(httpServletRequest, mappingResolver); + + if (!helper().shouldStart(currentContext, requestContext)) { + return; + } + + context = helper().start(currentContext, requestContext, servlet); scope = context.makeCurrent(); - tracer().setAsyncListenerResponse(httpServletRequest, (HttpServletResponse) response); + helper().setAsyncListenerResponse(httpServletRequest, (HttpServletResponse) response); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @@ -96,6 +103,7 @@ public static void stopSpan( @Advice.Argument(1) ServletResponse response, @Advice.Thrown Throwable throwable, @Advice.Local("otelCallDepth") CallDepth callDepth, + @Advice.Local("otelRequest") ServletRequestContext requestContext, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { @@ -105,13 +113,14 @@ public static void stopSpan( return; } - ServletAndFilterAdviceHelper.stopSpan( - tracer(), - (HttpServletRequest) request, - (HttpServletResponse) response, - throwable, - topLevel, - context, - scope); + helper() + .end( + requestContext, + (HttpServletRequest) request, + (HttpServletResponse) response, + throwable, + topLevel, + context, + scope); } } diff --git a/instrumentation/servlet/servlet-5.0/library/build.gradle.kts b/instrumentation/servlet/servlet-5.0/library/build.gradle.kts deleted file mode 100644 index d087dbc9e064..000000000000 --- a/instrumentation/servlet/servlet-5.0/library/build.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - id("otel.library-instrumentation") -} - -dependencies { - api(project(":instrumentation:servlet:servlet-common:library")) - - compileOnly("jakarta.servlet:jakarta.servlet-api:5.0.0") - - testImplementation("jakarta.servlet:jakarta.servlet-api:5.0.0") - testImplementation("org.mockito:mockito-core:3.6.0") - testImplementation("org.assertj:assertj-core") -} diff --git a/instrumentation/servlet/servlet-5.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/jakarta/v5_0/JakartaHttpServletRequestGetter.java b/instrumentation/servlet/servlet-5.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/jakarta/v5_0/JakartaHttpServletRequestGetter.java deleted file mode 100644 index 24ab41aab743..000000000000 --- a/instrumentation/servlet/servlet-5.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/jakarta/v5_0/JakartaHttpServletRequestGetter.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.servlet.jakarta.v5_0; - -import io.opentelemetry.context.propagation.TextMapGetter; -import jakarta.servlet.http.HttpServletRequest; -import java.util.Collections; - -public class JakartaHttpServletRequestGetter implements TextMapGetter { - - public static final JakartaHttpServletRequestGetter GETTER = - new JakartaHttpServletRequestGetter(); - - @Override - public Iterable keys(HttpServletRequest carrier) { - return Collections.list(carrier.getHeaderNames()); - } - - @Override - public String get(HttpServletRequest carrier, String key) { - return carrier.getHeader(key); - } -} diff --git a/instrumentation/servlet/servlet-5.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/jakarta/v5_0/JakartaServletHttpServerTracer.java b/instrumentation/servlet/servlet-5.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/jakarta/v5_0/JakartaServletHttpServerTracer.java deleted file mode 100644 index 29114c69e857..000000000000 --- a/instrumentation/servlet/servlet-5.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/jakarta/v5_0/JakartaServletHttpServerTracer.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.servlet.jakarta.v5_0; - -import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.FILTER; -import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.SERVLET; - -import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.instrumentation.api.servlet.MappingResolver; -import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming; -import io.opentelemetry.instrumentation.servlet.ServletHttpServerTracer; -import io.opentelemetry.instrumentation.servlet.naming.ServletSpanNameProvider; -import jakarta.servlet.RequestDispatcher; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -public class JakartaServletHttpServerTracer - extends ServletHttpServerTracer { - private static final JakartaServletHttpServerTracer TRACER = new JakartaServletHttpServerTracer(); - private static final ServletSpanNameProvider SPAN_NAME_PROVIDER = - new ServletSpanNameProvider<>(JakartaServletAccessor.INSTANCE); - - public JakartaServletHttpServerTracer() { - super(JakartaServletAccessor.INSTANCE); - } - - public static JakartaServletHttpServerTracer tracer() { - return TRACER; - } - - public Context startSpan( - HttpServletRequest request, MappingResolver mappingResolver, boolean servlet) { - return startSpan(request, SPAN_NAME_PROVIDER.getSpanName(mappingResolver, request), servlet); - } - - public Context updateContext( - Context context, - HttpServletRequest request, - MappingResolver mappingResolver, - boolean servlet) { - ServerSpanNaming.updateServerSpanName( - context, - servlet ? SERVLET : FILTER, - () -> SPAN_NAME_PROVIDER.getSpanNameOrNull(mappingResolver, request)); - return updateContext(context, request); - } - - @Override - protected String getInstrumentationName() { - return "io.opentelemetry.servlet-5.0"; - } - - @Override - protected TextMapGetter getGetter() { - return JakartaHttpServletRequestGetter.GETTER; - } - - @Override - protected String errorExceptionAttributeName() { - return RequestDispatcher.ERROR_EXCEPTION; - } -} diff --git a/instrumentation/servlet/servlet-5.0/library/src/test/java/JakartaServletHttpServerTracerTest.java b/instrumentation/servlet/servlet-5.0/library/src/test/java/JakartaServletHttpServerTracerTest.java deleted file mode 100644 index 11b64d9b9242..000000000000 --- a/instrumentation/servlet/servlet-5.0/library/src/test/java/JakartaServletHttpServerTracerTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import io.opentelemetry.instrumentation.servlet.jakarta.v5_0.JakartaServletHttpServerTracer; -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.Test; - -public class JakartaServletHttpServerTracerTest { - private static final JakartaServletHttpServerTracer tracer = - JakartaServletHttpServerTracer.tracer(); - - @Test - void testGetSpanName_emptySpanName() { - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getServletPath()).thenReturn(""); - when(request.getMethod()).thenReturn("PUT"); - String spanName = tracer.getSpanName(request); - assertThat(spanName).isEqualTo("HTTP PUT"); - } - - @Test - void testGetSpanName_nullSpanName() { - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getServletPath()).thenReturn(null); - assertThatThrownBy(() -> tracer.getSpanName(request)).isInstanceOf(NullPointerException.class); - } - - @Test - void testGetSpanName_nullContextPath() { - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getServletPath()).thenReturn("/swizzler"); - when(request.getContextPath()).thenReturn(null); - String spanName = tracer.getSpanName(request); - assertThat(spanName).isEqualTo("/swizzler"); - } - - @Test - void testGetSpanName_emptyContextPath() { - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getServletPath()).thenReturn("/swizzler"); - when(request.getContextPath()).thenReturn(""); - String spanName = tracer.getSpanName(request); - assertThat(spanName).isEqualTo("/swizzler"); - } - - @Test - void testGetSpanName_slashContextPath() { - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getServletPath()).thenReturn("/swizzler"); - when(request.getContextPath()).thenReturn("/"); - String spanName = tracer.getSpanName(request); - assertThat(spanName).isEqualTo("/swizzler"); - } - - @Test - void testGetSpanName_appendsSpanNameToContext() { - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getServletPath()).thenReturn("/swizzler"); - when(request.getContextPath()).thenReturn("/path/to"); - String spanName = tracer.getSpanName(request); - assertThat(spanName).isEqualTo("/path/to/swizzler"); - } -} 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 new file mode 100644 index 000000000000..a1d3ac3839f5 --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRequestCompletionListener.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.servlet.ServletAsyncListener; +import java.util.concurrent.atomic.AtomicBoolean; + +public class AsyncRequestCompletionListener + implements ServletAsyncListener { + private final ServletHelper servletHelper; + private final Instrumenter, ServletResponseContext> + instrumenter; + private final ServletRequestContext requestContext; + private final Context context; + private final AtomicBoolean responseHandled = new AtomicBoolean(); + + public AsyncRequestCompletionListener( + ServletHelper servletHelper, + Instrumenter, ServletResponseContext> instrumenter, + ServletRequestContext requestContext, + Context context) { + this.servletHelper = servletHelper; + this.instrumenter = instrumenter; + this.requestContext = requestContext; + this.context = context; + } + + @Override + public void onComplete(RESPONSE response) { + if (responseHandled.compareAndSet(false, true)) { + ServletResponseContext responseContext = + new ServletResponseContext<>(response, null); + instrumenter.end(context, requestContext, responseContext, null); + } + } + + @Override + public void onTimeout(long timeout) { + if (responseHandled.compareAndSet(false, true)) { + RESPONSE response = servletHelper.getAsyncListenerResponse(requestContext.request()); + ServletResponseContext responseContext = + new ServletResponseContext<>(response, null); + responseContext.setTimeout(timeout); + instrumenter.end(context, requestContext, responseContext, null); + } + } + + @Override + public void onError(Throwable throwable, RESPONSE response) { + if (responseHandled.compareAndSet(false, true)) { + ServletResponseContext responseContext = + new ServletResponseContext<>(response, throwable); + instrumenter.end(context, requestContext, responseContext, 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 new file mode 100644 index 000000000000..69c61427fbd9 --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/BaseServletHelper.java @@ -0,0 +1,133 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet; + +import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.FILTER; +import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.SERVLET; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.servlet.AppServerBridge; +import io.opentelemetry.instrumentation.api.servlet.MappingResolver; +import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming; +import io.opentelemetry.instrumentation.api.servlet.ServletContextPath; +import io.opentelemetry.instrumentation.api.tracer.HttpServerTracer; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import io.opentelemetry.instrumentation.servlet.naming.ServletSpanNameProvider; + +public abstract class BaseServletHelper { + protected final Instrumenter, ServletResponseContext> + instrumenter; + protected final ServletAccessor accessor; + private final ServletSpanNameProvider spanNameProvider; + + protected BaseServletHelper( + Instrumenter, ServletResponseContext> instrumenter, + ServletAccessor accessor) { + this.instrumenter = instrumenter; + this.accessor = accessor; + this.spanNameProvider = new ServletSpanNameProvider<>(accessor); + } + + public boolean shouldStart(Context parentContext, ServletRequestContext requestContext) { + return instrumenter.shouldStart(parentContext, requestContext); + } + + protected Context start( + Context parentContext, + ServletRequestContext requestContext, + ServerSpanNaming.Source namingSource) { + Context context = instrumenter.start(parentContext, requestContext); + + REQUEST request = requestContext.request(); + SpanContext spanContext = Span.fromContext(context).getSpanContext(); + // we do this e.g. so that servlet containers can use these values in their access logs + // TODO: These are only available when using servlet instrumentation or when server + // instrumentation extends servlet instrumentation e.g. jetty. Either remove or make sure they + // also work on tomcat and wildfly. + accessor.setRequestAttribute(request, "trace_id", spanContext.getTraceId()); + accessor.setRequestAttribute(request, "span_id", spanContext.getSpanId()); + + context = ServerSpanNaming.init(context, namingSource); + context = addServletContextPath(context, request); + context = customizeContext(context, request); + + attachServerContext(context, request); + + return context; + } + + /** Override in subclass to customize context that is returned by {@code startSpan}. */ + protected Context customizeContext(Context context, REQUEST request) { + return context; + } + + private Context addServletContextPath(Context context, REQUEST request) { + String contextPath = accessor.getRequestContextPath(request); + if (contextPath != null && !contextPath.isEmpty() && !contextPath.equals("/")) { + return context.with(ServletContextPath.CONTEXT_KEY, contextPath); + } + return context; + } + + public Context getServerContext(REQUEST request) { + Object context = accessor.getRequestAttribute(request, HttpServerTracer.CONTEXT_ATTRIBUTE); + return context instanceof Context ? (Context) context : null; + } + + private void attachServerContext(Context context, REQUEST request) { + accessor.setRequestAttribute(request, HttpServerTracer.CONTEXT_ATTRIBUTE, context); + } + + public void recordException(Context context, Throwable throwable) { + AppServerBridge.recordException(context, throwable); + } + + /** + * When server spans are managed by app server instrumentation we need to add context path of + * current request to context if it isn't already added. Servlet instrumentation adds it when it + * starts server span. + */ + public Context updateContext(Context context, REQUEST request) { + String contextPath = context.get(ServletContextPath.CONTEXT_KEY); + if (contextPath == null) { + context = addServletContextPath(context, request); + } + + return context; + } + + public Context updateContext( + Context context, REQUEST request, MappingResolver mappingResolver, boolean servlet) { + ServerSpanNaming.updateServerSpanName( + context, + servlet ? SERVLET : FILTER, + () -> spanNameProvider.getSpanNameOrNull(mappingResolver, request)); + return updateContext(context, request); + } + + /* + Given request already has a context associated with it. + As there should not be nested spans of kind SERVER, we should NOT create a new span here. + + But it may happen that there is no span in current Context or it is from a different trace. + E.g. in case of async servlet request processing we create span for incoming request in one thread, + but actual request continues processing happens in another thread. + Depending on servlet container implementation, this processing may again arrive into this method. + E.g. Jetty handles async requests in a way that calls HttpServlet.service method twice. + + In this case we have to put the span from the request into current context before continuing. + */ + public boolean needsRescoping(Context currentContext, Context attachedContext) { + return !sameTrace(Span.fromContext(currentContext), Span.fromContext(attachedContext)); + } + + private static boolean sameTrace(Span oneSpan, Span otherSpan) { + return oneSpan.getSpanContext().getTraceId().equals(otherSpan.getSpanContext().getTraceId()); + } +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletAdditionalAttributesExtractor.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletAdditionalAttributesExtractor.java new file mode 100644 index 000000000000..8077bc9749a8 --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletAdditionalAttributesExtractor.java @@ -0,0 +1,50 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet; + +import static io.opentelemetry.api.common.AttributeKey.longKey; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import io.opentelemetry.instrumentation.servlet.ServletHttpServerTracer; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.security.Principal; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class ServletAdditionalAttributesExtractor + extends AttributesExtractor, ServletResponseContext> { + private static final AttributeKey SERVLET_TIMEOUT = longKey("servlet.timeout"); + + private final ServletAccessor accessor; + + public ServletAdditionalAttributesExtractor(ServletAccessor accessor) { + this.accessor = accessor; + } + + @Override + protected void onStart( + AttributesBuilder attributes, ServletRequestContext requestContext) {} + + @Override + protected void onEnd( + AttributesBuilder attributes, + ServletRequestContext requestContext, + @Nullable ServletResponseContext responseContext, + @Nullable Throwable error) { + Principal principal = accessor.getRequestUserPrincipal(requestContext.request()); + if (principal != null) { + set(attributes, SemanticAttributes.ENDUSER_ID, principal.getName()); + } + if (!ServletHttpServerTracer.CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) { + return; + } + if (responseContext != null && responseContext.hasTimeout()) { + set(attributes, SERVLET_TIMEOUT, responseContext.getTimeout()); + } + } +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletErrorCauseExtractor.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletErrorCauseExtractor.java new file mode 100644 index 000000000000..9b7c5471db4f --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletErrorCauseExtractor.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet; + +import io.opentelemetry.instrumentation.api.instrumenter.ErrorCauseExtractor; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; + +public class ServletErrorCauseExtractor implements ErrorCauseExtractor { + private final ServletAccessor accessor; + + public ServletErrorCauseExtractor(ServletAccessor accessor) { + this.accessor = accessor; + } + + @Override + public Throwable extractCause(Throwable error) { + if (accessor.isServletException(error) && error.getCause() != null) { + error = error.getCause(); + } + return ErrorCauseExtractor.jdk().extractCause(error); + } +} 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 new file mode 100644 index 000000000000..75043e628798 --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHelper.java @@ -0,0 +1,128 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet; + +import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.FILTER; +import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.SERVLET; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import io.opentelemetry.instrumentation.servlet.ServletHttpServerTracer; + +public class ServletHelper extends BaseServletHelper { + + public ServletHelper( + Instrumenter, ServletResponseContext> instrumenter, + ServletAccessor accessor) { + super(instrumenter, accessor); + } + + public Context start( + Context parentContext, ServletRequestContext requestContext, boolean servlet) { + ServerSpanNaming.Source namingSource = servlet ? SERVLET : FILTER; + return start(parentContext, requestContext, namingSource); + } + + public void end( + ServletRequestContext requestContext, + REQUEST request, + RESPONSE response, + Throwable throwable, + boolean topLevel, + Context context, + Scope scope) { + + if (scope != null) { + scope.close(); + } + + 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) { + recordException(currentContext, throwable); + } + } + + if (scope == null || context == null) { + return; + } + + ServletResponseContext responseContext = + new ServletResponseContext<>(response, throwable); + if (throwable != null) { + instrumenter.end(context, requestContext, responseContext, throwable); + return; + } + + if (mustEndOnHandlerMethodExit(request)) { + instrumenter.end(context, requestContext, responseContext, null); + } + } + + /** + * Helper method to determine whether the appserver handler/servlet service/servlet filter method + * 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)) { + // This request is handled asynchronously and startAsync instrumentation has already attached + // the listener. + return false; + } + + // This means that startAsync was not called (assuming startAsync instrumentation works + // correctly on this servlet engine), therefore the request was handled synchronously, and + // handler method end must also end the span. + return true; + } + + /** + * 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. + */ + public void setAsyncListenerResponse(REQUEST request, RESPONSE response) { + accessor.setRequestAttribute( + request, ServletHttpServerTracer.ASYNC_LISTENER_RESPONSE_ATTRIBUTE, response); + } + + public RESPONSE getAsyncListenerResponse(REQUEST request) { + return (RESPONSE) + accessor.getRequestAttribute( + request, ServletHttpServerTracer.ASYNC_LISTENER_RESPONSE_ATTRIBUTE); + } + + 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); + + if (context != null) { + Object response = getAsyncListenerResponse(request); + + accessor.addRequestAsyncListener( + request, + new AsyncRequestCompletionListener<>(this, instrumenter, requestContext, context), + response); + accessor.setRequestAttribute(request, ServletHttpServerTracer.ASYNC_LISTENER_ATTRIBUTE, true); + } + } + + public boolean isAsyncListenerAttached(REQUEST request) { + return accessor.getRequestAttribute(request, ServletHttpServerTracer.ASYNC_LISTENER_ATTRIBUTE) + != null; + } +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHttpAttributesExtractor.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHttpAttributesExtractor.java new file mode 100644 index 000000000000..035d4d3aecd0 --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHttpAttributesExtractor.java @@ -0,0 +1,159 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet; + +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor; +import io.opentelemetry.instrumentation.api.internal.UriBuilder; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class ServletHttpAttributesExtractor + extends HttpAttributesExtractor< + ServletRequestContext, ServletResponseContext> { + protected final ServletAccessor accessor; + + public ServletHttpAttributesExtractor(ServletAccessor accessor) { + this.accessor = accessor; + } + + @Override + protected @Nullable String method(ServletRequestContext requestContext) { + return accessor.getRequestMethod(requestContext.request()); + } + + @Override + protected @Nullable String url(ServletRequestContext requestContext) { + REQUEST request = requestContext.request(); + + return UriBuilder.uri( + accessor.getRequestScheme(request), + accessor.getRequestServerName(request), + accessor.getRequestServerPort(request), + accessor.getRequestUri(request), + accessor.getRequestQueryString(request)); + } + + @Override + protected @Nullable String target(ServletRequestContext requestContext) { + /* + String target = httpServletRequest.getRequestURI(); + String queryString = httpServletRequest.getQueryString(); + if (queryString != null) { + target += "?" + queryString; + } + return target; + */ + return null; + } + + @Override + protected @Nullable String host(ServletRequestContext requestContext) { + /* + REQUEST request = requestContext.request(); + return accessor.getRequestServerName(request) + ":" + accessor.getRequestServerPort(request); + */ + return null; + } + + @Override + protected @Nullable String scheme(ServletRequestContext requestContext) { + // return accessor.getRequestScheme(requestContext.request()); + return null; + } + + @Override + protected @Nullable String userAgent(ServletRequestContext requestContext) { + return accessor.getRequestHeader(requestContext.request(), "User-Agent"); + } + + @Override + protected @Nullable Long requestContentLength( + ServletRequestContext requestContext, + @Nullable ServletResponseContext responseContext) { + /* + int contentLength = accessor.getRequestContentLength(requestContext.request()); + if (contentLength > -1) { + return (long) contentLength; + } + */ + return null; + } + + @Override + protected @Nullable Long requestContentLengthUncompressed( + ServletRequestContext requestContext, + @Nullable ServletResponseContext responseContext) { + return null; + } + + @Override + protected @Nullable String flavor( + ServletRequestContext requestContext, + @Nullable ServletResponseContext responseContext) { + String flavor = accessor.getRequestProtocol(requestContext.request()); + if (flavor != null) { + // remove HTTP/ prefix to comply with semantic conventions + if (flavor.startsWith("HTTP/")) { + flavor = flavor.substring("HTTP/".length()); + } + } + return flavor; + } + + @Override + protected @Nullable Integer statusCode( + ServletRequestContext requestContext, + ServletResponseContext responseContext) { + RESPONSE response = responseContext.response(); + + if (!accessor.isResponseCommitted(response) && responseContext.error() != null) { + // if response is not committed and there is a throwable set status to 500 / + // INTERNAL_SERVER_ERROR, due to servlet spec + // https://javaee.github.io/servlet-spec/downloads/servlet-4.0/servlet-4_0_FINAL.pdf: + // "If a servlet generates an error that is not handled by the error page mechanism as + // described above, the container must ensure to send a response with status 500." + return 500; + } + return accessor.getResponseStatus(response); + } + + @Override + protected @Nullable Long responseContentLength( + ServletRequestContext requestContext, + ServletResponseContext responseContext) { + /* + String contentLength = servletAccessor.getResponseHeader(responseContext.response(), "Content-Length"); + if (contentLength != null) { + try { + return Long.valueOf(contentLength); + } catch (NumberFormatException ignored) { + // ignore + } + } + */ + return null; + } + + @Override + protected @Nullable Long responseContentLengthUncompressed( + ServletRequestContext requestContext, + ServletResponseContext responseContext) { + return null; + } + + @Override + protected @Nullable String route(ServletRequestContext requestContext) { + return null; + } + + @Override + protected @Nullable String serverName( + ServletRequestContext requestContext, + @Nullable ServletResponseContext responseContext) { + // return servletAccessor.getRequestServerName(requestContext.request()); + return null; + } +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletInstrumenterBuilder.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletInstrumenterBuilder.java new file mode 100644 index 000000000000..8f41ef6325bf --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletInstrumenterBuilder.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import io.opentelemetry.javaagent.instrumentation.api.instrumenter.PeerServiceAttributesExtractor; + +public final class ServletInstrumenterBuilder { + + private ServletInstrumenterBuilder() {} + + public static + Instrumenter, ServletResponseContext> + newInstrumenter( + String instrumentationName, + ServletAccessor accessor, + SpanNameExtractor> spanNameExtractor, + HttpAttributesExtractor< + ServletRequestContext, ServletResponseContext> + httpAttributesExtractor) { + SpanStatusExtractor, ServletResponseContext> + spanStatusExtractor = HttpSpanStatusExtractor.create(httpAttributesExtractor); + ServletNetAttributesExtractor netAttributesExtractor = + new ServletNetAttributesExtractor<>(accessor); + ServletErrorCauseExtractor errorCauseExtractor = + new ServletErrorCauseExtractor<>(accessor); + AttributesExtractor, ServletResponseContext> + additionalAttributesExtractor = new ServletAdditionalAttributesExtractor<>(accessor); + + return Instrumenter + ., ServletResponseContext>newBuilder( + GlobalOpenTelemetry.get(), instrumentationName, spanNameExtractor) + .setSpanStatusExtractor(spanStatusExtractor) + .setErrorCauseExtractor(errorCauseExtractor) + .addAttributesExtractor(httpAttributesExtractor) + .addAttributesExtractor(netAttributesExtractor) + .addAttributesExtractor(PeerServiceAttributesExtractor.create(netAttributesExtractor)) + .addAttributesExtractor(additionalAttributesExtractor) + .addRequestMetrics(HttpServerMetrics.get()) + .newServerInstrumenter(new ServletRequestGetter<>(accessor)); + } + + public static + Instrumenter, ServletResponseContext> + newInstrumenter(String instrumentationName, ServletAccessor accessor) { + HttpAttributesExtractor, ServletResponseContext> + httpAttributesExtractor = new ServletHttpAttributesExtractor<>(accessor); + SpanNameExtractor> spanNameExtractor = + new ServletSpanNameExtractor<>(accessor); + + return newInstrumenter( + instrumentationName, accessor, spanNameExtractor, httpAttributesExtractor); + } +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletNetAttributesExtractor.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletNetAttributesExtractor.java new file mode 100644 index 000000000000..f5f03cf15b52 --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletNetAttributesExtractor.java @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet; + +import io.opentelemetry.instrumentation.api.instrumenter.net.NetAttributesExtractor; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class ServletNetAttributesExtractor + extends NetAttributesExtractor< + ServletRequestContext, ServletResponseContext> { + private final ServletAccessor accessor; + + public ServletNetAttributesExtractor(ServletAccessor accessor) { + this.accessor = accessor; + } + + @Override + public @Nullable String transport(ServletRequestContext requestContext) { + // return SemanticAttributes.NetTransportValues.IP_TCP; + return null; + } + + @Override + public @Nullable String peerName( + ServletRequestContext requestContext, + @Nullable ServletResponseContext responseContext) { + // return accessor.getRequestRemoteHost(requestContext.request()); + return null; + } + + @Override + public @Nullable Integer peerPort( + ServletRequestContext requestContext, + @Nullable ServletResponseContext responseContext) { + return accessor.getRequestRemotePort(requestContext.request()); + } + + @Override + public @Nullable String peerIp( + ServletRequestContext requestContext, + @Nullable ServletResponseContext responseContext) { + return accessor.getRequestRemoteAddr(requestContext.request()); + } +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletRequestContext.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletRequestContext.java new file mode 100644 index 000000000000..131bef180e00 --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletRequestContext.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet; + +import io.opentelemetry.instrumentation.api.servlet.MappingResolver; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class ServletRequestContext { + private final T request; + private final MappingResolver mappingResolver; + + public ServletRequestContext(T request) { + this(request, null); + } + + public ServletRequestContext(T request, MappingResolver mappingResolver) { + this.request = request; + this.mappingResolver = mappingResolver; + } + + public T request() { + return request; + } + + @Nullable + public MappingResolver mappingResolver() { + return mappingResolver; + } +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletRequestGetter.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletRequestGetter.java new file mode 100644 index 000000000000..1616c54ba899 --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletRequestGetter.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet; + +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; + +public class ServletRequestGetter + implements TextMapGetter> { + protected final ServletAccessor accessor; + + public ServletRequestGetter(ServletAccessor accessor) { + this.accessor = accessor; + } + + @Override + public Iterable keys(ServletRequestContext carrier) { + return accessor.getRequestHeaderNames(carrier.request()); + } + + @Override + public String get(ServletRequestContext carrier, String key) { + return accessor.getRequestHeader(carrier.request(), key); + } +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletResponseContext.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletResponseContext.java new file mode 100644 index 000000000000..d186549b53d1 --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletResponseContext.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet; + +public class ServletResponseContext { + private final T response; + private final Throwable error; + // used for servlet 2.2 where request status can't be extracted from HttpServletResponse + private Integer status; + private Long timeout; + + public ServletResponseContext(T response, Throwable error) { + this.response = response; + this.error = error; + } + + public T response() { + return response; + } + + public Throwable error() { + return error; + } + + public void setStatus(int status) { + this.status = status; + } + + public int getStatus() { + return status; + } + + public boolean hasStatus() { + return status != null; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public long getTimeout() { + return timeout; + } + + public boolean hasTimeout() { + return timeout != null; + } +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletSpanNameExtractor.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletSpanNameExtractor.java new file mode 100644 index 000000000000..457d215b7097 --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletSpanNameExtractor.java @@ -0,0 +1,58 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet; + +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.instrumentation.api.servlet.MappingResolver; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class ServletSpanNameExtractor + implements SpanNameExtractor> { + private final ServletAccessor accessor; + + public ServletSpanNameExtractor(ServletAccessor accessor) { + this.accessor = accessor; + } + + private @Nullable String route(ServletRequestContext requestContext) { + MappingResolver mappingResolver = requestContext.mappingResolver(); + if (mappingResolver == null) { + return null; + } + + REQUEST request = requestContext.request(); + String servletPath = accessor.getRequestServletPath(request); + String pathInfo = accessor.getRequestPathInfo(request); + String contextPath = accessor.getRequestContextPath(request); + boolean hasContextPath = + contextPath != null && !contextPath.isEmpty() && !contextPath.equals("/"); + + String route = requestContext.mappingResolver().resolve(servletPath, pathInfo); + if (route == null) { + if (hasContextPath) { + return contextPath + "/*"; + } + return null; + } + // prepend context path + return contextPath + route; + } + + @Override + public String extract(ServletRequestContext requestContext) { + String route = route(requestContext); + if (route != null) { + return route; + } + REQUEST request = requestContext.request(); + String method = accessor.getRequestMethod(request); + if (method != null) { + return "HTTP " + method; + } + return "HTTP request"; + } +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletAndFilterAdviceHelper.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletAndFilterAdviceHelper.java index fb7e1f12fb9c..503ddfcb34ac 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletAndFilterAdviceHelper.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletAndFilterAdviceHelper.java @@ -10,6 +10,7 @@ import io.opentelemetry.instrumentation.servlet.ServletHttpServerTracer; import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; +@Deprecated public class ServletAndFilterAdviceHelper { public static void stopSpan( ServletHttpServerTracer tracer, diff --git a/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/ServletAccessor.java b/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/ServletAccessor.java index ff10d69402b8..87ce609b62f0 100644 --- a/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/ServletAccessor.java +++ b/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/ServletAccessor.java @@ -42,6 +42,8 @@ public interface ServletAccessor { String getRequestHeader(REQUEST request, String name); + Iterable getRequestHeaderNames(REQUEST request); + String getRequestServletPath(REQUEST request); String getRequestPathInfo(REQUEST request); @@ -50,11 +52,17 @@ public interface ServletAccessor { Integer getRequestRemotePort(REQUEST request); + String getRequestRemoteHost(REQUEST request); + + int getRequestContentLength(REQUEST request); + void addRequestAsyncListener( REQUEST request, ServletAsyncListener listener, Object response); int getResponseStatus(RESPONSE response); + String getResponseHeader(RESPONSE response, String name); + boolean isResponseCommitted(RESPONSE response); boolean isServletException(Throwable throwable); diff --git a/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/ServletHttpServerTracer.java b/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/ServletHttpServerTracer.java index 2b3f07862979..6f9988cab87b 100644 --- a/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/ServletHttpServerTracer.java +++ b/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/ServletHttpServerTracer.java @@ -27,6 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Deprecated public abstract class ServletHttpServerTracer extends HttpServerTracer { @@ -37,7 +38,7 @@ public abstract class ServletHttpServerTracer public static final String ASYNC_LISTENER_RESPONSE_ATTRIBUTE = ServletHttpServerTracer.class.getName() + ".AsyncListenerResponse"; - private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES = + public static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES = Config.get().getBoolean("otel.instrumentation.servlet.experimental-span-attributes", false); private final ServletAccessor accessor; diff --git a/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/TagSettingAsyncListener.java b/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/TagSettingAsyncListener.java index 4597609e423c..088778bbebf9 100644 --- a/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/TagSettingAsyncListener.java +++ b/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/TagSettingAsyncListener.java @@ -8,6 +8,7 @@ import io.opentelemetry.context.Context; import java.util.concurrent.atomic.AtomicBoolean; +@Deprecated public class TagSettingAsyncListener implements ServletAsyncListener { private final ServletHttpServerTracer tracer; private final AtomicBoolean responseHandled; diff --git a/instrumentation/servlet/servlet-javax-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/javax/JavaxServletAccessor.java b/instrumentation/servlet/servlet-javax-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/javax/JavaxServletAccessor.java index f1310f9a3522..c92253ce8835 100644 --- a/instrumentation/servlet/servlet-javax-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/javax/JavaxServletAccessor.java +++ b/instrumentation/servlet/servlet-javax-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/javax/JavaxServletAccessor.java @@ -7,6 +7,7 @@ import io.opentelemetry.instrumentation.servlet.ServletAccessor; import java.security.Principal; +import java.util.Collections; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -66,11 +67,21 @@ public String getRequestRemoteAddr(HttpServletRequest request) { return request.getRemoteAddr(); } + @Override + public String getRequestRemoteHost(HttpServletRequest httpServletRequest) { + return httpServletRequest.getRemoteHost(); + } + @Override public String getRequestHeader(HttpServletRequest request, String name) { return request.getHeader(name); } + @Override + public Iterable getRequestHeaderNames(HttpServletRequest httpServletRequest) { + return Collections.list(httpServletRequest.getHeaderNames()); + } + @Override public String getRequestServletPath(HttpServletRequest request) { return request.getServletPath(); @@ -86,6 +97,11 @@ public Principal getRequestUserPrincipal(HttpServletRequest request) { return request.getUserPrincipal(); } + @Override + public int getRequestContentLength(HttpServletRequest request) { + return request.getContentLength(); + } + @Override public boolean isServletException(Throwable throwable) { return throwable instanceof ServletException; diff --git a/instrumentation/servlet/servlet-javax-common/library/src/test/java/RequestOnlyTracer.java b/instrumentation/servlet/servlet-javax-common/library/src/test/java/RequestOnlyTracer.java index c493437e22b7..f8191b20a561 100644 --- a/instrumentation/servlet/servlet-javax-common/library/src/test/java/RequestOnlyTracer.java +++ b/instrumentation/servlet/servlet-javax-common/library/src/test/java/RequestOnlyTracer.java @@ -28,6 +28,11 @@ public int getResponseStatus(Void unused) { throw new UnsupportedOperationException(); } + @Override + public String getResponseHeader(Void unused, String name) { + throw new UnsupportedOperationException(); + } + @Override public boolean isResponseCommitted(Void unused) { throw new UnsupportedOperationException(); 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 50ca5b66d2f3..a83c5da92e10 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 @@ -5,8 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0; -import io.opentelemetry.instrumentation.servlet.jakarta.v5_0.JakartaServletHttpServerTracer; -import io.opentelemetry.javaagent.instrumentation.tomcat.common.TomcatServerHandlerAdviceHelper; +import static io.opentelemetry.javaagent.instrumentation.tomcat.v10_0.Tomcat10Singletons.helper; + import net.bytebuddy.asm.Advice; import org.apache.coyote.Request; import org.apache.coyote.Response; @@ -20,11 +20,7 @@ public static void attachResponse( @Advice.Return boolean success) { if (success) { - TomcatServerHandlerAdviceHelper.attachResponseToRequest( - Tomcat10ServletEntityProvider.INSTANCE, - JakartaServletHttpServerTracer.tracer(), - request, - response); + helper().attachResponseToRequest(request, response); } } } diff --git a/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10ServerHandlerAdvice.java b/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10ServerHandlerAdvice.java index 23435036bd58..7303c263aa1b 100644 --- a/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10ServerHandlerAdvice.java +++ b/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10ServerHandlerAdvice.java @@ -5,12 +5,11 @@ package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0; -import static io.opentelemetry.javaagent.instrumentation.tomcat.v10_0.Tomcat10Tracer.tracer; +import static io.opentelemetry.javaagent.instrumentation.tomcat.v10_0.Tomcat10Singletons.helper; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.servlet.jakarta.v5_0.JakartaServletHttpServerTracer; -import io.opentelemetry.javaagent.instrumentation.tomcat.common.TomcatServerHandlerAdviceHelper; +import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; import net.bytebuddy.asm.Advice; import org.apache.coyote.Request; import org.apache.coyote.Response; @@ -24,11 +23,13 @@ public static void onEnter( @Advice.Argument(1) Response response, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - if (!tracer().shouldStartSpan(request)) { + + Context parentContext = Java8BytecodeBridge.currentContext(); + if (!helper().shouldStart(parentContext, request)) { return; } - context = tracer().startServerSpan(request); + context = helper().start(parentContext, request); scope = context.makeCurrent(); } @@ -41,14 +42,6 @@ public static void stopSpan( @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - TomcatServerHandlerAdviceHelper.stopSpan( - tracer(), - Tomcat10ServletEntityProvider.INSTANCE, - JakartaServletHttpServerTracer.tracer(), - request, - response, - throwable, - context, - scope); + helper().end(request, response, throwable, context, scope); } } diff --git a/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10Singletons.java b/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10Singletons.java new file mode 100644 index 000000000000..0e4b62fcbc0b --- /dev/null +++ b/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10Singletons.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0; + +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Accessor; +import io.opentelemetry.javaagent.instrumentation.servlet.v5_0.Servlet5Singletons; +import io.opentelemetry.javaagent.instrumentation.tomcat.common.TomcatHelper; +import io.opentelemetry.javaagent.instrumentation.tomcat.common.TomcatInstrumenterBuilder; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.coyote.Request; +import org.apache.coyote.Response; + +public final class Tomcat10Singletons { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.tomcat-10.0"; + private static final Instrumenter INSTRUMENTER = + TomcatInstrumenterBuilder.newInstrumenter( + INSTRUMENTATION_NAME, Servlet5Accessor.INSTANCE, Tomcat10ServletEntityProvider.INSTANCE); + private static final TomcatHelper HELPER = + new TomcatHelper<>( + INSTRUMENTER, Tomcat10ServletEntityProvider.INSTANCE, Servlet5Singletons.helper()); + + public static TomcatHelper helper() { + return HELPER; + } + + private Tomcat10Singletons() {} +} diff --git a/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10Tracer.java b/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10Tracer.java deleted file mode 100644 index 2bf2e151d8a9..000000000000 --- a/instrumentation/tomcat/tomcat-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/Tomcat10Tracer.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0; - -import io.opentelemetry.javaagent.instrumentation.tomcat.common.TomcatTracer; - -public class Tomcat10Tracer extends TomcatTracer { - private static final Tomcat10Tracer TRACER = new Tomcat10Tracer(); - - public static TomcatTracer tracer() { - return TRACER; - } - - @Override - protected String getInstrumentationName() { - return "io.opentelemetry.tomcat-10.0"; - } -} 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 229fd397d1da..22dfac2dd3cb 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 @@ -5,8 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.tomcat.v7_0; -import io.opentelemetry.instrumentation.servlet.v3_0.Servlet3HttpServerTracer; -import io.opentelemetry.javaagent.instrumentation.tomcat.common.TomcatServerHandlerAdviceHelper; +import static io.opentelemetry.javaagent.instrumentation.tomcat.v7_0.Tomcat7Singletons.helper; + import net.bytebuddy.asm.Advice; import org.apache.coyote.Request; import org.apache.coyote.Response; @@ -20,11 +20,7 @@ public static void attachResponse( @Advice.Return boolean success) { if (success) { - TomcatServerHandlerAdviceHelper.attachResponseToRequest( - Tomcat7ServletEntityProvider.INSTANCE, - Servlet3HttpServerTracer.tracer(), - request, - response); + helper().attachResponseToRequest(request, response); } } } diff --git a/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7ServerHandlerAdvice.java b/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7ServerHandlerAdvice.java index de6c47efe6a1..437e6ff37636 100644 --- a/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7ServerHandlerAdvice.java +++ b/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7ServerHandlerAdvice.java @@ -5,12 +5,11 @@ package io.opentelemetry.javaagent.instrumentation.tomcat.v7_0; -import static io.opentelemetry.javaagent.instrumentation.tomcat.v7_0.Tomcat7Tracer.tracer; +import static io.opentelemetry.javaagent.instrumentation.tomcat.v7_0.Tomcat7Singletons.helper; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.servlet.v3_0.Servlet3HttpServerTracer; -import io.opentelemetry.javaagent.instrumentation.tomcat.common.TomcatServerHandlerAdviceHelper; +import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; import net.bytebuddy.asm.Advice; import org.apache.coyote.Request; import org.apache.coyote.Response; @@ -24,11 +23,13 @@ public static void onEnter( @Advice.Argument(1) Response response, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - if (!tracer().shouldStartSpan(request)) { + + Context parentContext = Java8BytecodeBridge.currentContext(); + if (!helper().shouldStart(parentContext, request)) { return; } - context = tracer().startServerSpan(request); + context = helper().start(parentContext, request); scope = context.makeCurrent(); } @@ -41,14 +42,6 @@ public static void stopSpan( @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - TomcatServerHandlerAdviceHelper.stopSpan( - tracer(), - Tomcat7ServletEntityProvider.INSTANCE, - Servlet3HttpServerTracer.tracer(), - request, - response, - throwable, - context, - scope); + helper().end(request, response, throwable, context, scope); } } diff --git a/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7Singletons.java b/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7Singletons.java new file mode 100644 index 000000000000..17b227633d88 --- /dev/null +++ b/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7Singletons.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.tomcat.v7_0; + +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.servlet.v3_0.Servlet3Accessor; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons; +import io.opentelemetry.javaagent.instrumentation.tomcat.common.TomcatHelper; +import io.opentelemetry.javaagent.instrumentation.tomcat.common.TomcatInstrumenterBuilder; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.coyote.Request; +import org.apache.coyote.Response; + +public final class Tomcat7Singletons { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.tomcat-7.0"; + private static final Instrumenter INSTRUMENTER = + TomcatInstrumenterBuilder.newInstrumenter( + INSTRUMENTATION_NAME, Servlet3Accessor.INSTANCE, Tomcat7ServletEntityProvider.INSTANCE); + private static final TomcatHelper HELPER = + new TomcatHelper<>( + INSTRUMENTER, Tomcat7ServletEntityProvider.INSTANCE, Servlet3Singletons.helper()); + + public static TomcatHelper helper() { + return HELPER; + } + + private Tomcat7Singletons() {} +} diff --git a/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7Tracer.java b/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7Tracer.java deleted file mode 100644 index b011db9195ec..000000000000 --- a/instrumentation/tomcat/tomcat-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/v7_0/Tomcat7Tracer.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.tomcat.v7_0; - -import io.opentelemetry.javaagent.instrumentation.tomcat.common.TomcatTracer; - -public class Tomcat7Tracer extends TomcatTracer { - private static final Tomcat7Tracer TRACER = new Tomcat7Tracer(); - - public static TomcatTracer tracer() { - return TRACER; - } - - @Override - protected String getInstrumentationName() { - return "io.opentelemetry.tomcat-7.0"; - } -} diff --git a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatAdditionalAttributesExtractor.java b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatAdditionalAttributesExtractor.java new file mode 100644 index 000000000000..1b4b80230dcb --- /dev/null +++ b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatAdditionalAttributesExtractor.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.tomcat.common; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.security.Principal; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class TomcatAdditionalAttributesExtractor + extends AttributesExtractor { + private final ServletAccessor accessor; + private final TomcatServletEntityProvider servletEntityProvider; + + public TomcatAdditionalAttributesExtractor( + ServletAccessor accessor, + TomcatServletEntityProvider servletEntityProvider) { + this.accessor = accessor; + this.servletEntityProvider = servletEntityProvider; + } + + @Override + protected void onStart(AttributesBuilder attributes, Request request) {} + + @Override + protected void onEnd( + AttributesBuilder attributes, + Request request, + @Nullable Response response, + @Nullable Throwable error) { + REQUEST servletRequest = servletEntityProvider.getServletRequest(request); + Principal principal = accessor.getRequestUserPrincipal(servletRequest); + if (principal != null) { + set(attributes, SemanticAttributes.ENDUSER_ID, principal.getName()); + } + } +} 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 new file mode 100644 index 000000000000..4b513eb1dd4f --- /dev/null +++ b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatHelper.java @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.tomcat.common; + +import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTAINER; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.servlet.AppServerBridge; +import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper; +import org.apache.coyote.Request; +import org.apache.coyote.Response; + +public class TomcatHelper { + protected final Instrumenter instrumenter; + protected final TomcatServletEntityProvider servletEntityProvider; + private final ServletHelper servletHelper; + + public TomcatHelper( + Instrumenter instrumenter, + TomcatServletEntityProvider servletEntityProvider, + ServletHelper servletHelper) { + this.instrumenter = instrumenter; + this.servletEntityProvider = servletEntityProvider; + this.servletHelper = servletHelper; + } + + public boolean shouldStart(Context parentContext, Request request) { + return instrumenter.shouldStart(parentContext, request); + } + + public Context start(Context parentContext, Request request) { + Context context = instrumenter.start(parentContext, request); + + context = ServerSpanNaming.init(context, CONTAINER); + return AppServerBridge.init(context); + } + + public void end( + Request request, Response response, Throwable throwable, Context context, Scope scope) { + if (scope == null) { + return; + } + scope.close(); + + if (throwable == null) { + throwable = AppServerBridge.getException(context); + } + + if (throwable != null || mustEndOnHandlerMethodExit(request)) { + 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); + RESPONSE servletResponse = servletEntityProvider.getServletResponse(response); + + if (servletRequest != null && servletResponse != null) { + servletHelper.setAsyncListenerResponse(servletRequest, servletResponse); + } + } +} diff --git a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatHttpAttributesExtractor.java b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatHttpAttributesExtractor.java new file mode 100644 index 000000000000..aa6b8c332c16 --- /dev/null +++ b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatHttpAttributesExtractor.java @@ -0,0 +1,115 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.tomcat.common; + +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor; +import io.opentelemetry.instrumentation.api.internal.UriBuilder; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.apache.tomcat.util.buf.MessageBytes; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class TomcatHttpAttributesExtractor extends HttpAttributesExtractor { + + @Override + protected String method(Request request) { + return request.method().toString(); + } + + @Override + protected String url(Request request) { + MessageBytes schemeMessageBytes = request.scheme(); + String scheme = schemeMessageBytes.isNull() ? "http" : schemeMessageBytes.toString(); + String host = request.serverName().toString(); + int serverPort = request.getServerPort(); + String path = request.requestURI().toString(); + String query = request.queryString().toString(); + + return UriBuilder.uri(scheme, host, serverPort, path, query); + } + + @Override + protected @Nullable String target(Request request) { + return null; + } + + @Override + protected @Nullable String host(Request request) { + // return request.serverName().toString() + ":" + request.getServerPort(); + return null; + } + + @Override + protected @Nullable String scheme(Request request) { + /* + MessageBytes schemeMessageBytes = request.scheme(); + return schemeMessageBytes.isNull() ? "http" : schemeMessageBytes.toString(); + */ + return null; + } + + @Override + protected @Nullable String userAgent(Request request) { + return request.getHeader("User-Agent"); + } + + @Override + protected @Nullable Long requestContentLength(Request request, @Nullable Response response) { + /* + long contentLength = request.getContentLengthLong(); + return contentLength != -1 ? contentLength : null; + */ + return null; + } + + @Override + protected @Nullable Long requestContentLengthUncompressed( + Request request, @Nullable Response response) { + return null; + } + + @Override + protected @Nullable String flavor(Request request, @Nullable Response response) { + String flavor = request.protocol().toString(); + if (flavor != null) { + // remove HTTP/ prefix to comply with semantic conventions + if (flavor.startsWith("HTTP/")) { + flavor = flavor.substring("HTTP/".length()); + } + } + return flavor; + } + + @Override + protected @Nullable Integer statusCode(Request request, Response response) { + return response.getStatus(); + } + + @Override + protected @Nullable Long responseContentLength(Request request, Response response) { + /* + long contentLength = response.getContentLengthLong(); + return contentLength != -1 ? contentLength : null; + */ + return null; + } + + @Override + protected @Nullable Long responseContentLengthUncompressed(Request request, Response response) { + return null; + } + + @Override + protected @Nullable String route(Request request) { + return null; + } + + @Override + protected @Nullable String serverName(Request request, @Nullable Response response) { + // return request.serverName().toString(); + return null; + } +} diff --git a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatInstrumenterBuilder.java b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatInstrumenterBuilder.java new file mode 100644 index 000000000000..dd0b55994369 --- /dev/null +++ b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatInstrumenterBuilder.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.tomcat.common; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.net.NetAttributesExtractor; +import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import io.opentelemetry.javaagent.instrumentation.api.instrumenter.PeerServiceAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletErrorCauseExtractor; +import org.apache.coyote.Request; +import org.apache.coyote.Response; + +public final class TomcatInstrumenterBuilder { + + private TomcatInstrumenterBuilder() {} + + public static Instrumenter newInstrumenter( + String instrumentationName, + ServletAccessor accessor, + TomcatServletEntityProvider servletEntityProvider) { + HttpAttributesExtractor httpAttributesExtractor = + new TomcatHttpAttributesExtractor(); + SpanNameExtractor spanNameExtractor = + HttpSpanNameExtractor.create(httpAttributesExtractor); + SpanStatusExtractor spanStatusExtractor = + HttpSpanStatusExtractor.create(httpAttributesExtractor); + NetAttributesExtractor netAttributesExtractor = + new TomcatNetAttributesExtractor(); + AttributesExtractor additionalAttributeExtractor = + new TomcatAdditionalAttributesExtractor<>(accessor, servletEntityProvider); + + return Instrumenter.newBuilder( + GlobalOpenTelemetry.get(), instrumentationName, spanNameExtractor) + .setSpanStatusExtractor(spanStatusExtractor) + .setErrorCauseExtractor(new ServletErrorCauseExtractor<>(accessor)) + .addAttributesExtractor(httpAttributesExtractor) + .addAttributesExtractor(netAttributesExtractor) + .addAttributesExtractor(PeerServiceAttributesExtractor.create(netAttributesExtractor)) + .addAttributesExtractor(additionalAttributeExtractor) + .addRequestMetrics(HttpServerMetrics.get()) + .newServerInstrumenter(TomcatRequestGetter.GETTER); + } +} diff --git a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatNetAttributesExtractor.java b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatNetAttributesExtractor.java new file mode 100644 index 000000000000..4552f2d58284 --- /dev/null +++ b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatNetAttributesExtractor.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.tomcat.common; + +import io.opentelemetry.instrumentation.api.instrumenter.net.NetAttributesExtractor; +import org.apache.coyote.ActionCode; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class TomcatNetAttributesExtractor extends NetAttributesExtractor { + + @Override + public @Nullable String transport(Request request) { + // return SemanticAttributes.NetTransportValues.IP_TCP; + return null; + } + + @Override + public @Nullable String peerName(Request request, @Nullable Response response) { + /* + request.action(ActionCode.REQ_HOST_ATTRIBUTE, request); + return request.remoteHost().toString(); + */ + return null; + } + + @Override + public @Nullable Integer peerPort(Request request, @Nullable Response response) { + request.action(ActionCode.REQ_REMOTEPORT_ATTRIBUTE, request); + return request.getRemotePort(); + } + + @Override + public @Nullable String peerIp(Request request, @Nullable Response response) { + request.action(ActionCode.REQ_HOST_ADDR_ATTRIBUTE, request); + return request.remoteAddr().toString(); + } +} diff --git a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatRequestGetter.java b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatRequestGetter.java new file mode 100644 index 000000000000..1162391148c8 --- /dev/null +++ b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatRequestGetter.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.tomcat.common; + +import io.opentelemetry.context.propagation.TextMapGetter; +import java.util.Collections; +import org.apache.coyote.Request; + +public class TomcatRequestGetter implements TextMapGetter { + + public static final TomcatRequestGetter GETTER = new TomcatRequestGetter(); + + @Override + public Iterable keys(Request carrier) { + return Collections.list(carrier.getMimeHeaders().names()); + } + + @Override + public String get(Request carrier, String key) { + return carrier.getHeader(key); + } +} diff --git a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatServerHandlerAdviceHelper.java b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatServerHandlerAdviceHelper.java deleted file mode 100644 index 5583e137fb4e..000000000000 --- a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatServerHandlerAdviceHelper.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.tomcat.common; - -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.servlet.ServletHttpServerTracer; -import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterAdviceHelper; -import org.apache.coyote.Request; -import org.apache.coyote.Response; - -public class TomcatServerHandlerAdviceHelper { - /** - * Shared stop method used by advices for different Tomcat versions. - * - * @param tracer Tracer for non-async path (uses Tomcat Coyote request/response) - * @param servletTracer Tracer for async path (uses servlet request/response) - * @param request Tomcat Coyote request object - * @param response Tomcat Coyote request object - * @param HttpServletRequest class - * @param HttpServletResponse class - */ - public static void stopSpan( - TomcatTracer tracer, - TomcatServletEntityProvider servletEntityProvider, - ServletHttpServerTracer servletTracer, - Request request, - Response response, - Throwable throwable, - Context context, - Scope scope) { - if (scope != null) { - scope.close(); - } - - if (context == null) { - return; - } - - if (throwable != null) { - if (response.isCommitted()) { - tracer.endExceptionally(context, throwable, response); - } else { - // If the response is not committed, then response headers, including response code, are - // not yet written to the output stream. - tracer.endExceptionally(context, throwable); - } - return; - } - - if (response.isCommitted()) { - tracer.end(context, response); - return; - } - - REQUEST servletRequest = servletEntityProvider.getServletRequest(request); - - if (servletRequest != null - && ServletAndFilterAdviceHelper.mustEndOnHandlerMethodExit(servletTracer, servletRequest)) { - tracer.end(context, response); - } - } - - /** - * Must be attached in Tomcat instrumentations since Tomcat valves can use startAsync outside of - * servlet scope. - */ - public static void attachResponseToRequest( - TomcatServletEntityProvider servletEntityProvider, - ServletHttpServerTracer servletTracer, - Request request, - Response response) { - - REQUEST servletRequest = servletEntityProvider.getServletRequest(request); - RESPONSE servletResponse = servletEntityProvider.getServletResponse(response); - - if (servletRequest != null && servletResponse != null) { - servletTracer.setAsyncListenerResponse(servletRequest, servletResponse); - } - } -} diff --git a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatTracer.java b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatTracer.java deleted file mode 100644 index fd81f61395d8..000000000000 --- a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatTracer.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.tomcat.common; - -import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTAINER; - -import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.instrumentation.api.internal.UriBuilder; -import io.opentelemetry.instrumentation.api.servlet.AppServerBridge; -import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming; -import io.opentelemetry.instrumentation.api.tracer.HttpServerTracer; -import java.util.Collections; -import org.apache.coyote.ActionCode; -import org.apache.coyote.Request; -import org.apache.coyote.Response; -import org.apache.tomcat.util.buf.MessageBytes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Abstract tracer for all Tomcat versions. This class must not access any methods of fields of - * Tomcat classes which have the javax.servlet/jakarta.servlet packages in their - * signature - these must only be accessed by the version-specific subclasses. - */ -public abstract class TomcatTracer extends HttpServerTracer - implements TextMapGetter { - - private static final Logger logger = LoggerFactory.getLogger(TomcatTracer.class); - - public boolean shouldStartSpan(Request request) { - Context attachedContext = getServerContext(request); - if (attachedContext == null) { - return true; - } - logger.debug( - "Unexpected context found before server handler even started: {}", attachedContext); - return false; - } - - public Context startServerSpan(Request request) { - return startSpan(request, request, request, "HTTP " + request.method().toString()); - } - - @Override - protected Context customizeContext(Context context, Request request) { - context = ServerSpanNaming.init(context, CONTAINER); - return AppServerBridge.init(context); - } - - @Override - public Context getServerContext(Request storage) { - Object attribute = storage.getAttribute(CONTEXT_ATTRIBUTE); - return attribute instanceof Context ? (Context) attribute : null; - } - - @Override - protected Integer peerPort(Request connection) { - connection.action(ActionCode.REQ_REMOTEPORT_ATTRIBUTE, connection); - return connection.getRemotePort(); - } - - @Override - protected String peerHostIp(Request connection) { - connection.action(ActionCode.REQ_HOST_ADDR_ATTRIBUTE, connection); - return connection.remoteAddr().toString(); - } - - @Override - protected String flavor(Request connection, Request request) { - return request.protocol().toString(); - } - - @Override - protected TextMapGetter getGetter() { - return this; - } - - @Override - protected String url(Request request) { - MessageBytes schemeMessageBytes = request.scheme(); - String scheme = schemeMessageBytes.isNull() ? "http" : schemeMessageBytes.toString(); - String host = request.serverName().toString(); - int serverPort = request.getServerPort(); - String path = request.requestURI().toString(); - String query = request.queryString().toString(); - - return UriBuilder.uri(scheme, host, serverPort, path, query); - } - - @Override - protected String method(Request request) { - return request.method().toString(); - } - - @Override - protected String requestHeader(Request request, String name) { - return request.getHeader(name); - } - - @Override - protected int responseStatus(Response response) { - return response.getStatus(); - } - - @Override - protected void attachServerContext(Context context, Request storage) { - storage.setAttribute(CONTEXT_ATTRIBUTE, context); - } - - @Override - public Iterable keys(Request request) { - return Collections.list(request.getMimeHeaders().names()); - } - - @Override - public String get(Request request, String key) { - return request.getHeader(key); - } -} diff --git a/instrumentation/undertow-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowHttpServerTracer.java b/instrumentation/undertow-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowHttpServerTracer.java index 311e74c4e212..fbb7621e68eb 100644 --- a/instrumentation/undertow-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowHttpServerTracer.java +++ b/instrumentation/undertow-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowHttpServerTracer.java @@ -74,6 +74,10 @@ public void exchangeCompleted(Context context, HttpServerExchange exchange) { } private static void endSpan(Context context, Throwable throwable, HttpServerExchange exchange) { + if (throwable == null) { + throwable = AppServerBridge.getException(context); + } + if (throwable != null) { tracer().endExceptionally(context, throwable, exchange); } else { diff --git a/settings.gradle.kts b/settings.gradle.kts index 8f9bcba4a797..75053c1f4e4d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -298,11 +298,9 @@ include(":instrumentation:servlet:servlet-common:library") include(":instrumentation:servlet:servlet-common:javaagent") include(":instrumentation:servlet:servlet-javax-common:library") include(":instrumentation:servlet:servlet-javax-common:javaagent") -include(":instrumentation:servlet:servlet-2.2:library") include(":instrumentation:servlet:servlet-2.2:javaagent") include(":instrumentation:servlet:servlet-3.0:library") include(":instrumentation:servlet:servlet-3.0:javaagent") -include(":instrumentation:servlet:servlet-5.0:library") include(":instrumentation:servlet:servlet-5.0:javaagent") include(":instrumentation:spark-2.3:javaagent") include(":instrumentation:spring:spring-batch-3.0:javaagent")