From cd8080634940777cc2e334c5afef758f4bff0f4f Mon Sep 17 00:00:00 2001 From: Tyler Benson <734411+tylerbenson@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:22:42 -0400 Subject: [PATCH 01/10] Library instrumentation for Java Servlet 3.0 Filters. Includes handling for attaching all the async stuff, but can't support everything the java agent does. I could use help figuring out the right way to shadow all the necessary dependencies. I'm a bit rusty with our shadow usage. Depending on how aggressive we want to be, the dependency could be reversed so that `:servlet-3.0:javaagent` depends on this new module, but that's more aggressive. Perhaps shadow could prune unused classes (with `minimize()`) instead. Once we get the modules figured out, I can work on adding some unit tests. If we like this approach, it could probably be copied and modified pretty easily to support servlet 5, but I'm not familiar with that instrumentation. --- .../servlet-3.0/library/build.gradle.kts | 11 +++ .../v3_0/OpenTelemetryServletFilter.java | 69 +++++++++++++++ .../servlet/v3_0/OtelAsyncContext.java | 85 +++++++++++++++++++ .../servlet/v3_0/OtelHttpServletRequest.java | 43 ++++++++++ settings.gradle.kts | 1 + 5 files changed, 209 insertions(+) create mode 100644 instrumentation/servlet/servlet-3.0/library/build.gradle.kts create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelAsyncContext.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletRequest.java diff --git a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts new file mode 100644 index 000000000000..51506d02d36a --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("otel.library-instrumentation") +} + +dependencies { + library("javax.servlet:javax.servlet-api:3.0.1") + + // FIXME: These dependencies need to be shadowed into the library. + library(project(":instrumentation:servlet:servlet-3.0:javaagent")) + library(project(":instrumentation:servlet:servlet-common:javaagent")) +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java new file mode 100644 index 000000000000..4f8adc592604 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java @@ -0,0 +1,69 @@ +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * OpenTelemetry Library instrumentation for Java Servlet based applications that can't use a Java + * Agent. Due to inherit limitations in the servlet filter API, instrumenting at the filter level + * will miss anything that happens earlier in the filter stack or problems handled directly by the + * app server. For this reason, Java Agent instrumentation is preferred when possible. + */ +public class OpenTelemetryServletFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException {} + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + // Only HttpServlets are supported. + if (!(request instanceof HttpServletRequest && response instanceof HttpServletResponse)) { + chain.doFilter(request, response); + return; + } + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + ServletRequestContext requestContext = + new ServletRequestContext<>(httpRequest, this); + + // Bail if we shouldn't start a new span. + if (!helper().shouldStart(Context.current(), requestContext)) { + chain.doFilter(request, response); + return; + } + + Context spanContext = helper().start(Context.current(), requestContext); + helper().setAsyncListenerResponse(spanContext, (HttpServletResponse) response); + + // Not using try-with-resources to match the api usage of Servlet3Advice. + // (helper().end is responsible for closing the scope.) + Scope scope = spanContext.makeCurrent(); + Throwable throwable = null; + try { + chain.doFilter( + new OtelHttpServletRequest((HttpServletRequest) request), response); + } catch (Throwable e) { + throwable = e; + throw e; + } finally { + helper().end(requestContext, httpRequest, httpResponse, throwable, true, spanContext, scope); + } + } + + @Override + public void destroy() {} +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelAsyncContext.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelAsyncContext.java new file mode 100644 index 000000000000..d9cc4b891eed --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelAsyncContext.java @@ -0,0 +1,85 @@ +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncListener; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +/// Delegates all methods except [#start(Runnable) which wraps the [Runnable]. +public class OtelAsyncContext implements AsyncContext { + private final AsyncContext delegate; + + public OtelAsyncContext(AsyncContext delegate) { + this.delegate = delegate; + } + + @Override + public ServletRequest getRequest() { + return delegate.getRequest(); + } + + @Override + public ServletResponse getResponse() { + return delegate.getResponse(); + } + + @Override + public boolean hasOriginalRequestAndResponse() { + return delegate.hasOriginalRequestAndResponse(); + } + + @Override + public void dispatch() { + delegate.dispatch(); + } + + @Override + public void dispatch(String path) { + delegate.dispatch(path); + } + + @Override + public void dispatch(ServletContext context, String path) { + delegate.dispatch(context, path); + } + + @Override + public void complete() { + delegate.complete(); + } + + @Override + public void start(Runnable run) { + delegate.start(helper().wrapAsyncRunnable(run)); + } + + @Override + public void addListener(AsyncListener listener) { + delegate.addListener(listener); + } + + @Override + public void addListener( + AsyncListener listener, ServletRequest servletRequest, ServletResponse servletResponse) { + delegate.addListener(listener, servletRequest, servletResponse); + } + + @Override + public T createListener(Class clazz) throws ServletException { + return delegate.createListener(clazz); + } + + @Override + public void setTimeout(long timeout) { + delegate.setTimeout(timeout); + } + + @Override + public long getTimeout() { + return delegate.getTimeout(); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletRequest.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletRequest.java new file mode 100644 index 000000000000..6b8cd4291631 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletRequest.java @@ -0,0 +1,43 @@ +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; + +import io.opentelemetry.context.Context; +import javax.servlet.AsyncContext; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +/// Wrapper around [HttpServletRequest] that attaches an async listener if [#startAsync()] is +/// invoked and a wrapper around [#getAsyncContext()] to capture exceptions from async [Runnable]s. +public class OtelHttpServletRequest extends HttpServletRequestWrapper { + + public OtelHttpServletRequest(HttpServletRequest request) { + super(request); + } + + @Override + public AsyncContext getAsyncContext() { + return new OtelAsyncContext(super.getAsyncContext()); + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + try { + return super.startAsync(); + } finally { + helper().attachAsyncListener(this, Context.current()); + } + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) + throws IllegalStateException { + try { + return super.startAsync(servletRequest, servletResponse); + } finally { + helper().attachAsyncListener(this, Context.current()); + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index f1757a2155a3..d53bad53dd45 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -590,6 +590,7 @@ include(":instrumentation:scala-fork-join-2.8:javaagent") include(":instrumentation:servlet:servlet-2.2:javaagent") include(":instrumentation:servlet:servlet-3.0:javaagent") include(":instrumentation:servlet:servlet-3.0:javaagent-unit-tests") +include(":instrumentation:servlet:servlet-3.0:library") include(":instrumentation:servlet:servlet-3.0:testing") include(":instrumentation:servlet:servlet-5.0:javaagent") include(":instrumentation:servlet:servlet-5.0:javaagent-unit-tests") From 58773b514a4fd5fc41e2f9f256797657ce70c461 Mon Sep 17 00:00:00 2001 From: Tyler Benson <734411+tylerbenson@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:44:32 -0500 Subject: [PATCH 02/10] passing unit tests Copied over from :servlet3.0:javaagent. Had to disable/remove some test cases/scenarios that didn't make sense or just weren't working. Still not using proper gradle project structure though. --- .../servlet/v3_0/Servlet3Advice.java | 75 +--- .../v3_0/Servlet3FilterInitAdvice.java | 4 +- .../servlet/v3_0/Servlet3InitAdvice.java | 4 +- .../v3_0/Servlet3RequestAdviceScope.java | 75 ++++ .../v3_0/Servlet3ResponseAdviceScope.java | 48 +++ .../v3_0/Servlet3ResponseSendAdvice.java | 50 +-- .../servlet/v3_0/Servlet3Singletons.java | 12 +- .../servlet-3.0/library/build.gradle.kts | 18 + .../v3_0/OpenTelemetryServletFilter.java | 33 +- .../servlet/v3_0/OtelHttpServletRequest.java | 9 +- .../servlet/v3_0/OtelHttpServletResponse.java | 63 ++++ .../servlet/v3_0/AbstractServlet3Test.java | 144 +++++++ .../v3_0/RequestDispatcherServlet.java | 51 +++ .../v3_0/TestAgentHttpResponseCustomizer.java | 27 ++ .../servlet/v3_0/TestServlet3.java | 352 ++++++++++++++++++ .../v3_0/jetty/JettyServlet3AsyncTest.java | 63 ++++ .../jetty/JettyServlet3FakeAsyncTest.java | 16 + .../v3_0/jetty/JettyServlet3SyncTest.java | 16 + .../servlet/v3_0/jetty/JettyServlet3Test.java | 118 ++++++ .../jetty/dispatch/JettyDispatchTest.java | 18 + .../JettyServlet3DispatchAsyncTest.java | 59 +++ .../JettyServlet3DispatchImmediateTest.java | 62 +++ .../dispatch/JettyServlet3ForwardTest.java | 56 +++ .../dispatch/JettyServlet3IncludeTest.java | 62 +++ .../v3_0/tomcat/ErrorHandlerValve.java | 32 ++ .../v3_0/tomcat/TestAccessLogValve.java | 82 ++++ .../v3_0/tomcat/TomcatServlet3AsyncTest.java | 58 +++ .../tomcat/TomcatServlet3FakeAsyncTest.java | 17 + .../v3_0/tomcat/TomcatServlet3SyncTest.java | 17 + .../v3_0/tomcat/TomcatServlet3Test.java | 245 ++++++++++++ .../tomcat/dispatch/TomcatDispatchTest.java | 18 + .../TomcatServlet3DispatchAsyncTest.java | 68 ++++ .../TomcatServlet3DispatchImmediateTest.java | 61 +++ .../dispatch/TomcatServlet3ForwardTest.java | 64 ++++ .../dispatch/TomcatServlet3IncludeTest.java | 64 ++++ .../servlet/AsyncRunnableWrapper.java | 3 +- 36 files changed, 2004 insertions(+), 160 deletions(-) create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3RequestAdviceScope.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseAdviceScope.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletResponse.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/AbstractServlet3Test.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/RequestDispatcherServlet.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestServlet3.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java 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 874bca299cb0..cb1cd66e3ecf 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 @@ -6,18 +6,11 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.bootstrap.CallDepth; -import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder; import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge; -import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver; -import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Servlet3SnippetInjectingResponseWrapper; import javax.annotation.Nullable; -import javax.servlet.Servlet; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; @@ -27,70 +20,8 @@ import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument; import net.bytebuddy.implementation.bytecode.assign.Assigner; -@SuppressWarnings("unused") public class Servlet3Advice { - public static class AdviceScope { - private final CallDepth callDepth; - private final ServletRequestContext requestContext; - private final Context context; - private final Scope scope; - - public AdviceScope( - CallDepth callDepth, - HttpServletRequest request, - HttpServletResponse response, - Object servletOrFilter) { - this.callDepth = callDepth; - this.callDepth.getAndIncrement(); - - Context currentContext = Context.current(); - Context attachedContext = helper().getServerContext(request); - Context contextToUpdate; - - requestContext = new ServletRequestContext<>(request, servletOrFilter); - if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) { - context = helper().start(currentContext, requestContext); - helper().setAsyncListenerResponse(context, response); - - contextToUpdate = context; - } else if (attachedContext != null - && helper().needsRescoping(currentContext, attachedContext)) { - // Given request already has a context associated with it. - // see the needsRescoping() javadoc for more explanation - contextToUpdate = attachedContext; - context = null; - } else { - // We are inside nested servlet/filter/app-server span, don't create new span - contextToUpdate = currentContext; - context = null; - } - - // Update context with info from current request to ensure that server span gets the best - // possible name. - // In case server span was created by app server instrumentations calling updateContext - // returns a new context that contains servlet context path that is used in other - // instrumentations for naming server span. - MappingResolver mappingResolver = Servlet3Singletons.getMappingResolver(servletOrFilter); - boolean servlet = servletOrFilter instanceof Servlet; - contextToUpdate = helper().updateContext(contextToUpdate, request, mappingResolver, servlet); - scope = contextToUpdate.makeCurrent(); - - if (context != null) { - // Only trigger response customizer once, so only if server span was created here - HttpServerResponseCustomizerHolder.getCustomizer() - .customize(contextToUpdate, response, Servlet3Accessor.INSTANCE); - } - } - - public void exit( - @Nullable Throwable throwable, HttpServletRequest request, HttpServletResponse response) { - - boolean topLevel = callDepth.decrementAndGet() == 0; - helper().end(requestContext, request, response, throwable, topLevel, context, scope); - } - } - @AssignReturned.ToArguments({ @ToArgument(value = 0, index = 1), @ToArgument(value = 1, index = 2) @@ -114,8 +45,8 @@ public static Object[] onEnter( response = new Servlet3SnippetInjectingResponseWrapper((HttpServletResponse) response, snippet); } - AdviceScope adviceScope = - new AdviceScope( + Servlet3RequestAdviceScope adviceScope = + new Servlet3RequestAdviceScope( CallDepth.forClass(AppServerBridge.getCallDepthKey()), (HttpServletRequest) request, (HttpServletResponse) response, @@ -129,7 +60,7 @@ public static void stopSpan( @Advice.Argument(1) ServletResponse response, @Advice.Thrown @Nullable Throwable throwable, @Advice.Enter Object[] enterResult) { - AdviceScope adviceScope = (AdviceScope) enterResult[0]; + Servlet3RequestAdviceScope adviceScope = (Servlet3RequestAdviceScope) enterResult[0]; if (adviceScope == null || !(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3FilterInitAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3FilterInitAdvice.java index 5bddc729c9e1..98eeb7d5444d 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3FilterInitAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3FilterInitAdvice.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.FILTER_MAPPING_RESOLVER_FACTORY; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.FILTER_MAPPING_RESOLVER; import javax.servlet.Filter; import javax.servlet.FilterConfig; @@ -20,7 +20,7 @@ public static void filterInit( if (filterConfig == null) { return; } - FILTER_MAPPING_RESOLVER_FACTORY.set( + FILTER_MAPPING_RESOLVER.set( filter, new Servlet3FilterMappingResolverFactory(filterConfig)); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InitAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InitAdvice.java index 9c105d27464a..da94976b715b 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InitAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InitAdvice.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.SERVLET_MAPPING_RESOLVER_FACTORY; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.SERVLET_MAPPING_RESOLVER; import javax.servlet.Servlet; import javax.servlet.ServletConfig; @@ -20,7 +20,7 @@ public static void servletInit( if (servletConfig == null) { return; } - SERVLET_MAPPING_RESOLVER_FACTORY.set( + SERVLET_MAPPING_RESOLVER.set( servlet, new Servlet3MappingResolverFactory(servletConfig)); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3RequestAdviceScope.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3RequestAdviceScope.java new file mode 100644 index 000000000000..c439bd6bc768 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3RequestAdviceScope.java @@ -0,0 +1,75 @@ +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.bootstrap.CallDepth; +import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder; +import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; +import javax.annotation.Nullable; +import javax.servlet.Servlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class Servlet3RequestAdviceScope { + private final CallDepth callDepth; + private final ServletRequestContext requestContext; + private final Context context; + private final Scope scope; + + public Servlet3RequestAdviceScope( + CallDepth callDepth, + HttpServletRequest request, + HttpServletResponse response, + Object servletOrFilter) { + this.callDepth = callDepth; + this.callDepth.getAndIncrement(); + + Context currentContext = Context.current(); + Context attachedContext = helper().getServerContext(request); + Context contextToUpdate; + + requestContext = new ServletRequestContext<>(request, servletOrFilter); + if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) { + context = helper().start(currentContext, requestContext); + helper().setAsyncListenerResponse(context, response); + + contextToUpdate = context; + } else if (attachedContext != null + && helper().needsRescoping(currentContext, attachedContext)) { + // Given request already has a context associated with it. + // see the needsRescoping() javadoc for more explanation + contextToUpdate = attachedContext; + context = null; + } else { + // We are inside nested servlet/filter/app-server span, don't create new span + contextToUpdate = currentContext; + context = null; + } + + // Update context with info from current request to ensure that server span gets the best + // possible name. + // In case server span was created by app server instrumentations calling updateContext + // returns a new context that contains servlet context path that is used in other + // instrumentations for naming server span. + MappingResolver mappingResolver = Servlet3Singletons.getMappingResolver(servletOrFilter); + boolean servlet = servletOrFilter instanceof Servlet; + contextToUpdate = helper().updateContext(contextToUpdate, request, mappingResolver, servlet); + scope = contextToUpdate.makeCurrent(); + + if (context != null) { + // Only trigger response customizer once, so only if server span was created here + HttpServerResponseCustomizerHolder.getCustomizer() + .customize(contextToUpdate, response, Servlet3Accessor.INSTANCE); + } + } + + public void exit( + @Nullable Throwable throwable, HttpServletRequest request, HttpServletResponse response) { + + boolean topLevel = callDepth.decrementAndGet() == 0; + helper().end(requestContext, request, response, throwable, topLevel, context, scope); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseAdviceScope.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseAdviceScope.java new file mode 100644 index 000000000000..cdd06bb20e74 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseAdviceScope.java @@ -0,0 +1,48 @@ +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.responseInstrumenter; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; +import io.opentelemetry.javaagent.bootstrap.CallDepth; +import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseAdviceHelper; +import javax.annotation.Nullable; + +public class Servlet3ResponseAdviceScope { + private final CallDepth callDepth; + private final ClassAndMethod classAndMethod; + private final Context context; + private final Scope scope; + + public Servlet3ResponseAdviceScope( + CallDepth callDepth, Class declaringClass, String methodName) { + this.callDepth = callDepth; + if (callDepth.getAndIncrement() > 0) { + this.classAndMethod = null; + this.context = null; + this.scope = null; + return; + } + HttpServletResponseAdviceHelper.StartResult result = + HttpServletResponseAdviceHelper.startSpan( + responseInstrumenter(), declaringClass, methodName); + if (result != null) { + classAndMethod = result.getClassAndMethod(); + context = result.getContext(); + scope = result.getScope(); + } else { + classAndMethod = null; + context = null; + scope = null; + } + } + + public void exit(@Nullable Throwable throwable) { + if (callDepth.decrementAndGet() > 0) { + return; + } + HttpServletResponseAdviceHelper.stopSpan( + responseInstrumenter(), throwable, context, scope, classAndMethod); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseSendAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseSendAdvice.java index 4e2441f4b4d0..ebb7921320dc 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseSendAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseSendAdvice.java @@ -5,67 +5,23 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.responseInstrumenter; - -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; import io.opentelemetry.javaagent.bootstrap.CallDepth; -import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseAdviceHelper; -import javax.annotation.Nullable; import javax.servlet.http.HttpServletResponse; import net.bytebuddy.asm.Advice; @SuppressWarnings("unused") public class Servlet3ResponseSendAdvice { - public static class AdviceScope { - private final CallDepth callDepth; - private final ClassAndMethod classAndMethod; - private final Context context; - private final Scope scope; - - public AdviceScope(CallDepth callDepth, Class declaringClass, String methodName) { - this.callDepth = callDepth; - if (callDepth.getAndIncrement() > 0) { - this.classAndMethod = null; - this.context = null; - this.scope = null; - return; - } - HttpServletResponseAdviceHelper.StartResult result = - HttpServletResponseAdviceHelper.startSpan( - responseInstrumenter(), declaringClass, methodName); - if (result != null) { - classAndMethod = result.getClassAndMethod(); - context = result.getContext(); - scope = result.getScope(); - } else { - classAndMethod = null; - context = null; - scope = null; - } - } - - public void exit(@Nullable Throwable throwable) { - if (callDepth.decrementAndGet() > 0) { - return; - } - HttpServletResponseAdviceHelper.stopSpan( - responseInstrumenter(), throwable, context, scope, classAndMethod); - } - } - @Advice.OnMethodEnter(suppress = Throwable.class) - public static AdviceScope start( + public static Servlet3ResponseAdviceScope start( @Advice.Origin("#t") Class declaringClass, @Advice.Origin("#m") String methodName) { - return new AdviceScope( + return new Servlet3ResponseAdviceScope( CallDepth.forClass(HttpServletResponse.class), declaringClass, methodName); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void stopSpan( - @Advice.Thrown Throwable throwable, @Advice.Enter AdviceScope adviceScope) { + @Advice.Thrown Throwable throwable, @Advice.Enter Servlet3ResponseAdviceScope adviceScope) { adviceScope.exit(throwable); } } 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 index 17c7082b6f52..73c3228d149d 100644 --- 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 @@ -24,14 +24,6 @@ public final class Servlet3Singletons { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.servlet-3.0"; - public static final VirtualField - SERVLET_MAPPING_RESOLVER_FACTORY = - VirtualField.find(Servlet.class, MappingResolver.Factory.class); - - public static final VirtualField - FILTER_MAPPING_RESOLVER_FACTORY = - VirtualField.find(Filter.class, MappingResolver.Factory.class); - private static final Instrumenter< ServletRequestContext, ServletResponseContext> INSTRUMENTER = @@ -41,9 +33,9 @@ public final class Servlet3Singletons { private static final ServletHelper HELPER = new ServletHelper<>(INSTRUMENTER, Servlet3Accessor.INSTANCE); - private static final VirtualField SERVLET_MAPPING_RESOLVER = + public static final VirtualField SERVLET_MAPPING_RESOLVER = VirtualField.find(Servlet.class, MappingResolver.Factory.class); - private static final VirtualField FILTER_MAPPING_RESOLVER = + public static final VirtualField FILTER_MAPPING_RESOLVER = VirtualField.find(Filter.class, MappingResolver.Factory.class); private static final Instrumenter RESPONSE_INSTRUMENTER = diff --git a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts index 51506d02d36a..eb74a6dc14d3 100644 --- a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts +++ b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts @@ -8,4 +8,22 @@ dependencies { // FIXME: These dependencies need to be shadowed into the library. library(project(":instrumentation:servlet:servlet-3.0:javaagent")) library(project(":instrumentation:servlet:servlet-common:javaagent")) + library(project(":instrumentation:servlet:servlet-common:bootstrap")) + library(project(":javaagent-extension-api")) + +// testImplementation(project(":testing:agent-exporter")) + + testLibrary("org.eclipse.jetty:jetty-server:8.0.0.v20110901") + testLibrary("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901") + testLibrary("org.apache.tomcat.embed:tomcat-embed-core:8.0.41") + testLibrary("org.apache.tomcat.embed:tomcat-embed-jasper:8.0.41") +} + + +tasks { + withType().configureEach { + // required on jdk17 to allow tomcat to shutdown properly. + jvmArgs("--add-opens=java.base/java.util=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + } } diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java index 4f8adc592604..8878171638fe 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java @@ -1,10 +1,8 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.FILTER_MAPPING_RESOLVER; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; -import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; +import io.opentelemetry.javaagent.bootstrap.CallDepth; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -12,6 +10,7 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -21,10 +20,13 @@ * will miss anything that happens earlier in the filter stack or problems handled directly by the * app server. For this reason, Java Agent instrumentation is preferred when possible. */ + @WebFilter("/*") public class OpenTelemetryServletFilter implements Filter { @Override - public void init(FilterConfig filterConfig) throws ServletException {} + public void init(FilterConfig filterConfig) throws ServletException { + FILTER_MAPPING_RESOLVER.set(this, new Servlet3FilterMappingResolverFactory(filterConfig)); + } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) @@ -37,30 +39,19 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; - ServletRequestContext requestContext = - new ServletRequestContext<>(httpRequest, this); - - // Bail if we shouldn't start a new span. - if (!helper().shouldStart(Context.current(), requestContext)) { - chain.doFilter(request, response); - return; - } - - Context spanContext = helper().start(Context.current(), requestContext); - helper().setAsyncListenerResponse(spanContext, (HttpServletResponse) response); - // Not using try-with-resources to match the api usage of Servlet3Advice. - // (helper().end is responsible for closing the scope.) - Scope scope = spanContext.makeCurrent(); Throwable throwable = null; + Servlet3RequestAdviceScope adviceScope = + new Servlet3RequestAdviceScope( + CallDepth.forClass(OpenTelemetryServletFilter.class), httpRequest, httpResponse, this); try { chain.doFilter( - new OtelHttpServletRequest((HttpServletRequest) request), response); + new OtelHttpServletRequest(httpRequest), new OtelHttpServletResponse(httpResponse)); } catch (Throwable e) { throwable = e; throw e; } finally { - helper().end(requestContext, httpRequest, httpResponse, throwable, true, spanContext, scope); + adviceScope.exit(throwable, httpRequest, httpResponse); } } diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletRequest.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletRequest.java index 6b8cd4291631..1c77aa02039f 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletRequest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletRequest.java @@ -23,19 +23,18 @@ public AsyncContext getAsyncContext() { } @Override - public AsyncContext startAsync() throws IllegalStateException { + public AsyncContext startAsync() { try { - return super.startAsync(); + return new OtelAsyncContext(super.startAsync()); } finally { helper().attachAsyncListener(this, Context.current()); } } @Override - public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) - throws IllegalStateException { + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) { try { - return super.startAsync(servletRequest, servletResponse); + return new OtelAsyncContext(super.startAsync(servletRequest, servletResponse)); } finally { helper().attachAsyncListener(this, Context.current()); } diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletResponse.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletResponse.java new file mode 100644 index 000000000000..fbeb6119f226 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletResponse.java @@ -0,0 +1,63 @@ +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + + +import io.opentelemetry.javaagent.bootstrap.CallDepth; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +/// Wrapper around [HttpServletResponse]. +public class OtelHttpServletResponse extends HttpServletResponseWrapper { + + public OtelHttpServletResponse(HttpServletResponse response) { + super(response); + } + + @Override + public void sendError(int sc, String msg) throws IOException { + Servlet3ResponseAdviceScope scope = + new Servlet3ResponseAdviceScope( + CallDepth.forClass(HttpServletResponse.class), this.getClass(), "sendError"); + Throwable throwable = null; + try { + super.sendError(sc, msg); + } catch (Throwable ex) { + throwable = ex; + throw ex; + } finally { + scope.exit(throwable); + } + } + + @Override + public void sendError(int sc) throws IOException { + Servlet3ResponseAdviceScope scope = + new Servlet3ResponseAdviceScope( + CallDepth.forClass(HttpServletResponse.class), this.getClass(), "sendError"); + Throwable throwable = null; + try { + super.sendError(sc); + } catch (Throwable ex) { + throwable = ex; + throw ex; + } finally { + scope.exit(throwable); + } + } + + @Override + public void sendRedirect(String location) throws IOException { + Servlet3ResponseAdviceScope scope = + new Servlet3ResponseAdviceScope( + CallDepth.forClass(HttpServletResponse.class), this.getClass(), "sendRedirect"); + Throwable throwable = null; + try { + super.sendRedirect(location); + } catch (Throwable ex) { + throwable = ex; + throw ex; + } finally { + scope.exit(throwable); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/AbstractServlet3Test.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/AbstractServlet3Test.java new file mode 100644 index 000000000000..0c792bf7c92b --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/AbstractServlet3Test.java @@ -0,0 +1,144 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.trace.data.SpanData; +import javax.servlet.Servlet; + +public abstract class AbstractServlet3Test extends AbstractHttpServerTest { + + public static final ServerEndpoint HTML_PRINT_WRITER = + new ServerEndpoint( + "HTML_PRINT_WRITER", + "htmlPrintWriter", + 200, + "\n" + + "\n" + + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + ""); + public static final ServerEndpoint HTML_SERVLET_OUTPUT_STREAM = + new ServerEndpoint( + "HTML_SERVLET_OUTPUT_STREAM", + "htmlServletOutputStream", + 200, + "\n" + + "\n" + + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + ""); + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestCaptureRequestParameters(false); // Requires AgentConfig. + options.setTestCaptureHttpHeaders(false); // Requires AgentConfig. + options.disableTestNonStandardHttpMethod(); // test doesn't use route mapping correctly. + options.setTestException(false); // filters don't have visibility into exception handling above. + HttpServerResponseCustomizerHolder.setCustomizer(new TestAgentHttpResponseCustomizer()); + options.setHasResponseCustomizer(e -> true); + options.setHasResponseSpan(this::hasResponseSpan); + } + + protected boolean hasResponseSpan(ServerEndpoint endpoint) { + return endpoint.equals(REDIRECT) || (endpoint.equals(ERROR) && errorEndpointUsesSendError()); + } + + public abstract Class servlet(); + + public abstract void addServlet(CONTEXT context, String path, Class servlet) + throws Exception; + + protected void setupServlets(CONTEXT context) throws Exception { + Class servlet = servlet(); + + addServlet(context, SUCCESS.getPath(), servlet); + addServlet(context, QUERY_PARAM.getPath(), servlet); + addServlet(context, ERROR.getPath(), servlet); + addServlet(context, EXCEPTION.getPath(), servlet); + addServlet(context, REDIRECT.getPath(), servlet); + addServlet(context, AUTH_REQUIRED.getPath(), servlet); + addServlet(context, INDEXED_CHILD.getPath(), servlet); + addServlet(context, CAPTURE_HEADERS.getPath(), servlet); + addServlet(context, CAPTURE_PARAMETERS.getPath(), servlet); + addServlet(context, HTML_PRINT_WRITER.getPath(), servlet); + addServlet(context, HTML_SERVLET_OUTPUT_STREAM.getPath(), servlet); + } + + @Override + public String expectedHttpRoute(ServerEndpoint endpoint, String method) { + // no need to compute route if we're not expecting it + if (!hasHttpRouteAttribute(endpoint)) { + return null; + } + + if (method.equals(HttpConstants._OTHER)) { + return getContextPath() + endpoint.getPath(); + } + + // NOTE: Primary difference from javaagent servlet instrumentation! + // Since just we're working with a filter, we can't actually get the proper servlet path. + return getContextPath() + "/*"; + } + + public boolean errorEndpointUsesSendError() { + return true; + } + + @Override + protected SpanDataAssert assertResponseSpan( + SpanDataAssert span, SpanData parentSpan, String method, ServerEndpoint endpoint) { + switch (endpoint.name()) { + case "REDIRECT": + SpanDataAssert spanDataAssert = + span.satisfies(s -> assertThat(s.getName()).matches(".*\\.sendRedirect")) + .hasKind(SpanKind.INTERNAL); + if (assertParentOnRedirect()) { + return spanDataAssert.hasParent(parentSpan); + } + return spanDataAssert; + case "ERROR": + return span.satisfies(s -> assertThat(s.getName()).matches(".*\\.sendError")) + .hasKind(SpanKind.INTERNAL) + .hasParent(parentSpan); + default: + break; + } + return span; + } + + protected boolean assertParentOnRedirect() { + return true; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/RequestDispatcherServlet.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/RequestDispatcherServlet.java new file mode 100644 index 000000000000..3413a0b4e16c --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/RequestDispatcherServlet.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import java.io.IOException; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class RequestDispatcherServlet { + + @WebServlet(asyncSupported = true) + public static class Forward extends HttpServlet { + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + String target = req.getServletPath().replace("/dispatch", ""); + ServletContext context = getServletContext(); + RequestDispatcher dispatcher = context.getRequestDispatcher(target); + dispatcher.forward(req, resp); + } + } + + @WebServlet(asyncSupported = true) + public static class Include extends HttpServlet { + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + String target = req.getServletPath().replace("/dispatch", ""); + ServletContext context = getServletContext(); + RequestDispatcher dispatcher = context.getRequestDispatcher(target); + // for HTML test case, set the content type before calling include because + // setContentType will be rejected if called inside include + // check + // https://docs.oracle.com/javaee/7/api/javax/servlet/RequestDispatcher.html#include-javax.servlet.ServletRequest-javax.servlet.ServletResponse- + if ("/htmlPrintWriter".equals(target) || "/htmlServletOutputStream".equals(target)) { + resp.setContentType("text/html"); + } + dispatcher.include(req, resp); + } + } + + private RequestDispatcherServlet() {} +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java new file mode 100644 index 000000000000..4ec0e3706f35 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizer; +import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator; + +public class TestAgentHttpResponseCustomizer implements HttpServerResponseCustomizer { + + @Override + public void customize( + Context serverContext, T response, HttpServerResponseMutator responseMutator) { + + SpanContext spanContext = Span.fromContext(serverContext).getSpanContext(); + String traceId = spanContext.getTraceId(); + String spanId = spanContext.getSpanId(); + + responseMutator.appendHeader(response, "X-Test-TraceId", traceId); + responseMutator.appendHeader(response, "X-Test-SpanId", spanId); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestServlet3.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestServlet3.java new file mode 100644 index 000000000000..293c1eaf861d --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestServlet3.java @@ -0,0 +1,352 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest.controller; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test.HTML_PRINT_WRITER; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.SERVLET_MAPPING_RESOLVER; + +import io.opentelemetry.instrumentation.testing.GlobalTraceUtil; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import javax.servlet.AsyncContext; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class TestServlet3 { + + private TestServlet3() {} + + @WebServlet + public static class Sync extends HttpServlet { + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + SERVLET_MAPPING_RESOLVER.set(this, new Servlet3MappingResolverFactory(config)); + } + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException { + String servletPath = (String) req.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); + if (servletPath == null) { + servletPath = req.getServletPath(); + } + + ServerEndpoint endpoint = ServerEndpoint.forPath(servletPath); + controller( + endpoint, + () -> { + resp.setContentType("text/plain"); + if (SUCCESS.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (INDEXED_CHILD.equals(endpoint)) { + endpoint.collectSpanAttributes(req::getParameter); + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (QUERY_PARAM.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(req.getQueryString()); + } else if (REDIRECT.equals(endpoint)) { + resp.sendRedirect(endpoint.getBody()); + } else if (CAPTURE_HEADERS.equals(endpoint)) { + resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request")); + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (CAPTURE_PARAMETERS.equals(endpoint)) { + req.setCharacterEncoding("UTF8"); + String value = req.getParameter("test-parameter"); + if (!value.equals("test value õäöü")) { + throw new IllegalStateException( + "request parameter does not have expected value " + value); + } + + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (ERROR.equals(endpoint)) { + resp.sendError(endpoint.getStatus(), endpoint.getBody()); + } else if (EXCEPTION.equals(endpoint)) { + throw new IllegalStateException(endpoint.getBody()); + } else if (HTML_PRINT_WRITER.equals(endpoint)) { + resp.setContentType("text/html"); + resp.setStatus(endpoint.getStatus()); + resp.setContentLength(endpoint.getBody().length()); + resp.getWriter().print(endpoint.getBody()); + } else if (HTML_SERVLET_OUTPUT_STREAM.equals(endpoint)) { + resp.setContentType("text/html"); + resp.setStatus(endpoint.getStatus()); + resp.setContentLength(endpoint.getBody().length()); + byte[] body = endpoint.getBody().getBytes(StandardCharsets.UTF_8); + resp.getOutputStream().write(body, 0, body.length); + } + return null; + }); + } + } + + @WebServlet(asyncSupported = true) + public static class Async extends HttpServlet { + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + SERVLET_MAPPING_RESOLVER.set(this, new Servlet3MappingResolverFactory(config)); + } + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) { + ServerEndpoint endpoint = ServerEndpoint.forPath(req.getServletPath()); + CountDownLatch latch = new CountDownLatch(1); + boolean startAsyncInSpan = + SUCCESS.equals(endpoint) && "true".equals(req.getParameter("startAsyncInSpan")); + AsyncContext context = + startAsyncInSpan + ? GlobalTraceUtil.runWithSpan("startAsync", () -> req.startAsync()) + : req.startAsync(); + if (endpoint.equals(EXCEPTION)) { + context.setTimeout(5000); + } + + context.start( + () -> { + try { + controller( + endpoint, + () -> { + resp.setContentType("text/plain"); + if (SUCCESS.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + context.complete(); + } else if (INDEXED_CHILD.equals(endpoint)) { + endpoint.collectSpanAttributes(req::getParameter); + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + context.complete(); + } else if (QUERY_PARAM.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(req.getQueryString()); + context.complete(); + } else if (REDIRECT.equals(endpoint)) { + resp.sendRedirect(endpoint.getBody()); + context.complete(); + } else if (CAPTURE_HEADERS.equals(endpoint)) { + resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request")); + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + context.complete(); + } else if (CAPTURE_PARAMETERS.equals(endpoint)) { + req.setCharacterEncoding("UTF8"); + String value = req.getParameter("test-parameter"); + if (!value.equals("test value õäöü")) { + throw new IllegalStateException( + "request parameter does not have expected value " + value); + } + + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + context.complete(); + } else if (ERROR.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + context.complete(); + } else if (EXCEPTION.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + PrintWriter writer = resp.getWriter(); + writer.print(endpoint.getBody()); + if (req.getClass().getName().contains("catalina")) { + // on tomcat close the writer to ensure response is sent immediately, + // otherwise there is a chance that tomcat resets the connection before the + // response is sent + writer.close(); + } + throw new IllegalStateException(endpoint.getBody()); + } else if (HTML_PRINT_WRITER.equals(endpoint)) { + resp.setContentType("text/html"); + resp.setStatus(endpoint.getStatus()); + resp.setContentLength(endpoint.getBody().length()); + resp.getWriter().print(endpoint.getBody()); + context.complete(); + } else if (HTML_SERVLET_OUTPUT_STREAM.equals(endpoint)) { + resp.setContentType("text/html"); + resp.setStatus(endpoint.getStatus()); + resp.getOutputStream().print(endpoint.getBody()); + context.complete(); + } + return null; + }); + } catch (Exception exception) { + if (exception instanceof RuntimeException) { + throw (RuntimeException) exception; + } + throw new IllegalStateException(exception); + } finally { + latch.countDown(); + } + }); + try { + latch.await(); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + } + } + } + + @WebServlet(asyncSupported = true) + public static class FakeAsync extends HttpServlet { + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + SERVLET_MAPPING_RESOLVER.set(this, new Servlet3MappingResolverFactory(config)); + } + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException { + AsyncContext context = req.startAsync(); + try { + ServerEndpoint endpoint = ServerEndpoint.forPath(req.getServletPath()); + + controller( + endpoint, + () -> { + resp.setContentType("text/plain"); + if (SUCCESS.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (INDEXED_CHILD.equals(endpoint)) { + endpoint.collectSpanAttributes(req::getParameter); + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (QUERY_PARAM.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(req.getQueryString()); + } else if (REDIRECT.equals(endpoint)) { + resp.sendRedirect(endpoint.getBody()); + } else if (CAPTURE_HEADERS.equals(endpoint)) { + resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request")); + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (CAPTURE_PARAMETERS.equals(endpoint)) { + req.setCharacterEncoding("UTF8"); + String value = req.getParameter("test-parameter"); + if (!value.equals("test value õäöü")) { + throw new IllegalStateException( + "request parameter does not have expected value " + value); + } + + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (ERROR.equals(endpoint)) { + resp.sendError(endpoint.getStatus(), endpoint.getBody()); + } else if (EXCEPTION.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + throw new IllegalStateException(endpoint.getBody()); + } else if (HTML_PRINT_WRITER.equals(endpoint)) { + // intentionally testing setting status before contentType here to cover that case + // somewhere + resp.setStatus(endpoint.getStatus()); + resp.setContentType("text/html"); + resp.getWriter().print(endpoint.getBody()); + } else if (HTML_SERVLET_OUTPUT_STREAM.equals(endpoint)) { + resp.setContentType("text/html"); + resp.setStatus(endpoint.getStatus()); + resp.getOutputStream().print(endpoint.getBody()); + } + return null; + }); + } finally { + context.complete(); + } + } + } + + @WebServlet(asyncSupported = true) + public static class DispatchImmediate extends HttpServlet { + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + SERVLET_MAPPING_RESOLVER.set(this, new Servlet3MappingResolverFactory(config)); + } + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) { + String target = req.getServletPath().replace("/dispatch", ""); + if (req.getQueryString() != null) { + target += "?" + req.getQueryString(); + } + + req.startAsync().dispatch(target); + } + } + + @WebServlet(asyncSupported = true) + public static class DispatchAsync extends HttpServlet { + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + SERVLET_MAPPING_RESOLVER.set(this, new Servlet3MappingResolverFactory(config)); + } + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) { + AsyncContext context = req.startAsync(); + context.start( + () -> { + String target = req.getServletPath().replace("/dispatch", ""); + if (req.getQueryString() != null) { + target += "?" + req.getQueryString(); + } + context.dispatch(target); + }); + } + } + + @WebServlet(asyncSupported = true) + public static class DispatchRecursive extends HttpServlet { + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + SERVLET_MAPPING_RESOLVER.set(this, new Servlet3MappingResolverFactory(config)); + } + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException { + if (req.getServletPath().equals("/recursive")) { + resp.getWriter().print("Hello Recursive"); + } + + int depth = Integer.parseInt(req.getParameter("depth")); + if (depth > 0) { + req.startAsync().dispatch("/dispatch/recursive?depth=" + (depth - 1)); + } else { + req.startAsync().dispatch("/recursive"); + } + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java new file mode 100644 index 000000000000..ab3276f60172 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; +import io.opentelemetry.testing.internal.armeria.common.HttpMethod; +import javax.servlet.Servlet; +import org.junit.jupiter.api.Test; + +class JettyServlet3AsyncTest extends JettyServlet3Test { + + @Override + public Class servlet() { + return TestServlet3.Async.class; + } + + @Override + public boolean errorEndpointUsesSendError() { + return false; + } + + @Override + public boolean isAsyncTest() { + return true; + } + + @Test + void startAsyncInSpan() { + AggregatedHttpRequest request = + AggregatedHttpRequest.of( + HttpMethod.GET, resolveAddress(SUCCESS, "h1c://") + "?startAsyncInSpan=true"); + AggregatedHttpResponse response = client.execute(request).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(SUCCESS.getStatus()); + assertThat(response.contentUtf8()).isEqualTo(SUCCESS.getBody()); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET " + getContextPath() + "/*") + .hasKind(SpanKind.SERVER) + .hasNoParent(), + span -> + span.hasName("startAsync") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)), + span -> + span.hasName("controller") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java new file mode 100644 index 000000000000..3345b10847b7 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import javax.servlet.Servlet; + +class JettyServlet3FakeAsyncTest extends JettyServlet3Test { + @Override + public Class servlet() { + return TestServlet3.FakeAsync.class; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java new file mode 100644 index 000000000000..b38451c89f5f --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import javax.servlet.Servlet; + +class JettyServlet3SyncTest extends JettyServlet3Test { + @Override + public Class servlet() { + return TestServlet3.Sync.class; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java new file mode 100644 index 000000000000..1a4ef1805a03 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java @@ -0,0 +1,118 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.OpenTelemetryServletFilter; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.io.IOException; +import java.io.Writer; +import java.net.InetSocketAddress; +import java.util.EnumSet; +import javax.servlet.DispatcherType; +import javax.servlet.Servlet; +import javax.servlet.http.HttpServletRequest; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.junit.jupiter.api.extension.RegisterExtension; + +public abstract class JettyServlet3Test + extends AbstractServlet3Test { + + @RegisterExtension + protected static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forLibrary(); + + static final boolean IS_BEFORE_94 = isBefore94(); + + public static boolean isBefore94() { + String[] version = Server.getVersion().split("\\."); + int major = Integer.parseInt(version[0]); + int minor = Integer.parseInt(version[1]); + return major < 9 || (major == 9 && minor < 4); + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestNotFound(false); + options.setContextPath("/jetty-context"); + options.setVerifyServerSpanEndTime(!isAsyncTest()); + } + + @Override + protected boolean hasResponseSpan(ServerEndpoint endpoint) { + return (IS_BEFORE_94 && endpoint == EXCEPTION && !isAsyncTest()) + || super.hasResponseSpan(endpoint); + } + + public boolean isAsyncTest() { + return false; + } + + @Override + protected SpanDataAssert assertResponseSpan( + SpanDataAssert span, + SpanData controllerSpan, + SpanData handlerSpan, + String method, + ServerEndpoint endpoint) { + if (IS_BEFORE_94 && endpoint.equals(EXCEPTION)) { + span.satisfies(it -> assertThat(it.getName()).matches(".*\\.sendError")) + .hasKind(SpanKind.INTERNAL) + .hasParent(handlerSpan); + } + + return super.assertResponseSpan(span, controllerSpan, handlerSpan, method, endpoint); + } + + @Override + protected Server setupServer() throws Exception { + Server jettyServer = new Server(new InetSocketAddress("localhost", port)); + + ServletContextHandler servletContext = new ServletContextHandler(null, getContextPath()); + servletContext.setErrorHandler( + new ErrorHandler() { + @Override + protected void handleErrorPage( + HttpServletRequest request, Writer writer, int code, String message) + throws IOException { + Throwable th = (Throwable) request.getAttribute("javax.servlet.error.exception"); + writer.write(th != null ? th.getMessage() : message); + } + }); + servletContext.addFilter(OpenTelemetryServletFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); + setupServlets(servletContext); + jettyServer.setHandler(servletContext); + + jettyServer.start(); + + return jettyServer; + } + + @Override + public void stopServer(Server server) throws Exception { + server.stop(); + server.destroy(); + } + + @Override + public void addServlet( + ServletContextHandler servletContext, String path, Class servlet) + throws Exception { + servletContext.addServlet(servlet, path); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java new file mode 100644 index 000000000000..a4432b127433 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.JettyServlet3Test; + +abstract class JettyDispatchTest extends JettyServlet3Test { + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setContextPath(getContextPath() + "/dispatch"); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java new file mode 100644 index 000000000000..83c03f135f57 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import javax.servlet.Servlet; +import org.eclipse.jetty.servlet.ServletContextHandler; + +class JettyServlet3DispatchAsyncTest extends JettyDispatchTest { + @Override + public Class servlet() { + return TestServlet3.Async.class; + } + + @Override + public boolean isAsyncTest() { + return true; + } + + @Override + protected void setupServlets(ServletContextHandler context) throws Exception { + super.setupServlets(context); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), TestServlet3.DispatchAsync.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + SUCCESS.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + QUERY_PARAM.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + ERROR.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + AUTH_REQUIRED.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + CAPTURE_HEADERS.getPath(), TestServlet3.DispatchAsync.class); + addServlet( + context, "/dispatch" + CAPTURE_PARAMETERS.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + INDEXED_CHILD.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive.class); + } + + @Override + public boolean errorEndpointUsesSendError() { + return false; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java new file mode 100644 index 000000000000..5e8ba5457163 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import javax.servlet.Servlet; +import org.eclipse.jetty.servlet.ServletContextHandler; + +class JettyServlet3DispatchImmediateTest extends JettyDispatchTest { + @Override + public Class servlet() { + return TestServlet3.Async.class; + } + + @Override + public boolean isAsyncTest() { + return true; + } + + @Override + public boolean errorEndpointUsesSendError() { + return false; + } + + @Override + protected void setupServlets(ServletContextHandler context) throws Exception { + super.setupServlets(context); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + SUCCESS.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + QUERY_PARAM.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + ERROR.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + AUTH_REQUIRED.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + CAPTURE_HEADERS.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + CAPTURE_PARAMETERS.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + INDEXED_CHILD.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java new file mode 100644 index 000000000000..ef83a24e85c5 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.RequestDispatcherServlet; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import javax.servlet.Servlet; +import org.eclipse.jetty.servlet.ServletContextHandler; + +class JettyServlet3ForwardTest extends JettyDispatchTest { + @Override + public Class servlet() { + return TestServlet3.Sync.class; // dispatch to sync servlet + } + + @Override + protected void setupServlets(ServletContextHandler context) throws Exception { + super.setupServlets(context); + + addServlet(context, "/dispatch" + SUCCESS.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + QUERY_PARAM.getPath(), RequestDispatcherServlet.Forward.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), RequestDispatcherServlet.Forward.class); + addServlet(context, "/dispatch" + ERROR.getPath(), RequestDispatcherServlet.Forward.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + AUTH_REQUIRED.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + CAPTURE_HEADERS.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, + "/dispatch" + CAPTURE_PARAMETERS.getPath(), + RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + INDEXED_CHILD.getPath(), RequestDispatcherServlet.Forward.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java new file mode 100644 index 000000000000..724c16475613 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.RequestDispatcherServlet; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import javax.servlet.Servlet; +import org.eclipse.jetty.servlet.ServletContextHandler; + +class JettyServlet3IncludeTest extends JettyDispatchTest { + @Override + public Class servlet() { + return TestServlet3.Sync.class; // dispatch to sync servlet + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestRedirect(false); + options.setTestCaptureHttpHeaders(false); + options.setTestError(false); + } + + @Override + protected void setupServlets(ServletContextHandler context) throws Exception { + super.setupServlets(context); + + addServlet(context, "/dispatch" + SUCCESS.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + QUERY_PARAM.getPath(), RequestDispatcherServlet.Include.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), RequestDispatcherServlet.Include.class); + addServlet(context, "/dispatch" + ERROR.getPath(), RequestDispatcherServlet.Include.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + AUTH_REQUIRED.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, + "/dispatch" + CAPTURE_PARAMETERS.getPath(), + RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + INDEXED_CHILD.getPath(), RequestDispatcherServlet.Include.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java new file mode 100644 index 000000000000..e2266081cf08 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +import java.io.IOException; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.apache.catalina.valves.ErrorReportValve; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// public, because it's loaded by reflection +public class ErrorHandlerValve extends ErrorReportValve { + + private static final Logger logger = LoggerFactory.getLogger(ErrorHandlerValve.class); + + @Override + protected void report(Request request, Response response, Throwable t) { + if (response.getStatus() < 400 || response.getContentWritten() > 0 || !response.isError()) { + return; + } + + try { + response.getWriter().print(t != null ? t.getCause().getMessage() : response.getMessage()); + } catch (IOException e) { + logger.error("Failed to write error response", e); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java new file mode 100644 index 000000000000..1be5cbe759a7 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java @@ -0,0 +1,82 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +import java.io.IOException; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.ServletException; +import org.apache.catalina.AccessLog; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.apache.catalina.valves.ValveBase; + +// public, because it's loaded by reflection +public class TestAccessLogValve extends ValveBase implements AccessLog { + + public final List> getLoggedIds() { + return loggedIds; + } + + private final List> loggedIds = new ArrayList<>(); + + public TestAccessLogValve() { + super(true); + } + + @Override + public void log(Request request, Response response, long time) { + if (request.getParameter("access-log") == null) { + return; + } + + synchronized (loggedIds) { + loggedIds.add( + new AbstractMap.SimpleEntry<>( + request.getAttribute("trace_id").toString(), + request.getAttribute("span_id").toString())); + loggedIds.notifyAll(); + } + } + + public void waitForLoggedIds(int expected) { + long timeout = TimeUnit.SECONDS.toMillis(20); + long startTime = System.currentTimeMillis(); + long endTime = startTime + timeout; + long toWait = timeout; + synchronized (loggedIds) { + while (loggedIds.size() < expected && toWait > 0) { + try { + loggedIds.wait(toWait); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + toWait = endTime - System.currentTimeMillis(); + } + + if (toWait <= 0) { + throw new RuntimeException( + "Timeout waiting for " + expected + " access log ids, got " + loggedIds.size()); + } + } + } + + @Override + public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {} + + @Override + public boolean getRequestAttributesEnabled() { + return false; + } + + @Override + public void invoke(Request request, Response response) throws IOException, ServletException { + getNext().invoke(request, response); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java new file mode 100644 index 000000000000..8c07ba131ddd --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java @@ -0,0 +1,58 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; +import io.opentelemetry.testing.internal.armeria.common.HttpMethod; +import javax.servlet.Servlet; +import org.junit.jupiter.api.Test; + +class TomcatServlet3AsyncTest extends TomcatServlet3Test { + + @Override + public Class servlet() { + return TestServlet3.Async.class; + } + + @Override + public boolean errorEndpointUsesSendError() { + return false; + } + + @Test + void startAsyncInSpan() { + AggregatedHttpRequest request = + AggregatedHttpRequest.of( + HttpMethod.GET, resolveAddress(SUCCESS, "h1c://") + "?startAsyncInSpan=true"); + AggregatedHttpResponse response = client.execute(request).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(SUCCESS.getStatus()); + assertThat(response.contentUtf8()).isEqualTo(SUCCESS.getBody()); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET " + getContextPath() + "/*") + .hasKind(SpanKind.SERVER) + .hasNoParent(), + span -> + span.hasName("startAsync") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)), + span -> + span.hasName("controller") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java new file mode 100644 index 000000000000..831e7360415f --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java @@ -0,0 +1,17 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import javax.servlet.Servlet; + +class TomcatServlet3FakeAsyncTest extends TomcatServlet3Test { + + @Override + public Class servlet() { + return TestServlet3.FakeAsync.class; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java new file mode 100644 index 000000000000..6b0a20e52860 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java @@ -0,0 +1,17 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import javax.servlet.Servlet; + +class TomcatServlet3SyncTest extends TomcatServlet3Test { + + @Override + public Class servlet() { + return TestServlet3.Sync.class; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java new file mode 100644 index 000000000000..6774b3dde7ad --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java @@ -0,0 +1,245 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.OpenTelemetryServletFilter; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.testing.assertj.TraceAssert; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.servlet.Servlet; +import org.apache.catalina.Container; +import org.apache.catalina.Context; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.core.StandardContext; +import org.apache.catalina.core.StandardEngine; +import org.apache.catalina.core.StandardHost; +import org.apache.catalina.startup.Tomcat; +import org.apache.tomcat.util.descriptor.web.FilterDef; +import org.apache.tomcat.util.descriptor.web.FilterMap; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public abstract class TomcatServlet3Test extends AbstractServlet3Test { + + @RegisterExtension + protected static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forLibrary(); + + private static final ServerEndpoint ACCESS_LOG_SUCCESS = + new ServerEndpoint( + "ACCESS_LOG_SUCCESS", + "success?access-log=true", + SUCCESS.getStatus(), + SUCCESS.getBody(), + false); + private static final ServerEndpoint ACCESS_LOG_ERROR = + new ServerEndpoint( + "ACCESS_LOG_ERROR", + "error-status?access-log=true", + ERROR.getStatus(), + ERROR.getBody(), + false); + private final TestAccessLogValve accessLogValue = new TestAccessLogValve(); + + @TempDir private static File tempDir; + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setContextPath("/tomcat-context"); + options.setTestError(testError()); + } + + public boolean testError() { + return false; + } + + @Override + protected SpanDataAssert assertResponseSpan( + SpanDataAssert span, SpanData parentSpan, String method, ServerEndpoint endpoint) { + if (NOT_FOUND.equals(endpoint)) { + span.satisfies(s -> assertThat(s.getName()).matches(".*\\.sendError")) + .hasKind(SpanKind.INTERNAL) + .hasParent(parentSpan); + } + return super.assertResponseSpan(span, parentSpan, method, endpoint); + } + + @Override + protected boolean hasResponseSpan(ServerEndpoint endpoint) { + return endpoint == NOT_FOUND || super.hasResponseSpan(endpoint); + } + + @SuppressWarnings("deprecation") // needed API also on Engine. + @Override + protected Tomcat setupServer() throws Exception { + Tomcat tomcatServer = new Tomcat(); + + File baseDir = tempDir; + tomcatServer.setBaseDir(baseDir.getAbsolutePath()); + + tomcatServer.setPort(port); + tomcatServer.getConnector().setEnableLookups(true); // get localhost instead of 127.0.0.1 + + File applicationDir = new File(baseDir, "/webapps/ROOT"); + applicationDir.mkdirs(); + + Context servletContext = + tomcatServer.addWebapp(getContextPath(), applicationDir.getAbsolutePath()); + // Speed up startup by disabling jar scanning: + servletContext.getJarScanner().setJarScanFilter((jarScanType, jarName) -> false); + + setupServlets(servletContext); + + ((StandardHost) tomcatServer.getHost()) + .setErrorReportValveClass(ErrorHandlerValve.class.getName()); + tomcatServer.getHost().getPipeline().addValve(accessLogValue); + + StandardEngine engine = (StandardEngine) tomcatServer.getServer().findService("Tomcat").getContainer(); + Container container = engine.findChild(engine.getDefaultHost()); + StandardContext context = (StandardContext) container.findChild(getContextPath()); + + FilterDef filter1definition = new FilterDef(); + filter1definition.setFilterName(OpenTelemetryServletFilter.class.getSimpleName()); + filter1definition.setFilterClass(OpenTelemetryServletFilter.class.getName()); + context.addFilterDef(filter1definition); + + FilterMap filter1mapping = new FilterMap(); + filter1mapping.setFilterName(OpenTelemetryServletFilter.class.getSimpleName()); + filter1mapping.addURLPattern("/*"); + context.addFilterMap(filter1mapping); + + tomcatServer.start(); + + return tomcatServer; + } + + @BeforeEach + void setUp() { + accessLogValue.getLoggedIds().clear(); + testing().clearAllExportedData(); + } + + @Override + public void stopServer(Tomcat server) throws LifecycleException { + // requires --add-opens=java.base/java.util=ALL-UNNAMED on newer JVMs. + server.stop(); + server.destroy(); + } + + @Override + public void addServlet(Context servletContext, String path, Class servlet) + throws Exception { + String name = UUID.randomUUID().toString(); + Tomcat.addServlet(servletContext, name, servlet.getConstructor().newInstance()); + servletContext.addServletMappingDecoded(path, name); + } + + @ParameterizedTest + @CsvSource({"1", "4"}) + void accessLogHasIdsForCountRequests(int count) { + AggregatedHttpRequest request = request(ACCESS_LOG_SUCCESS, "GET"); + + IntStream.range(0, count) + .mapToObj(i -> client.execute(request).aggregate().join()) + .forEach( + response -> { + assertThat(response.status().code()).isEqualTo(ACCESS_LOG_SUCCESS.getStatus()); + assertThat(response.contentUtf8()).isEqualTo(ACCESS_LOG_SUCCESS.getBody()); + }); + + accessLogValue.waitForLoggedIds(count); + assertThat(accessLogValue.getLoggedIds().size()).isEqualTo(count); + List loggedTraces = + accessLogValue.getLoggedIds().stream().map(Map.Entry::getKey).collect(Collectors.toList()); + List loggedSpans = + accessLogValue.getLoggedIds().stream() + .map(Map.Entry::getValue) + .collect(Collectors.toList()); + + testing() + .waitAndAssertTraces( + IntStream.range(0, count) + .mapToObj( + i -> + (Consumer) + trace -> { + trace.hasSpansSatisfyingExactly( + span -> + assertServerSpan( + span, "GET", ACCESS_LOG_SUCCESS, SUCCESS.getStatus()), + span -> assertControllerSpan(span, null)); + SpanData span = trace.getSpan(0); + assertThat(loggedTraces).contains(span.getTraceId()); + assertThat(loggedSpans).contains(span.getSpanId()); + }) + .collect(Collectors.toList())); + } + + @Test + void accessLogHasIdsForErrorRequest() { + Assumptions.assumeTrue(testError()); + + AggregatedHttpRequest request = request(ACCESS_LOG_ERROR, "GET"); + AggregatedHttpResponse response = client.execute(request).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(ACCESS_LOG_ERROR.getStatus()); + assertThat(response.contentUtf8()).isEqualTo(ACCESS_LOG_ERROR.getBody()); + + List> spanDataAsserts = new ArrayList<>(); + spanDataAsserts.add( + (span, trace) -> assertServerSpan(span, "GET", ACCESS_LOG_ERROR, ERROR.getStatus())); + spanDataAsserts.add((span, trace) -> assertControllerSpan(span, null)); + if (errorEndpointUsesSendError()) { + spanDataAsserts.add( + (span, trace) -> + span.satisfies(s -> assertThat(s.getName()).matches(".*\\.sendError")) + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(1))); + } + + accessLogValue.waitForLoggedIds(1); + testing() + .waitAndAssertTraces( + trace -> { + trace.hasSpansSatisfyingExactly( + spanDataAsserts.stream() + .map(e -> (Consumer) span -> e.accept(span, trace)) + .collect(Collectors.toList())); + SpanData span = trace.getSpan(0); + Map.Entry entry = accessLogValue.getLoggedIds().get(0); + assertThat(entry.getKey()).isEqualTo(span.getTraceId()); + assertThat(entry.getValue()).isEqualTo(span.getSpanId()); + }); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java new file mode 100644 index 000000000000..bbcf1374133c --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TomcatServlet3Test; + +abstract class TomcatDispatchTest extends TomcatServlet3Test { + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setContextPath(getContextPath() + "/dispatch"); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java new file mode 100644 index 000000000000..05570ecba0ec --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import javax.servlet.Servlet; +import org.apache.catalina.Context; + +class TomcatServlet3DispatchAsyncTest extends TomcatDispatchTest { + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setVerifyServerSpanEndTime(false); + } + + @Override + public Class servlet() { + return TestServlet3.Async.class; + } + + @Override + protected void setupServlets(Context context) throws Exception { + super.setupServlets(context); + + addServlet(context, "/dispatch" + SUCCESS.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + QUERY_PARAM.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + ERROR.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + AUTH_REQUIRED.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + CAPTURE_HEADERS.getPath(), TestServlet3.DispatchAsync.class); + addServlet( + context, "/dispatch" + CAPTURE_PARAMETERS.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + INDEXED_CHILD.getPath(), TestServlet3.DispatchAsync.class); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), TestServlet3.DispatchAsync.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive.class); + } + + @Override + public boolean errorEndpointUsesSendError() { + return false; + } + + @Override + protected boolean assertParentOnRedirect() { + return false; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java new file mode 100644 index 000000000000..7c55fb37ba4a --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import javax.servlet.Servlet; +import org.apache.catalina.Context; + +class TomcatServlet3DispatchImmediateTest extends TomcatDispatchTest { + + @Override + public Class servlet() { + return TestServlet3.Sync.class; + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestNotFound(false); + } + + @Override + protected void setupServlets(Context context) throws Exception { + super.setupServlets(context); + + addServlet(context, "/dispatch" + SUCCESS.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + QUERY_PARAM.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + ERROR.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + AUTH_REQUIRED.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + CAPTURE_HEADERS.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + CAPTURE_PARAMETERS.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + INDEXED_CHILD.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java new file mode 100644 index 000000000000..dc6fcba0215b --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.RequestDispatcherServlet; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import javax.servlet.Servlet; +import org.apache.catalina.Context; + +class TomcatServlet3ForwardTest extends TomcatDispatchTest { + + @Override + public Class servlet() { + return TestServlet3.Sync.class; // dispatch to sync servlet + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestNotFound(false); + } + + @Override + protected void setupServlets(Context context) throws Exception { + super.setupServlets(context); + + addServlet(context, "/dispatch" + SUCCESS.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + QUERY_PARAM.getPath(), RequestDispatcherServlet.Forward.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), RequestDispatcherServlet.Forward.class); + addServlet(context, "/dispatch" + ERROR.getPath(), RequestDispatcherServlet.Forward.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + AUTH_REQUIRED.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + CAPTURE_HEADERS.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, + "/dispatch" + CAPTURE_PARAMETERS.getPath(), + RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + INDEXED_CHILD.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + RequestDispatcherServlet.Forward.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java new file mode 100644 index 000000000000..7ac6c895c408 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.RequestDispatcherServlet; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import javax.servlet.Servlet; +import org.apache.catalina.Context; + +class TomcatServlet3IncludeTest extends TomcatDispatchTest { + + @Override + public Class servlet() { + return TestServlet3.Sync.class; // dispatch to sync servlet + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestNotFound(false); + options.setTestRedirect(false); + options.setTestCaptureHttpHeaders(false); + options.setTestError(false); + } + + @Override + protected void setupServlets(Context context) throws Exception { + super.setupServlets(context); + + addServlet(context, "/dispatch" + SUCCESS.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + QUERY_PARAM.getPath(), RequestDispatcherServlet.Include.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), RequestDispatcherServlet.Include.class); + addServlet(context, "/dispatch" + ERROR.getPath(), RequestDispatcherServlet.Include.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + AUTH_REQUIRED.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, + "/dispatch" + CAPTURE_PARAMETERS.getPath(), + RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + INDEXED_CHILD.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + RequestDispatcherServlet.Include.class); + } +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRunnableWrapper.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRunnableWrapper.java index 31aafe5a6214..b22b678e2d1d 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRunnableWrapper.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRunnableWrapper.java @@ -6,6 +6,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet; import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; public class AsyncRunnableWrapper implements Runnable { private final ServletHelper helper; @@ -27,7 +28,7 @@ public static Runnable wrap(ServletHelper helper, Runnable @Override public void run() { - try { + try(Scope scope = context.makeCurrent()) { runnable.run(); } catch (Throwable throwable) { helper.recordAsyncException(context, throwable); From 4f0eb99f3e2d26ea3b260e734e161173183f1505 Mon Sep 17 00:00:00 2001 From: Tyler Benson <734411+tylerbenson@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:11:55 -0500 Subject: [PATCH 03/10] fix package name --- .../servlet/v3_0/OpenTelemetryServletFilter.java | 6 ++++-- .../instrumentation/servlet/v3_0/OtelAsyncContext.java | 2 +- .../servlet/v3_0/OtelHttpServletRequest.java | 2 +- .../servlet/v3_0/OtelHttpServletResponse.java | 3 ++- .../instrumentation/servlet/v3_0/AbstractServlet3Test.java | 2 +- .../servlet/v3_0/RequestDispatcherServlet.java | 2 +- .../servlet/v3_0/TestAgentHttpResponseCustomizer.java | 2 +- .../instrumentation/servlet/v3_0/TestServlet3.java | 7 ++++--- .../servlet/v3_0/jetty/JettyServlet3AsyncTest.java | 4 ++-- .../servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java | 4 ++-- .../servlet/v3_0/jetty/JettyServlet3SyncTest.java | 4 ++-- .../servlet/v3_0/jetty/JettyServlet3Test.java | 6 +++--- .../servlet/v3_0/jetty/dispatch/JettyDispatchTest.java | 4 ++-- .../jetty/dispatch/JettyServlet3DispatchAsyncTest.java | 4 ++-- .../jetty/dispatch/JettyServlet3DispatchImmediateTest.java | 4 ++-- .../v3_0/jetty/dispatch/JettyServlet3ForwardTest.java | 6 +++--- .../v3_0/jetty/dispatch/JettyServlet3IncludeTest.java | 6 +++--- .../servlet/v3_0/tomcat/ErrorHandlerValve.java | 2 +- .../servlet/v3_0/tomcat/TestAccessLogValve.java | 2 +- .../servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java | 4 ++-- .../servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java | 4 ++-- .../servlet/v3_0/tomcat/TomcatServlet3SyncTest.java | 4 ++-- .../servlet/v3_0/tomcat/TomcatServlet3Test.java | 6 +++--- .../servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java | 4 ++-- .../tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java | 4 ++-- .../dispatch/TomcatServlet3DispatchImmediateTest.java | 4 ++-- .../v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java | 6 +++--- .../v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java | 6 +++--- 28 files changed, 59 insertions(+), 55 deletions(-) rename instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java (87%) rename instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/OtelAsyncContext.java (96%) rename instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/OtelHttpServletRequest.java (95%) rename instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/OtelHttpServletResponse.java (92%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/AbstractServlet3Test.java (98%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/RequestDispatcherServlet.java (96%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java (93%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/TestServlet3.java (97%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java (93%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java (65%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java (64%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java (94%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java (70%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java (94%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java (94%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java (91%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java (91%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java (92%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java (96%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java (92%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java (65%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java (65%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java (97%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java (70%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java (94%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java (94%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java (91%) rename instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/{javaagent => }/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java (91%) diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java similarity index 87% rename from instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java rename to instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java index 8878171638fe..3f46f58b6d55 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java @@ -1,8 +1,10 @@ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +package io.opentelemetry.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.FILTER_MAPPING_RESOLVER; import io.opentelemetry.javaagent.bootstrap.CallDepth; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3FilterMappingResolverFactory; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3RequestAdviceScope; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -24,7 +26,7 @@ public class OpenTelemetryServletFilter implements Filter { @Override - public void init(FilterConfig filterConfig) throws ServletException { + public void init(FilterConfig filterConfig) { FILTER_MAPPING_RESOLVER.set(this, new Servlet3FilterMappingResolverFactory(filterConfig)); } diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelAsyncContext.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelAsyncContext.java similarity index 96% rename from instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelAsyncContext.java rename to instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelAsyncContext.java index d9cc4b891eed..f9c64440cf87 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelAsyncContext.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelAsyncContext.java @@ -1,4 +1,4 @@ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +package io.opentelemetry.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletRequest.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletRequest.java similarity index 95% rename from instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletRequest.java rename to instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletRequest.java index 1c77aa02039f..30328d66e372 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletRequest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletRequest.java @@ -1,4 +1,4 @@ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +package io.opentelemetry.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletResponse.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletResponse.java similarity index 92% rename from instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletResponse.java rename to instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletResponse.java index fbeb6119f226..69326f31a7a9 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/OtelHttpServletResponse.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletResponse.java @@ -1,7 +1,8 @@ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +package io.opentelemetry.instrumentation.servlet.v3_0; import io.opentelemetry.javaagent.bootstrap.CallDepth; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3ResponseAdviceScope; import java.io.IOException; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/AbstractServlet3Test.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/AbstractServlet3Test.java similarity index 98% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/AbstractServlet3Test.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/AbstractServlet3Test.java index 0c792bf7c92b..610393b94d13 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/AbstractServlet3Test.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/AbstractServlet3Test.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +package io.opentelemetry.instrumentation.servlet.v3_0; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/RequestDispatcherServlet.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/RequestDispatcherServlet.java similarity index 96% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/RequestDispatcherServlet.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/RequestDispatcherServlet.java index 3413a0b4e16c..6d93f53d0f8e 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/RequestDispatcherServlet.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/RequestDispatcherServlet.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +package io.opentelemetry.instrumentation.servlet.v3_0; import java.io.IOException; import javax.servlet.RequestDispatcher; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java similarity index 93% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java index 4ec0e3706f35..f096f8df0066 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +package io.opentelemetry.instrumentation.servlet.v3_0; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestServlet3.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestServlet3.java similarity index 97% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestServlet3.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestServlet3.java index 293c1eaf861d..a8d2a01a6af3 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/TestServlet3.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestServlet3.java @@ -3,8 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +package io.opentelemetry.instrumentation.servlet.v3_0; +import static io.opentelemetry.instrumentation.servlet.v3_0.AbstractServlet3Test.HTML_PRINT_WRITER; +import static io.opentelemetry.instrumentation.servlet.v3_0.AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM; import static io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest.controller; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; @@ -14,12 +16,11 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test.HTML_PRINT_WRITER; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.SERVLET_MAPPING_RESOLVER; import io.opentelemetry.instrumentation.testing.GlobalTraceUtil; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3MappingResolverFactory; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java similarity index 93% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java index ab3276f60172..ebcc14d6b137 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java @@ -3,13 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; +package io.opentelemetry.instrumentation.servlet.v3_0.jetty; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest; import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; import io.opentelemetry.testing.internal.armeria.common.HttpMethod; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java similarity index 65% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java index 3345b10847b7..e448c644d738 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; +package io.opentelemetry.instrumentation.servlet.v3_0.jetty; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import javax.servlet.Servlet; class JettyServlet3FakeAsyncTest extends JettyServlet3Test { diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java similarity index 64% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java index b38451c89f5f..ab4fc7e85f88 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; +package io.opentelemetry.instrumentation.servlet.v3_0.jetty; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import javax.servlet.Servlet; class JettyServlet3SyncTest extends JettyServlet3Test { diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java similarity index 94% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java index 1a4ef1805a03..1217c5a18f14 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java @@ -3,18 +3,18 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; +package io.opentelemetry.instrumentation.servlet.v3_0.jetty; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.servlet.v3_0.AbstractServlet3Test; +import io.opentelemetry.instrumentation.servlet.v3_0.OpenTelemetryServletFilter; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.OpenTelemetryServletFilter; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.trace.data.SpanData; import java.io.IOException; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java similarity index 70% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java index a4432b127433..97aa7260e907 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; +package io.opentelemetry.instrumentation.servlet.v3_0.jetty.dispatch; +import io.opentelemetry.instrumentation.servlet.v3_0.jetty.JettyServlet3Test; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.JettyServlet3Test; abstract class JettyDispatchTest extends JettyServlet3Test { diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java similarity index 94% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java index 83c03f135f57..c6e0d86b9e11 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; +package io.opentelemetry.instrumentation.servlet.v3_0.jetty.dispatch; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; @@ -15,7 +15,7 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import javax.servlet.Servlet; import org.eclipse.jetty.servlet.ServletContextHandler; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java similarity index 94% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java index 5e8ba5457163..b547f1dc82f2 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; +package io.opentelemetry.instrumentation.servlet.v3_0.jetty.dispatch; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; @@ -15,7 +15,7 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import javax.servlet.Servlet; import org.eclipse.jetty.servlet.ServletContextHandler; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java similarity index 91% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java index ef83a24e85c5..901deb0b4a8e 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; +package io.opentelemetry.instrumentation.servlet.v3_0.jetty.dispatch; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; @@ -15,8 +15,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.RequestDispatcherServlet; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import io.opentelemetry.instrumentation.servlet.v3_0.RequestDispatcherServlet; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import javax.servlet.Servlet; import org.eclipse.jetty.servlet.ServletContextHandler; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java similarity index 91% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java index 724c16475613..365563c0c8e8 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; +package io.opentelemetry.instrumentation.servlet.v3_0.jetty.dispatch; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; @@ -14,9 +14,9 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import io.opentelemetry.instrumentation.servlet.v3_0.RequestDispatcherServlet; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.RequestDispatcherServlet; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; import javax.servlet.Servlet; import org.eclipse.jetty.servlet.ServletContextHandler; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java similarity index 92% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java index e2266081cf08..d26efccaf8aa 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; +package io.opentelemetry.instrumentation.servlet.v3_0.tomcat; import java.io.IOException; import org.apache.catalina.connector.Request; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java similarity index 96% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java index 1be5cbe759a7..d6292eb6a846 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; +package io.opentelemetry.instrumentation.servlet.v3_0.tomcat; import java.io.IOException; import java.util.AbstractMap; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java similarity index 92% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java index 8c07ba131ddd..b5ab735e8715 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java @@ -3,13 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; +package io.opentelemetry.instrumentation.servlet.v3_0.tomcat; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest; import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; import io.opentelemetry.testing.internal.armeria.common.HttpMethod; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java similarity index 65% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java index 831e7360415f..7b14184cce75 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; +package io.opentelemetry.instrumentation.servlet.v3_0.tomcat; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import javax.servlet.Servlet; class TomcatServlet3FakeAsyncTest extends TomcatServlet3Test { diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java similarity index 65% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java index 6b0a20e52860..2c16d5205a87 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; +package io.opentelemetry.instrumentation.servlet.v3_0.tomcat; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import javax.servlet.Servlet; class TomcatServlet3SyncTest extends TomcatServlet3Test { diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java similarity index 97% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java index 6774b3dde7ad..3a8c586b0591 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; +package io.opentelemetry.instrumentation.servlet.v3_0.tomcat; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; @@ -11,12 +11,12 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.servlet.v3_0.AbstractServlet3Test; +import io.opentelemetry.instrumentation.servlet.v3_0.OpenTelemetryServletFilter; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.OpenTelemetryServletFilter; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.testing.assertj.TraceAssert; import io.opentelemetry.sdk.trace.data.SpanData; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java similarity index 70% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java index bbcf1374133c..adf599f19a29 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; +package io.opentelemetry.instrumentation.servlet.v3_0.tomcat.dispatch; +import io.opentelemetry.instrumentation.servlet.v3_0.tomcat.TomcatServlet3Test; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TomcatServlet3Test; abstract class TomcatDispatchTest extends TomcatServlet3Test { diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java similarity index 94% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java index 05570ecba0ec..ca9a737a9f05 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; +package io.opentelemetry.instrumentation.servlet.v3_0.tomcat.dispatch; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; @@ -15,8 +15,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; import javax.servlet.Servlet; import org.apache.catalina.Context; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java similarity index 94% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java index 7c55fb37ba4a..7136887ec70f 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; +package io.opentelemetry.instrumentation.servlet.v3_0.tomcat.dispatch; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; @@ -15,8 +15,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; import javax.servlet.Servlet; import org.apache.catalina.Context; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java similarity index 91% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java index dc6fcba0215b..8e76458d959b 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; +package io.opentelemetry.instrumentation.servlet.v3_0.tomcat.dispatch; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; @@ -15,9 +15,9 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import io.opentelemetry.instrumentation.servlet.v3_0.RequestDispatcherServlet; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.RequestDispatcherServlet; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; import javax.servlet.Servlet; import org.apache.catalina.Context; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java similarity index 91% rename from instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java rename to instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java index 7ac6c895c408..acf4e0569b5d 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; +package io.opentelemetry.instrumentation.servlet.v3_0.tomcat.dispatch; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; @@ -14,9 +14,9 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import io.opentelemetry.instrumentation.servlet.v3_0.RequestDispatcherServlet; +import io.opentelemetry.instrumentation.servlet.v3_0.TestServlet3; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.RequestDispatcherServlet; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.TestServlet3; import javax.servlet.Servlet; import org.apache.catalina.Context; From dea3d1cf8769b32fe489720b3387bc8dff42a8dc Mon Sep 17 00:00:00 2001 From: Tyler Benson <734411+tylerbenson@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:14:13 -0500 Subject: [PATCH 04/10] spotlessApply --- .../servlet/v3_0/Servlet3FilterInitAdvice.java | 3 +-- .../instrumentation/servlet/v3_0/Servlet3InitAdvice.java | 3 +-- .../servlet/v3_0/Servlet3RequestAdviceScope.java | 5 +++++ .../servlet/v3_0/Servlet3ResponseAdviceScope.java | 5 +++++ .../servlet/servlet-3.0/library/build.gradle.kts | 1 - .../servlet/v3_0/OpenTelemetryServletFilter.java | 7 ++++++- .../instrumentation/servlet/v3_0/OtelAsyncContext.java | 5 +++++ .../servlet/v3_0/OtelHttpServletRequest.java | 5 +++++ .../servlet/v3_0/OtelHttpServletResponse.java | 8 ++++++-- .../servlet/v3_0/jetty/JettyServlet3Test.java | 3 ++- .../servlet/v3_0/tomcat/TomcatServlet3Test.java | 3 ++- .../instrumentation/servlet/AsyncRunnableWrapper.java | 2 +- 12 files changed, 39 insertions(+), 11 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3FilterInitAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3FilterInitAdvice.java index 98eeb7d5444d..3343b4238a5b 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3FilterInitAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3FilterInitAdvice.java @@ -20,7 +20,6 @@ public static void filterInit( if (filterConfig == null) { return; } - FILTER_MAPPING_RESOLVER.set( - filter, new Servlet3FilterMappingResolverFactory(filterConfig)); + FILTER_MAPPING_RESOLVER.set(filter, new Servlet3FilterMappingResolverFactory(filterConfig)); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InitAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InitAdvice.java index da94976b715b..57fa29af91ba 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InitAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InitAdvice.java @@ -20,7 +20,6 @@ public static void servletInit( if (servletConfig == null) { return; } - SERVLET_MAPPING_RESOLVER.set( - servlet, new Servlet3MappingResolverFactory(servletConfig)); + SERVLET_MAPPING_RESOLVER.set(servlet, new Servlet3MappingResolverFactory(servletConfig)); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3RequestAdviceScope.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3RequestAdviceScope.java index c439bd6bc768..c50beae8ec63 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3RequestAdviceScope.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3RequestAdviceScope.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseAdviceScope.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseAdviceScope.java index cdd06bb20e74..f5d893e16733 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseAdviceScope.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseAdviceScope.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.responseInstrumenter; diff --git a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts index eb74a6dc14d3..5027f854a755 100644 --- a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts +++ b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts @@ -19,7 +19,6 @@ dependencies { testLibrary("org.apache.tomcat.embed:tomcat-embed-jasper:8.0.41") } - tasks { withType().configureEach { // required on jdk17 to allow tomcat to shutdown properly. diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java index 3f46f58b6d55..0432932caae1 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.FILTER_MAPPING_RESOLVER; @@ -22,7 +27,7 @@ * will miss anything that happens earlier in the filter stack or problems handled directly by the * app server. For this reason, Java Agent instrumentation is preferred when possible. */ - @WebFilter("/*") +@WebFilter("/*") public class OpenTelemetryServletFilter implements Filter { @Override diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelAsyncContext.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelAsyncContext.java index f9c64440cf87..be5b6e303d6e 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelAsyncContext.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelAsyncContext.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletRequest.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletRequest.java index 30328d66e372..f85e8309b054 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletRequest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletRequest.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletResponse.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletResponse.java index 69326f31a7a9..949bd9ceaeae 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletResponse.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletResponse.java @@ -1,5 +1,9 @@ -package io.opentelemetry.instrumentation.servlet.v3_0; +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package io.opentelemetry.instrumentation.servlet.v3_0; import io.opentelemetry.javaagent.bootstrap.CallDepth; import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3ResponseAdviceScope; @@ -9,7 +13,7 @@ /// Wrapper around [HttpServletResponse]. public class OtelHttpServletResponse extends HttpServletResponseWrapper { - + public OtelHttpServletResponse(HttpServletResponse response) { super(response); } diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java index 1217c5a18f14..bd0f1c70e87c 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java @@ -94,7 +94,8 @@ protected void handleErrorPage( writer.write(th != null ? th.getMessage() : message); } }); - servletContext.addFilter(OpenTelemetryServletFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); + servletContext.addFilter( + OpenTelemetryServletFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); setupServlets(servletContext); jettyServer.setHandler(servletContext); diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java index 3a8c586b0591..f63e54b9920e 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java @@ -125,7 +125,8 @@ protected Tomcat setupServer() throws Exception { .setErrorReportValveClass(ErrorHandlerValve.class.getName()); tomcatServer.getHost().getPipeline().addValve(accessLogValue); - StandardEngine engine = (StandardEngine) tomcatServer.getServer().findService("Tomcat").getContainer(); + StandardEngine engine = + (StandardEngine) tomcatServer.getServer().findService("Tomcat").getContainer(); Container container = engine.findChild(engine.getDefaultHost()); StandardContext context = (StandardContext) container.findChild(getContextPath()); diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRunnableWrapper.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRunnableWrapper.java index b22b678e2d1d..f101a8112c53 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRunnableWrapper.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/AsyncRunnableWrapper.java @@ -28,7 +28,7 @@ public static Runnable wrap(ServletHelper helper, Runnable @Override public void run() { - try(Scope scope = context.makeCurrent()) { + try (Scope scope = context.makeCurrent()) { runnable.run(); } catch (Throwable throwable) { helper.recordAsyncException(context, throwable); From a3e548bed6c2f40c8070dbd8c484a43cd24a24ba Mon Sep 17 00:00:00 2001 From: Tyler Benson <734411+tylerbenson@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:15:19 -0500 Subject: [PATCH 05/10] ./gradlew generateFossaConfiguration --- .fossa.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.fossa.yml b/.fossa.yml index 161ceb50792f..53d88b206ce8 100644 --- a/.fossa.yml +++ b/.fossa.yml @@ -913,6 +913,9 @@ targets: - type: gradle path: ./ target: ':instrumentation:servlet:servlet-3.0:javaagent' + - type: gradle + path: ./ + target: ':instrumentation:servlet:servlet-3.0:library' - type: gradle path: ./ target: ':instrumentation:servlet:servlet-5.0:javaagent' From 2e0d691746f706b7883a8d1e0ba39101f1b2bbda Mon Sep 17 00:00:00 2001 From: Tyler Benson <734411+tylerbenson@users.noreply.github.com> Date: Wed, 5 Nov 2025 18:03:09 -0500 Subject: [PATCH 06/10] copy/embed all necessary classes. --- .../servlet/v3_0/Servlet3Advice.java | 75 +++++++- .../v3_0/Servlet3ResponseSendAdvice.java | 50 ++++- .../servlet-3.0/library/build.gradle.kts | 11 +- .../v3_0/OpenTelemetryServletFilter.java | 8 +- .../servlet/v3_0/OtelAsyncContext.java | 2 +- .../servlet/v3_0/OtelHttpServletRequest.java | 2 +- .../servlet/v3_0/OtelHttpServletResponse.java | 4 +- .../v3_0/copied/AgentCommonConfig.java | 22 +++ .../copied/AgentInstrumentationConfig.java | 47 +++++ .../servlet/v3_0/copied/AppServerBridge.java | 133 +++++++++++++ .../AsyncRequestCompletionListener.java | 63 +++++++ .../v3_0/copied/AsyncRunnableWrapper.java | 38 ++++ .../v3_0/copied/BaseServletHelper.java | 175 ++++++++++++++++++ .../servlet/v3_0/copied/CallDepth.java | 56 ++++++ .../v3_0/copied/CallDepthThreadLocalMap.java | 30 +++ .../copied/EmptyInstrumentationConfig.java | 80 ++++++++ .../copied/HttpServerResponseCustomizer.java | 32 ++++ .../HttpServerResponseCustomizerHolder.java | 35 ++++ .../copied/HttpServerResponseMutator.java | 11 ++ .../HttpServletResponseAdviceHelper.java | 70 +++++++ .../JavaagentHttpServerInstrumenters.java | 55 ++++++ .../v3_0/copied/JavaxServletAccessor.java | 107 +++++++++++ .../servlet/v3_0/copied/MappingResolver.java | 153 +++++++++++++++ .../copied/ResponseInstrumenterFactory.java | 29 +++ .../servlet/v3_0/copied/Servlet3Accessor.java | 106 +++++++++++ .../Servlet3FilterMappingResolverFactory.java | 52 ++++++ .../copied}/Servlet3RequestAdviceScope.java | 8 +- .../copied}/Servlet3ResponseAdviceScope.java | 6 +- .../v3_0/copied/Servlet3Singletons.java | 62 +++++++ .../servlet/v3_0/copied/ServletAccessor.java | 70 +++++++ .../ServletAdditionalAttributesExtractor.java | 62 +++++++ .../v3_0/copied/ServletAsyncContext.java | 85 +++++++++ .../v3_0/copied/ServletAsyncListener.java | 14 ++ .../v3_0/copied/ServletContextPath.java | 73 ++++++++ .../copied/ServletErrorCauseExtractor.java | 24 +++ .../ServletFilterMappingResolverFactory.java | 60 ++++++ .../servlet/v3_0/copied/ServletHelper.java | 123 ++++++++++++ .../copied/ServletHttpAttributesGetter.java | 140 ++++++++++++++ .../copied/ServletInstrumenterBuilder.java | 98 ++++++++++ .../copied/ServletMappingResolverFactory.java | 59 ++++++ .../v3_0/copied/ServletRequestContext.java | 31 ++++ .../v3_0/copied/ServletRequestGetter.java | 33 ++++ .../ServletRequestParametersExtractor.java | 83 +++++++++ .../v3_0/copied/ServletResponseContext.java | 45 +++++ .../v3_0/copied/ServletSpanNameProvider.java | 34 ++++ .../servlet/v3_0/AbstractServlet3Test.java | 2 +- .../v3_0/Servlet3MappingResolverFactory.java | 36 ++++ .../v3_0/TestAgentHttpResponseCustomizer.java | 4 +- .../servlet/v3_0/TestServlet3.java | 3 +- 49 files changed, 2563 insertions(+), 38 deletions(-) create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AgentCommonConfig.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AgentInstrumentationConfig.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AppServerBridge.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AsyncRequestCompletionListener.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AsyncRunnableWrapper.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/BaseServletHelper.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/CallDepth.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/CallDepthThreadLocalMap.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/EmptyInstrumentationConfig.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServerResponseCustomizer.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServerResponseCustomizerHolder.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServerResponseMutator.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServletResponseAdviceHelper.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/JavaagentHttpServerInstrumenters.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/JavaxServletAccessor.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/MappingResolver.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ResponseInstrumenterFactory.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3Accessor.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3FilterMappingResolverFactory.java rename instrumentation/servlet/servlet-3.0/{javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0 => library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied}/Servlet3RequestAdviceScope.java (86%) rename instrumentation/servlet/servlet-3.0/{javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0 => library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied}/Servlet3ResponseAdviceScope.java (80%) create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3Singletons.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAccessor.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAdditionalAttributesExtractor.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAsyncContext.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAsyncListener.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletContextPath.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletErrorCauseExtractor.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletFilterMappingResolverFactory.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletHelper.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletHttpAttributesGetter.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletInstrumenterBuilder.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletMappingResolverFactory.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletRequestContext.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletRequestGetter.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletRequestParametersExtractor.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletResponseContext.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletSpanNameProvider.java create mode 100644 instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/Servlet3MappingResolverFactory.java 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 cb1cd66e3ecf..874bca299cb0 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 @@ -6,11 +6,18 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.bootstrap.CallDepth; +import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder; import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge; +import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Servlet3SnippetInjectingResponseWrapper; import javax.annotation.Nullable; +import javax.servlet.Servlet; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; @@ -20,8 +27,70 @@ import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument; import net.bytebuddy.implementation.bytecode.assign.Assigner; +@SuppressWarnings("unused") public class Servlet3Advice { + public static class AdviceScope { + private final CallDepth callDepth; + private final ServletRequestContext requestContext; + private final Context context; + private final Scope scope; + + public AdviceScope( + CallDepth callDepth, + HttpServletRequest request, + HttpServletResponse response, + Object servletOrFilter) { + this.callDepth = callDepth; + this.callDepth.getAndIncrement(); + + Context currentContext = Context.current(); + Context attachedContext = helper().getServerContext(request); + Context contextToUpdate; + + requestContext = new ServletRequestContext<>(request, servletOrFilter); + if (attachedContext == null && helper().shouldStart(currentContext, requestContext)) { + context = helper().start(currentContext, requestContext); + helper().setAsyncListenerResponse(context, response); + + contextToUpdate = context; + } else if (attachedContext != null + && helper().needsRescoping(currentContext, attachedContext)) { + // Given request already has a context associated with it. + // see the needsRescoping() javadoc for more explanation + contextToUpdate = attachedContext; + context = null; + } else { + // We are inside nested servlet/filter/app-server span, don't create new span + contextToUpdate = currentContext; + context = null; + } + + // Update context with info from current request to ensure that server span gets the best + // possible name. + // In case server span was created by app server instrumentations calling updateContext + // returns a new context that contains servlet context path that is used in other + // instrumentations for naming server span. + MappingResolver mappingResolver = Servlet3Singletons.getMappingResolver(servletOrFilter); + boolean servlet = servletOrFilter instanceof Servlet; + contextToUpdate = helper().updateContext(contextToUpdate, request, mappingResolver, servlet); + scope = contextToUpdate.makeCurrent(); + + if (context != null) { + // Only trigger response customizer once, so only if server span was created here + HttpServerResponseCustomizerHolder.getCustomizer() + .customize(contextToUpdate, response, Servlet3Accessor.INSTANCE); + } + } + + public void exit( + @Nullable Throwable throwable, HttpServletRequest request, HttpServletResponse response) { + + boolean topLevel = callDepth.decrementAndGet() == 0; + helper().end(requestContext, request, response, throwable, topLevel, context, scope); + } + } + @AssignReturned.ToArguments({ @ToArgument(value = 0, index = 1), @ToArgument(value = 1, index = 2) @@ -45,8 +114,8 @@ public static Object[] onEnter( response = new Servlet3SnippetInjectingResponseWrapper((HttpServletResponse) response, snippet); } - Servlet3RequestAdviceScope adviceScope = - new Servlet3RequestAdviceScope( + AdviceScope adviceScope = + new AdviceScope( CallDepth.forClass(AppServerBridge.getCallDepthKey()), (HttpServletRequest) request, (HttpServletResponse) response, @@ -60,7 +129,7 @@ public static void stopSpan( @Advice.Argument(1) ServletResponse response, @Advice.Thrown @Nullable Throwable throwable, @Advice.Enter Object[] enterResult) { - Servlet3RequestAdviceScope adviceScope = (Servlet3RequestAdviceScope) enterResult[0]; + AdviceScope adviceScope = (AdviceScope) enterResult[0]; if (adviceScope == null || !(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseSendAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseSendAdvice.java index ebb7921320dc..4e2441f4b4d0 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseSendAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseSendAdvice.java @@ -5,23 +5,67 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.responseInstrumenter; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; import io.opentelemetry.javaagent.bootstrap.CallDepth; +import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseAdviceHelper; +import javax.annotation.Nullable; import javax.servlet.http.HttpServletResponse; import net.bytebuddy.asm.Advice; @SuppressWarnings("unused") public class Servlet3ResponseSendAdvice { + public static class AdviceScope { + private final CallDepth callDepth; + private final ClassAndMethod classAndMethod; + private final Context context; + private final Scope scope; + + public AdviceScope(CallDepth callDepth, Class declaringClass, String methodName) { + this.callDepth = callDepth; + if (callDepth.getAndIncrement() > 0) { + this.classAndMethod = null; + this.context = null; + this.scope = null; + return; + } + HttpServletResponseAdviceHelper.StartResult result = + HttpServletResponseAdviceHelper.startSpan( + responseInstrumenter(), declaringClass, methodName); + if (result != null) { + classAndMethod = result.getClassAndMethod(); + context = result.getContext(); + scope = result.getScope(); + } else { + classAndMethod = null; + context = null; + scope = null; + } + } + + public void exit(@Nullable Throwable throwable) { + if (callDepth.decrementAndGet() > 0) { + return; + } + HttpServletResponseAdviceHelper.stopSpan( + responseInstrumenter(), throwable, context, scope, classAndMethod); + } + } + @Advice.OnMethodEnter(suppress = Throwable.class) - public static Servlet3ResponseAdviceScope start( + public static AdviceScope start( @Advice.Origin("#t") Class declaringClass, @Advice.Origin("#m") String methodName) { - return new Servlet3ResponseAdviceScope( + return new AdviceScope( CallDepth.forClass(HttpServletResponse.class), declaringClass, methodName); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void stopSpan( - @Advice.Thrown Throwable throwable, @Advice.Enter Servlet3ResponseAdviceScope adviceScope) { + @Advice.Thrown Throwable throwable, @Advice.Enter AdviceScope adviceScope) { adviceScope.exit(throwable); } } diff --git a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts index 5027f854a755..48273c54b02f 100644 --- a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts +++ b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts @@ -4,14 +4,7 @@ plugins { dependencies { library("javax.servlet:javax.servlet-api:3.0.1") - - // FIXME: These dependencies need to be shadowed into the library. - library(project(":instrumentation:servlet:servlet-3.0:javaagent")) - library(project(":instrumentation:servlet:servlet-common:javaagent")) - library(project(":instrumentation:servlet:servlet-common:bootstrap")) - library(project(":javaagent-extension-api")) - -// testImplementation(project(":testing:agent-exporter")) + library("io.opentelemetry.semconv:opentelemetry-semconv-incubating") testLibrary("org.eclipse.jetty:jetty-server:8.0.0.v20110901") testLibrary("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901") @@ -21,7 +14,7 @@ dependencies { tasks { withType().configureEach { - // required on jdk17 to allow tomcat to shutdown properly. + // required on jdk17+ to allow tomcat to shutdown properly. jvmArgs("--add-opens=java.base/java.util=ALL-UNNAMED") jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") } diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java index 0432932caae1..b854784b0805 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OpenTelemetryServletFilter.java @@ -5,11 +5,11 @@ package io.opentelemetry.instrumentation.servlet.v3_0; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.FILTER_MAPPING_RESOLVER; +import static io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3Singletons.FILTER_MAPPING_RESOLVER; -import io.opentelemetry.javaagent.bootstrap.CallDepth; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3FilterMappingResolverFactory; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3RequestAdviceScope; +import io.opentelemetry.instrumentation.servlet.v3_0.copied.CallDepth; +import io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3FilterMappingResolverFactory; +import io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3RequestAdviceScope; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelAsyncContext.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelAsyncContext.java index be5b6e303d6e..f9f09234ccd3 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelAsyncContext.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelAsyncContext.java @@ -5,7 +5,7 @@ package io.opentelemetry.instrumentation.servlet.v3_0; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; +import static io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3Singletons.helper; import javax.servlet.AsyncContext; import javax.servlet.AsyncListener; diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletRequest.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletRequest.java index f85e8309b054..6f2fd5d1897a 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletRequest.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletRequest.java @@ -5,7 +5,7 @@ package io.opentelemetry.instrumentation.servlet.v3_0; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; +import static io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3Singletons.helper; import io.opentelemetry.context.Context; import javax.servlet.AsyncContext; diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletResponse.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletResponse.java index 949bd9ceaeae..068b2c1f754c 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletResponse.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/OtelHttpServletResponse.java @@ -5,8 +5,8 @@ package io.opentelemetry.instrumentation.servlet.v3_0; -import io.opentelemetry.javaagent.bootstrap.CallDepth; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3ResponseAdviceScope; +import io.opentelemetry.instrumentation.servlet.v3_0.copied.CallDepth; +import io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3ResponseAdviceScope; import java.io.IOException; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AgentCommonConfig.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AgentCommonConfig.java new file mode 100644 index 000000000000..dfb5365a8700 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AgentCommonConfig.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.instrumentation.api.incubator.config.internal.CommonConfig; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class AgentCommonConfig { + private AgentCommonConfig() {} + + private static final CommonConfig instance = new CommonConfig(AgentInstrumentationConfig.get()); + + public static CommonConfig get() { + return instance; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AgentInstrumentationConfig.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AgentInstrumentationConfig.java new file mode 100644 index 000000000000..57f9cfedb4b5 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AgentInstrumentationConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import static java.util.Objects.requireNonNull; + +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import java.util.logging.Logger; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class AgentInstrumentationConfig { + private AgentInstrumentationConfig() {} + + private static final Logger logger = Logger.getLogger(AgentInstrumentationConfig.class.getName()); + + private static final InstrumentationConfig DEFAULT = new EmptyInstrumentationConfig(); + + // lazy initialized, so that javaagent can set it + private static volatile InstrumentationConfig instance = DEFAULT; + + /** + * Sets the instrumentation configuration singleton. This method is only supposed to be called + * once, during the agent initialization, just before {@link AgentInstrumentationConfig#get()} is + * used for the first time. + * + *

This method is internal and is hence not for public use. Its API is unstable and can change + * at any time. + */ + public static void internalInitializeConfig(InstrumentationConfig config) { + if (instance != DEFAULT) { + logger.warning("InstrumentationConfig#instance was already set earlier"); + return; + } + instance = requireNonNull(config); + } + + /** Returns the global instrumentation configuration. */ + public static InstrumentationConfig get() { + return instance; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AppServerBridge.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AppServerBridge.java new file mode 100644 index 000000000000..e27c33d55423 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AppServerBridge.java @@ -0,0 +1,133 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import javax.annotation.Nullable; + +/** + * Helper container for Context attributes for transferring certain information between servlet + * integration and app-server server handler integrations. + */ +public class AppServerBridge { + + private static final ContextKey CONTEXT_KEY = + ContextKey.named("opentelemetry-servlet-app-server-bridge"); + + private final boolean servletShouldRecordException; + private boolean captureServletAttributes; + private Throwable exception; + + private AppServerBridge(Builder builder) { + servletShouldRecordException = builder.recordException; + captureServletAttributes = builder.captureServletAttributes; + } + + /** + * 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; + } + + /** + * Test whether servlet attributes should be captured. This method will return true only on the + * first call with given context. + * + * @param context server context + * @return true when servlet attributes should be captured + */ + public static boolean captureServletAttributes(Context context) { + AppServerBridge appServerBridge = context.get(AppServerBridge.CONTEXT_KEY); + if (appServerBridge != null) { + boolean result = appServerBridge.captureServletAttributes; + appServerBridge.captureServletAttributes = false; + return result; + } + return false; + } + + /** + * Class used as key in CallDepthThreadLocalMap for counting servlet invocation depth in + * Servlet3Advice and Servlet2Advice. We can not use helper classes like Servlet3Advice and + * Servlet2Advice for determining call depth of server invocation because they can be injected + * into multiple class loaders. + * + * @return class used as a key in CallDepthThreadLocalMap for counting servlet invocation depth + */ + public static Class getCallDepthKey() { + class Key {} + + return Key.class; + } + + public static class Builder { + boolean recordException; + boolean captureServletAttributes; + + /** + * Use on servers where exceptions thrown during servlet invocation are not propagated to the + * method where server span is closed. Recorded exception can be retrieved by calling {@link + * #getException(Context)} + * + * @return this builder. + */ + @CanIgnoreReturnValue + public Builder recordException() { + recordException = true; + return this; + } + + /** + * Use on servers where server instrumentation is not based on servlet instrumentation. Setting + * this flag lets servlet instrumentation know that it should augment server span with servlet + * specific attributes. + * + * @return this builder. + */ + @CanIgnoreReturnValue + public Builder captureServletAttributes() { + captureServletAttributes = true; + return this; + } + + /** + * Attach AppServerBridge to context. + * + * @param context server context + * @return new context with AppServerBridge attached. + */ + public Context init(Context context) { + Context result = context.with(AppServerBridge.CONTEXT_KEY, new AppServerBridge(this)); + result = ServletAsyncContext.init(result); + return result; + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AsyncRequestCompletionListener.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AsyncRequestCompletionListener.java new file mode 100644 index 000000000000..eee17d055527 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AsyncRequestCompletionListener.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +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) { + // The context passed into this method may contain other spans besides the server span. To end + // the server span we get the context that set at the start of the request with + // ServletHelper#setAsyncListenerResponse that contains just the server span. + Context serverSpanContext = servletHelper.getAsyncListenerContext(context); + this.servletHelper = servletHelper; + this.instrumenter = instrumenter; + this.requestContext = requestContext; + this.context = serverSpanContext != null ? serverSpanContext : context; + } + + @Override + public void onComplete(RESPONSE response) { + if (responseHandled.compareAndSet(false, true)) { + ServletResponseContext responseContext = new ServletResponseContext<>(response); + Throwable throwable = servletHelper.getAsyncException(context); + instrumenter.end(context, requestContext, responseContext, throwable); + } + } + + @Override + public void onTimeout(long timeout) { + if (responseHandled.compareAndSet(false, true)) { + RESPONSE response = servletHelper.getAsyncListenerResponse(context); + ServletResponseContext responseContext = new ServletResponseContext<>(response); + responseContext.setTimeout(timeout); + Throwable throwable = servletHelper.getAsyncException(context); + instrumenter.end(context, requestContext, responseContext, throwable); + } + } + + @Override + public void onError(Throwable throwable, RESPONSE response) { + if (responseHandled.compareAndSet(false, true)) { + ServletResponseContext responseContext = new ServletResponseContext<>(response); + instrumenter.end(context, requestContext, responseContext, throwable); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AsyncRunnableWrapper.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AsyncRunnableWrapper.java new file mode 100644 index 000000000000..99ab7e354f16 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/AsyncRunnableWrapper.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; + +public class AsyncRunnableWrapper implements Runnable { + private final ServletHelper helper; + private final Runnable runnable; + private final Context context; + + private AsyncRunnableWrapper(ServletHelper helper, Runnable runnable) { + this.helper = helper; + this.runnable = runnable; + this.context = Context.current(); + } + + public static Runnable wrap(ServletHelper helper, Runnable runnable) { + if (runnable == null || runnable instanceof AsyncRunnableWrapper) { + return runnable; + } + return new AsyncRunnableWrapper<>(helper, runnable); + } + + @Override + public void run() { + try (Scope scope = context.makeCurrent()) { + runnable.run(); + } catch (Throwable throwable) { + helper.recordAsyncException(context, throwable); + throw throwable; + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/BaseServletHelper.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/BaseServletHelper.java new file mode 100644 index 000000000000..02bc791e5e21 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/BaseServletHelper.java @@ -0,0 +1,175 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import static io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource.SERVER; +import static io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource.SERVER_FILTER; + +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.instrumenter.LocalRootSpan; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute; +import io.opentelemetry.semconv.incubating.EnduserIncubatingAttributes; +import java.security.Principal; +import java.util.function.Function; + +@SuppressWarnings("deprecation") // using deprecated semconv +public abstract class BaseServletHelper { + protected final Instrumenter, ServletResponseContext> + instrumenter; + protected final ServletAccessor accessor; + private final ServletSpanNameProvider spanNameProvider; + private final Function contextPathExtractor; + private final ServletRequestParametersExtractor parameterExtractor; + + protected BaseServletHelper( + Instrumenter, ServletResponseContext> instrumenter, + ServletAccessor accessor) { + this.instrumenter = instrumenter; + this.accessor = accessor; + this.spanNameProvider = new ServletSpanNameProvider<>(accessor); + this.contextPathExtractor = accessor::getRequestContextPath; + this.parameterExtractor = + ServletRequestParametersExtractor.enabled() + ? new ServletRequestParametersExtractor<>(accessor) + : null; + } + + public boolean shouldStart(Context parentContext, ServletRequestContext requestContext) { + return instrumenter.shouldStart(parentContext, requestContext); + } + + public Context start(Context parentContext, ServletRequestContext requestContext) { + 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 = addServletContextPath(context, request); + context = addAsyncContext(context); + + attachServerContext(context, request); + + return context; + } + + protected Context addServletContextPath(Context context, REQUEST request) { + return ServletContextPath.init(context, contextPathExtractor, request); + } + + protected Context addAsyncContext(Context context) { + return ServletAsyncContext.init(context); + } + + public Context getServerContext(REQUEST request) { + Object context = accessor.getRequestAttribute(request, ServletHelper.CONTEXT_ATTRIBUTE); + return context instanceof Context ? (Context) context : null; + } + + private void attachServerContext(Context context, REQUEST request) { + accessor.setRequestAttribute(request, ServletHelper.CONTEXT_ATTRIBUTE, context); + } + + public void recordException(Context context, Throwable throwable) { + AppServerBridge.recordException(context, throwable); + } + + public Context updateContext( + Context context, REQUEST request, MappingResolver mappingResolver, boolean servlet) { + Context result = addServletContextPath(context, request); + result = addAsyncContext(result); + + if (mappingResolver != null) { + HttpServerRoute.update( + result, servlet ? SERVER : SERVER_FILTER, spanNameProvider, mappingResolver, request); + } + + return result; + } + + /** + * Capture servlet specific span attributes when SERVER span is not create by servlet + * instrumentation. + */ + public void captureServletAttributes(Context context, REQUEST request) { + if (!AppServerBridge.captureServletAttributes(context)) { + return; + } + Span serverSpan = LocalRootSpan.fromContextOrNull(context); + if (serverSpan == null) { + return; + } + + captureRequestParameters(serverSpan, request); + captureEnduserId(serverSpan, request); + } + + /** + * Capture servlet request parameters as span attributes when SERVER span is not create by servlet + * instrumentation. + * + *

When SERVER span is created by servlet instrumentation we register {@link + * ServletRequestParametersExtractor} as an attribute extractor. When SERVER span is not created + * by servlet instrumentation we call this method on exit from the last servlet or filter. + */ + private void captureRequestParameters(Span serverSpan, REQUEST request) { + if (parameterExtractor == null) { + return; + } + + parameterExtractor.setAttributes(request, serverSpan::setAttribute); + } + + /** + * Capture {@link EnduserIncubatingAttributes#ENDUSER_ID} as span attributes when SERVER span is + * not create by servlet instrumentation. + * + *

When SERVER span is created by servlet instrumentation we register {@link + * ServletAdditionalAttributesExtractor} as an attribute extractor. When SERVER span is not + * created by servlet instrumentation we call this method on exit from the last servlet or filter. + */ + private void captureEnduserId(Span serverSpan, REQUEST request) { + if (!AgentCommonConfig.get().getEnduserConfig().isIdEnabled()) { + return; + } + + Principal principal = accessor.getRequestUserPrincipal(request); + if (principal != null) { + String name = principal.getName(); + if (name != null) { + serverSpan.setAttribute(EnduserIncubatingAttributes.ENDUSER_ID, name); + } + } + } + + /* + 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-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/CallDepth.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/CallDepth.java new file mode 100644 index 000000000000..03565046cc67 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/CallDepth.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +/** + * A utility to track nested calls in an instrumentation. + * + *

For example, this can be used to track nested calls to {@code super()} in constructors by + * calling {@link #getAndIncrement()} at the beginning of each constructor. + * + *

This works the following way: when you enter some method that you want to track, you call + * {@link #getAndIncrement()} method. If returned number is larger than 0, then you have already + * been in this method and are in recursive call now. When you then leave the method, you call + * {@link #decrementAndGet()} method. If returned number is larger than 0, then you have already + * been in this method and are in recursive call now. + * + *

In short, the semantic of both methods is the same: they will return value 0 if and only if + * current method invocation is the first one for the current call stack. + */ +public final class CallDepth { + + private int depth; + + CallDepth() { + this.depth = 0; + } + + /** + * Return the current call depth for a given class (not method; we want to be able to track calls + * between different methods in a class). + * + *

The returned instance is unique per given class and per thread. + */ + public static CallDepth forClass(Class cls) { + return CallDepthThreadLocalMap.getCallDepth(cls); + } + + /** + * Increment the current call depth and return the previous value. This method will always return + * 0 if it's the first (outermost) call. + */ + public int getAndIncrement() { + return this.depth++; + } + + /** + * Decrement the current call depth and return the current value. This method will always return 0 + * if it's the last (outermost) call. + */ + public int decrementAndGet() { + return --this.depth; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/CallDepthThreadLocalMap.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/CallDepthThreadLocalMap.java new file mode 100644 index 000000000000..1b4bdb7a10f5 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/CallDepthThreadLocalMap.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +final class CallDepthThreadLocalMap { + + private static final ClassValue TLS = + new ClassValue() { + @Override + protected ThreadLocalDepth computeValue(Class type) { + return new ThreadLocalDepth(); + } + }; + + static CallDepth getCallDepth(Class k) { + return TLS.get(k).get(); + } + + private static final class ThreadLocalDepth extends ThreadLocal { + @Override + protected CallDepth initialValue() { + return new CallDepth(); + } + } + + private CallDepthThreadLocalMap() {} +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/EmptyInstrumentationConfig.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/EmptyInstrumentationConfig.java new file mode 100644 index 000000000000..6dd67a05120d --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/EmptyInstrumentationConfig.java @@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.api.incubator.config.ConfigProvider; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +final class EmptyInstrumentationConfig implements InstrumentationConfig { + + @Nullable + @Override + public String getString(String name) { + return null; + } + + @Override + public String getString(String name, String defaultValue) { + return defaultValue; + } + + @Override + public boolean getBoolean(String name, boolean defaultValue) { + return defaultValue; + } + + @Override + public int getInt(String name, int defaultValue) { + return defaultValue; + } + + @Override + public long getLong(String name, long defaultValue) { + return defaultValue; + } + + @Override + public double getDouble(String name, double defaultValue) { + return defaultValue; + } + + @Override + public Duration getDuration(String name, Duration defaultValue) { + return defaultValue; + } + + @Override + public List getList(String name, List defaultValue) { + return defaultValue; + } + + @Override + public Map getMap(String name, Map defaultValue) { + return defaultValue; + } + + @Override + public boolean isDeclarative() { + return false; + } + + @Override + public DeclarativeConfigProperties getDeclarativeConfig(String node) { + throw new IllegalStateException( + "Declarative configuration is not supported in the empty instrumentation config"); + } + + @Nullable + @Override + public ConfigProvider getConfigProvider() { + return null; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServerResponseCustomizer.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServerResponseCustomizer.java new file mode 100644 index 000000000000..0dab5f2e99ba --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServerResponseCustomizer.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.context.Context; + +/** + * {@link HttpServerResponseCustomizer} can be used to execute code after an HTTP server response is + * created for the purpose of mutating the response in some way, such as appending headers, that may + * depend on the context of the SERVER span. + * + *

This is a service provider interface that requires implementations to be registered in a + * provider-configuration file stored in the {@code META-INF/services} resource directory. + */ +public interface HttpServerResponseCustomizer { + /** + * Called for each HTTP server response with its SERVER span context provided. This is called at a + * time when response headers can already be set, but the response is not yet committed, which is + * typically at the start of request handling. + * + * @param serverContext Context of a SERVER span {@link io.opentelemetry.api.trace.SpanContext} + * @param response Response object specific to the library being instrumented + * @param responseMutator Mutator through which the provided response object can be mutated + */ + void customize( + Context serverContext, + RESPONSE response, + HttpServerResponseMutator responseMutator); +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServerResponseCustomizerHolder.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServerResponseCustomizerHolder.java new file mode 100644 index 000000000000..a9490de44813 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServerResponseCustomizerHolder.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.context.Context; + +/** + * Holds the currently active response customizer. This is set during agent initialization to an + * instance that calls each {@link HttpServerResponseCustomizer} found in the agent classpath. It is + * intended to be used directly from HTTP server library instrumentations, which is why this package + * is inside the bootstrap package that gets loaded in the bootstrap classloader. + */ +public final class HttpServerResponseCustomizerHolder { + private static volatile HttpServerResponseCustomizer responseCustomizer = new NoOpCustomizer(); + + public static void setCustomizer(HttpServerResponseCustomizer customizer) { + HttpServerResponseCustomizerHolder.responseCustomizer = customizer; + } + + public static HttpServerResponseCustomizer getCustomizer() { + return responseCustomizer; + } + + private HttpServerResponseCustomizerHolder() {} + + private static class NoOpCustomizer implements HttpServerResponseCustomizer { + + @Override + public void customize( + Context serverContext, T response, HttpServerResponseMutator responseMutator) {} + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServerResponseMutator.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServerResponseMutator.java new file mode 100644 index 000000000000..488c57e76f92 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServerResponseMutator.java @@ -0,0 +1,11 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +/** Provides the ability to mutate an instrumentation library specific response. */ +public interface HttpServerResponseMutator { + void appendHeader(RESPONSE response, String name, String value); +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServletResponseAdviceHelper.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServletResponseAdviceHelper.java new file mode 100644 index 000000000000..f2b2a8140579 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/HttpServletResponseAdviceHelper.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; + +public class HttpServletResponseAdviceHelper { + + public static StartResult startSpan( + Instrumenter instrumenter, Class declaringClass, String methodName) { + Context parentContext = Context.current(); + // Don't want to generate a new top-level span + if (Span.fromContext(parentContext).getSpanContext().isValid()) { + ClassAndMethod classAndMethod = ClassAndMethod.create(declaringClass, methodName); + if (instrumenter.shouldStart(parentContext, classAndMethod)) { + Context context = instrumenter.start(parentContext, classAndMethod); + Scope scope = context.makeCurrent(); + return new StartResult(classAndMethod, context, scope); + } + } + + return null; + } + + public static final class StartResult { + private final ClassAndMethod classAndMethod; + private final Context context; + private final Scope scope; + + private StartResult(ClassAndMethod classAndMethod, Context context, Scope scope) { + this.classAndMethod = classAndMethod; + this.context = context; + this.scope = scope; + } + + public ClassAndMethod getClassAndMethod() { + return classAndMethod; + } + + public Context getContext() { + return context; + } + + public Scope getScope() { + return scope; + } + } + + public static void stopSpan( + Instrumenter instrumenter, + Throwable throwable, + Context context, + Scope scope, + ClassAndMethod request) { + if (scope != null) { + scope.close(); + + instrumenter.end(context, request, null, throwable); + } + } + + private HttpServletResponseAdviceHelper() {} +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/JavaagentHttpServerInstrumenters.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/JavaagentHttpServerInstrumenters.java new file mode 100644 index 000000000000..ef7a3aaf6079 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/JavaagentHttpServerInstrumenters.java @@ -0,0 +1,55 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesGetter; +import java.util.function.Consumer; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class JavaagentHttpServerInstrumenters { + + private JavaagentHttpServerInstrumenters() {} + + public static Instrumenter create( + String instrumentationName, + HttpServerAttributesGetter httpAttributesGetter, + TextMapGetter headerGetter) { + return create(instrumentationName, httpAttributesGetter, headerGetter, customizer -> {}); + } + + public static Instrumenter create( + DefaultHttpServerInstrumenterBuilder builder) { + return create(builder, customizer -> {}); + } + + public static Instrumenter create( + String instrumentationName, + HttpServerAttributesGetter httpAttributesGetter, + TextMapGetter headerGetter, + Consumer> instrumenterBuilderConsumer) { + return create( + DefaultHttpServerInstrumenterBuilder.create( + instrumentationName, GlobalOpenTelemetry.get(), httpAttributesGetter, headerGetter), + instrumenterBuilderConsumer); + } + + public static Instrumenter create( + DefaultHttpServerInstrumenterBuilder builder, + Consumer> builderCustomizer) { + return builder + .configure(AgentCommonConfig.get()) + .setBuilderCustomizer(builderCustomizer) + .build(); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/JavaxServletAccessor.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/JavaxServletAccessor.java new file mode 100644 index 000000000000..f42dc3833c89 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/JavaxServletAccessor.java @@ -0,0 +1,107 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +public abstract class JavaxServletAccessor implements ServletAccessor { + @Override + public String getRequestContextPath(HttpServletRequest request) { + return request.getContextPath(); + } + + @Override + public String getRequestScheme(HttpServletRequest request) { + return request.getScheme(); + } + + @Override + public String getRequestUri(HttpServletRequest request) { + return request.getRequestURI(); + } + + @Override + public String getRequestQueryString(HttpServletRequest request) { + return request.getQueryString(); + } + + @Override + public Object getRequestAttribute(HttpServletRequest request, String name) { + return request.getAttribute(name); + } + + @Override + public void setRequestAttribute(HttpServletRequest request, String name, Object value) { + request.setAttribute(name, value); + } + + @Override + public String getRequestProtocol(HttpServletRequest request) { + return request.getProtocol(); + } + + @Override + public String getRequestMethod(HttpServletRequest request) { + return request.getMethod(); + } + + @Override + public String getRequestRemoteAddr(HttpServletRequest request) { + return request.getRemoteAddr(); + } + + @Override + public String getRequestHeader(HttpServletRequest request, String name) { + return request.getHeader(name); + } + + @Override + public List getRequestHeaderValues(HttpServletRequest request, String name) { + @SuppressWarnings("unchecked") + Enumeration values = request.getHeaders(name); + return values == null ? Collections.emptyList() : Collections.list(values); + } + + @Override + public Iterable getRequestHeaderNames(HttpServletRequest httpServletRequest) { + @SuppressWarnings("unchecked") + Enumeration names = httpServletRequest.getHeaderNames(); + return Collections.list(names); + } + + @Override + public List getRequestParameterValues( + HttpServletRequest httpServletRequest, String name) { + String[] values = httpServletRequest.getParameterValues(name); + return values == null ? Collections.emptyList() : Arrays.asList(values); + } + + @Override + public String getRequestServletPath(HttpServletRequest request) { + return request.getServletPath(); + } + + @Override + public String getRequestPathInfo(HttpServletRequest request) { + return request.getPathInfo(); + } + + @Override + public Principal getRequestUserPrincipal(HttpServletRequest request) { + return request.getUserPrincipal(); + } + + @Override + public boolean isServletException(Throwable throwable) { + return throwable instanceof ServletException; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/MappingResolver.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/MappingResolver.java new file mode 100644 index 000000000000..8faf16c4e0af --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/MappingResolver.java @@ -0,0 +1,153 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; + +/** + * Helper class for finding a mapping that matches current request from a collection of mappings. + */ +public final class MappingResolver { + private final Set exactMatches; + private final List wildcardMatchers; + private final boolean hasDefault; + + private MappingResolver( + Set exactMatches, List wildcardMatchers, boolean hasDefault) { + this.exactMatches = exactMatches.isEmpty() ? Collections.emptySet() : exactMatches; + this.wildcardMatchers = wildcardMatchers.isEmpty() ? Collections.emptyList() : wildcardMatchers; + this.hasDefault = hasDefault; + } + + public static MappingResolver build(Collection mappings) { + List wildcardMatchers = new ArrayList<>(); + Set exactMatches = new HashSet<>(); + boolean hasDefault = false; + for (String mapping : mappings) { + if (mapping.equals("")) { + exactMatches.add("/"); + } else if (mapping.equals("/") || mapping.equals("/*")) { + hasDefault = true; + } else if (mapping.startsWith("*.") && mapping.length() > 2) { + wildcardMatchers.add(new SuffixMatcher("/" + mapping, mapping.substring(1))); + } else if (mapping.endsWith("/*")) { + wildcardMatchers.add( + new PrefixMatcher(mapping, mapping.substring(0, mapping.length() - 2))); + } else { + exactMatches.add(mapping); + } + } + + // wildfly has empty mappings for default servlet + if (mappings.isEmpty()) { + hasDefault = true; + } + + return new MappingResolver(exactMatches, wildcardMatchers, hasDefault); + } + + /** Find mapping for requested path. */ + @Nullable + public String resolve(@Nullable String servletPath, @Nullable String pathInfo) { + if (servletPath == null) { + return null; + } + + // get full path inside context + String path = servletPath; + if (pathInfo != null) { + path += pathInfo; + } + // trim trailing / + if (path.endsWith("/") && !path.equals("/")) { + path = path.substring(0, path.length() - 1); + } + + if (exactMatches.contains(path)) { + return path; + } + + for (WildcardMatcher matcher : wildcardMatchers) { + if (matcher.match(path)) { + String mapping = matcher.getMapping(); + // for jsp return servlet path + if ("/*.jsp".equals(mapping) || "/*.jspx".equals(mapping)) { + return servletPath; + } + return mapping; + } + } + + if (hasDefault) { + return path.equals("/") ? "/" : "/*"; + } + + return null; + } + + private interface WildcardMatcher { + boolean match(String path); + + String getMapping(); + } + + private static class PrefixMatcher implements WildcardMatcher { + private final String mapping; + private final String prefix; + + private PrefixMatcher(String mapping, String prefix) { + this.mapping = mapping; + this.prefix = prefix; + } + + @Override + public boolean match(String path) { + return path.equals(prefix) || path.startsWith(prefix + "/"); + } + + @Override + public String getMapping() { + return mapping; + } + } + + private static class SuffixMatcher implements WildcardMatcher { + private final String mapping; + private final String suffix; + + private SuffixMatcher(String mapping, String suffix) { + this.mapping = mapping; + this.suffix = suffix; + } + + @Override + public boolean match(String path) { + return path.endsWith(suffix); + } + + @Override + public String getMapping() { + return mapping; + } + } + + /** + * Factory interface for creating {@link MappingResolver} instances. The main reason this class is + * here is that we need to ensure that the class used for {@code VirtualField} lookup is always + * the same. If we would use an injected class it could be different in different class loaders. + */ + public interface Factory { + + @Nullable + MappingResolver get(); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ResponseInstrumenterFactory.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ResponseInstrumenterFactory.java new file mode 100644 index 000000000000..f1827c14b1de --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ResponseInstrumenterFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; + +public final class ResponseInstrumenterFactory { + + public static Instrumenter createInstrumenter(String instrumentationName) { + CodeAttributesGetter codeAttributesGetter = + ClassAndMethod.codeAttributesGetter(); + return Instrumenter.builder( + GlobalOpenTelemetry.get(), + instrumentationName, + CodeSpanNameExtractor.create(codeAttributesGetter)) + .addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter)) + .buildInstrumenter(); + } + + private ResponseInstrumenterFactory() {} +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3Accessor.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3Accessor.java new file mode 100644 index 000000000000..3e8d75a576c4 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3Accessor.java @@ -0,0 +1,106 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class Servlet3Accessor extends JavaxServletAccessor + implements HttpServerResponseMutator { + public static final Servlet3Accessor INSTANCE = new Servlet3Accessor(); + + private Servlet3Accessor() {} + + @Override + public Integer getRequestRemotePort(HttpServletRequest request) { + return request.getRemotePort(); + } + + @Override + public String getRequestLocalAddr(HttpServletRequest request) { + return request.getLocalAddr(); + } + + @Override + public Integer getRequestLocalPort(HttpServletRequest request) { + return request.getLocalPort(); + } + + @Override + public void addRequestAsyncListener( + HttpServletRequest request, + ServletAsyncListener listener, + Object response) { + if (response instanceof HttpServletResponse) { + request + .getAsyncContext() + .addListener(new Listener(listener), request, (HttpServletResponse) response); + } + } + + @Override + public int getResponseStatus(HttpServletResponse response) { + return response.getStatus(); + } + + @Override + public List getResponseHeaderValues(HttpServletResponse response, String name) { + Collection values = response.getHeaders(name); + if (values == null) { + return Collections.emptyList(); + } + if (values instanceof List) { + return (List) values; + } + return new ArrayList<>(values); + } + + @Override + public boolean isResponseCommitted(HttpServletResponse response) { + return response.isCommitted(); + } + + @Override + public void appendHeader(HttpServletResponse response, String name, String value) { + response.addHeader(name, value); + } + + private static class Listener implements AsyncListener { + private final ServletAsyncListener listener; + + private Listener(ServletAsyncListener listener) { + this.listener = listener; + } + + @Override + public void onComplete(AsyncEvent event) { + listener.onComplete((HttpServletResponse) event.getSuppliedResponse()); + } + + @Override + public void onTimeout(AsyncEvent event) { + listener.onTimeout(event.getAsyncContext().getTimeout()); + } + + @Override + public void onError(AsyncEvent event) { + listener.onError(event.getThrowable(), (HttpServletResponse) event.getSuppliedResponse()); + } + + @Override + public void onStartAsync(AsyncEvent event) { + event + .getAsyncContext() + .addListener(this, event.getSuppliedRequest(), event.getSuppliedResponse()); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3FilterMappingResolverFactory.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3FilterMappingResolverFactory.java new file mode 100644 index 000000000000..f1326e1c61dc --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3FilterMappingResolverFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import java.util.Collection; +import javax.servlet.FilterConfig; +import javax.servlet.FilterRegistration; +import javax.servlet.ServletContext; +import javax.servlet.ServletRegistration; + +public class Servlet3FilterMappingResolverFactory + extends ServletFilterMappingResolverFactory { + private final FilterConfig filterConfig; + + public Servlet3FilterMappingResolverFactory(FilterConfig filterConfig) { + this.filterConfig = filterConfig; + } + + @Override + protected FilterRegistration getFilterRegistration() { + String filterName = filterConfig.getFilterName(); + ServletContext servletContext = filterConfig.getServletContext(); + if (filterName == null || servletContext == null) { + return null; + } + return servletContext.getFilterRegistration(filterName); + } + + @Override + protected Collection getUrlPatternMappings(FilterRegistration filterRegistration) { + return filterRegistration.getUrlPatternMappings(); + } + + @Override + protected Collection getServletNameMappings(FilterRegistration filterRegistration) { + return filterRegistration.getServletNameMappings(); + } + + @Override + @SuppressWarnings("ReturnsNullCollection") + protected Collection getServletMappings(String servletName) { + ServletRegistration servletRegistration = + filterConfig.getServletContext().getServletRegistration(servletName); + if (servletRegistration == null) { + return null; + } + return servletRegistration.getMappings(); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3RequestAdviceScope.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3RequestAdviceScope.java similarity index 86% rename from instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3RequestAdviceScope.java rename to instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3RequestAdviceScope.java index c50beae8ec63..5fcdf4c35754 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3RequestAdviceScope.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3RequestAdviceScope.java @@ -3,16 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +package io.opentelemetry.instrumentation.servlet.v3_0.copied; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; +import static io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3Singletons.helper; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.javaagent.bootstrap.CallDepth; -import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder; -import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver; -import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; import javax.annotation.Nullable; import javax.servlet.Servlet; import javax.servlet.http.HttpServletRequest; diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseAdviceScope.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3ResponseAdviceScope.java similarity index 80% rename from instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseAdviceScope.java rename to instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3ResponseAdviceScope.java index f5d893e16733..9c4568952f97 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3ResponseAdviceScope.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3ResponseAdviceScope.java @@ -3,15 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +package io.opentelemetry.instrumentation.servlet.v3_0.copied; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.responseInstrumenter; +import static io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3Singletons.responseInstrumenter; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; -import io.opentelemetry.javaagent.bootstrap.CallDepth; -import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseAdviceHelper; import javax.annotation.Nullable; public class Servlet3ResponseAdviceScope { diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3Singletons.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3Singletons.java new file mode 100644 index 000000000000..3450fc72e3bc --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/Servlet3Singletons.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import javax.servlet.Filter; +import javax.servlet.Servlet; +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.create() + .build(INSTRUMENTATION_NAME, Servlet3Accessor.INSTANCE); + + private static final ServletHelper HELPER = + new ServletHelper<>(INSTRUMENTER, Servlet3Accessor.INSTANCE); + + public static final VirtualField SERVLET_MAPPING_RESOLVER = + VirtualField.find(Servlet.class, MappingResolver.Factory.class); + public static final VirtualField FILTER_MAPPING_RESOLVER = + VirtualField.find(Filter.class, MappingResolver.Factory.class); + + private static final Instrumenter RESPONSE_INSTRUMENTER = + ResponseInstrumenterFactory.createInstrumenter(INSTRUMENTATION_NAME); + + public static ServletHelper helper() { + return HELPER; + } + + public static Instrumenter responseInstrumenter() { + return RESPONSE_INSTRUMENTER; + } + + public static MappingResolver getMappingResolver(Object servletOrFilter) { + MappingResolver.Factory factory = getMappingResolverFactory(servletOrFilter); + if (factory != null) { + return factory.get(); + } + return null; + } + + private static MappingResolver.Factory getMappingResolverFactory(Object servletOrFilter) { + boolean servlet = servletOrFilter instanceof Servlet; + if (servlet) { + return SERVLET_MAPPING_RESOLVER.get((Servlet) servletOrFilter); + } else { + return FILTER_MAPPING_RESOLVER.get((Filter) servletOrFilter); + } + } + + private Servlet3Singletons() {} +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAccessor.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAccessor.java new file mode 100644 index 000000000000..2efca2850808 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAccessor.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import java.security.Principal; +import java.util.List; + +/** + * This interface is used to access methods of ServletContext, HttpServletRequest and + * HttpServletResponse classes in shared code that is used for both jakarta.servlet and + * javax.servlet versions of those classes. A wrapper class with extra information attached may be + * used as well in cases where the class itself does not provide some field (such as response status + * for Servlet API 2.2). + * + * @param HttpServletRequest class (or a wrapper) + * @param HttpServletResponse class (or a wrapper) + */ +public interface ServletAccessor { + String getRequestContextPath(REQUEST request); + + String getRequestScheme(REQUEST request); + + String getRequestUri(REQUEST request); + + String getRequestQueryString(REQUEST request); + + Object getRequestAttribute(REQUEST request, String name); + + void setRequestAttribute(REQUEST request, String name, Object value); + + String getRequestProtocol(REQUEST request); + + String getRequestMethod(REQUEST request); + + String getRequestRemoteAddr(REQUEST request); + + Integer getRequestRemotePort(REQUEST request); + + String getRequestLocalAddr(REQUEST request); + + Integer getRequestLocalPort(REQUEST request); + + String getRequestHeader(REQUEST request, String name); + + List getRequestHeaderValues(REQUEST request, String name); + + Iterable getRequestHeaderNames(REQUEST request); + + List getRequestParameterValues(REQUEST request, String name); + + String getRequestServletPath(REQUEST request); + + String getRequestPathInfo(REQUEST request); + + Principal getRequestUserPrincipal(REQUEST request); + + void addRequestAsyncListener( + REQUEST request, ServletAsyncListener listener, Object response); + + int getResponseStatus(RESPONSE response); + + List getResponseHeaderValues(RESPONSE response, String name); + + boolean isResponseCommitted(RESPONSE response); + + boolean isServletException(Throwable throwable); +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAdditionalAttributesExtractor.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAdditionalAttributesExtractor.java new file mode 100644 index 000000000000..9ad95c8fc883 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAdditionalAttributesExtractor.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import static io.opentelemetry.api.common.AttributeKey.longKey; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.semconv.incubating.EnduserIncubatingAttributes; +import java.security.Principal; +import javax.annotation.Nullable; + +public class ServletAdditionalAttributesExtractor + implements AttributesExtractor< + ServletRequestContext, ServletResponseContext> { + private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES = + AgentInstrumentationConfig.get() + .getBoolean("otel.instrumentation.servlet.experimental-span-attributes", false); + private static final AttributeKey SERVLET_TIMEOUT = longKey("servlet.timeout"); + + private final ServletAccessor accessor; + + public ServletAdditionalAttributesExtractor(ServletAccessor accessor) { + this.accessor = accessor; + } + + @Override + public void onStart( + AttributesBuilder attributes, + Context parentContext, + ServletRequestContext requestContext) {} + + @SuppressWarnings("deprecation") // using deprecated semconv + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + ServletRequestContext requestContext, + @Nullable ServletResponseContext responseContext, + @Nullable Throwable error) { + if (AgentCommonConfig.get().getEnduserConfig().isIdEnabled()) { + Principal principal = accessor.getRequestUserPrincipal(requestContext.request()); + if (principal != null) { + String name = principal.getName(); + if (name != null) { + attributes.put(EnduserIncubatingAttributes.ENDUSER_ID, name); + } + } + } + if (!CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) { + return; + } + if (responseContext != null && responseContext.hasTimeout()) { + attributes.put(SERVLET_TIMEOUT, responseContext.getTimeout()); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAsyncContext.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAsyncContext.java new file mode 100644 index 000000000000..6534528b0d7a --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAsyncContext.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import static io.opentelemetry.context.ContextKey.named; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import io.opentelemetry.context.ImplicitContextKeyed; +import javax.annotation.Nullable; + +public class ServletAsyncContext implements ImplicitContextKeyed { + private static final ContextKey CONTEXT_KEY = + named("opentelemetry-servlet-async-context"); + + private boolean isAsyncListenerAttached; + private Throwable throwable; + private Object response; + private Context context; + + public static Context init(Context context) { + if (context.get(CONTEXT_KEY) != null) { + return context; + } + return context.with(new ServletAsyncContext()); + } + + @Nullable + public static ServletAsyncContext get(@Nullable Context context) { + return context != null ? context.get(CONTEXT_KEY) : null; + } + + public static boolean isAsyncListenerAttached(@Nullable Context context) { + ServletAsyncContext servletAsyncContext = get(context); + return servletAsyncContext != null && servletAsyncContext.isAsyncListenerAttached; + } + + public static void setAsyncListenerAttached(@Nullable Context context, boolean value) { + ServletAsyncContext servletAsyncContext = get(context); + if (servletAsyncContext != null) { + servletAsyncContext.isAsyncListenerAttached = value; + } + } + + public static Throwable getAsyncException(@Nullable Context context) { + ServletAsyncContext servletAsyncContext = get(context); + return servletAsyncContext != null ? servletAsyncContext.throwable : null; + } + + public static void recordAsyncException(@Nullable Context context, Throwable throwable) { + ServletAsyncContext servletAsyncContext = get(context); + if (servletAsyncContext != null) { + servletAsyncContext.throwable = throwable; + } + } + + public static Object getAsyncListenerResponse(@Nullable Context context) { + ServletAsyncContext servletAsyncContext = get(context); + return servletAsyncContext != null ? servletAsyncContext.response : null; + } + + public static void setAsyncListenerResponse(Context context, Object response) { + ServletAsyncContext servletAsyncContext = get(context); + if (servletAsyncContext != null) { + servletAsyncContext.response = response; + servletAsyncContext.context = context; + } + } + + public static Context getAsyncListenerContext(Context context) { + ServletAsyncContext servletAsyncContext = get(context); + if (servletAsyncContext != null) { + return servletAsyncContext.context; + } + return null; + } + + @Override + public Context storeInContext(Context context) { + return context.with(CONTEXT_KEY, this); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAsyncListener.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAsyncListener.java new file mode 100644 index 000000000000..120c387a080d --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAsyncListener.java @@ -0,0 +1,14 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +public interface ServletAsyncListener { + void onComplete(RESPONSE response); + + void onTimeout(long timeout); + + void onError(Throwable throwable, RESPONSE response); +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletContextPath.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletContextPath.java new file mode 100644 index 000000000000..d127824eaa38 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletContextPath.java @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import java.util.function.Function; + +/** + * The context key here is used to propagate the servlet context path throughout the request, so + * that routing framework instrumentation that updates the span name with a more specific route can + * prepend the servlet context path in front of that route. + * + *

This needs to be in the instrumentation-api module, instead of injected as a helper class into + * the different modules that need it, in order to make sure that there is only a single instance of + * the context key, since otherwise instrumentation across different class loaders would use + * different context keys and not be able to share the servlet context path. + */ +public final class ServletContextPath { + + // Keeps track of the servlet context path that needs to be prepended to the route when updating + // the span name + private static final ContextKey CONTEXT_KEY = + ContextKey.named("opentelemetry-servlet-context-path-key"); + + public static Context init( + Context context, Function contextPathExtractor, REQUEST request) { + ServletContextPath servletContextPath = context.get(CONTEXT_KEY); + if (servletContextPath != null) { + return context; + } + String contextPath = contextPathExtractor.apply(request); + if (contextPath == null) { + // context path isn't know yet + return context; + } + if (contextPath.isEmpty() || contextPath.equals("/")) { + // normalize empty context path to null + contextPath = null; + } + return context.with(CONTEXT_KEY, new ServletContextPath(contextPath)); + } + + private final String contextPath; + + private ServletContextPath(String contextPath) { + this.contextPath = contextPath; + } + + /** + * Returns a concatenation of a servlet context path stored in the given {@code context} and a + * given {@code spanName}. If there is no servlet path stored in the context, returns {@code + * spanName}. + */ + public static String prepend(Context context, String spanName) { + ServletContextPath servletContextPath = context.get(CONTEXT_KEY); + if (servletContextPath != null) { + String value = servletContextPath.contextPath; + if (value != null) { + if (spanName == null || spanName.isEmpty()) { + return value; + } else { + return value + (spanName.startsWith("/") ? spanName : ("/" + spanName)); + } + } + } + + return spanName; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletErrorCauseExtractor.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletErrorCauseExtractor.java new file mode 100644 index 000000000000..9e223d95454b --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletErrorCauseExtractor.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.instrumentation.api.instrumenter.ErrorCauseExtractor; + +public class ServletErrorCauseExtractor implements ErrorCauseExtractor { + private final ServletAccessor accessor; + + public ServletErrorCauseExtractor(ServletAccessor accessor) { + this.accessor = accessor; + } + + @Override + public Throwable extract(Throwable error) { + if (accessor.isServletException(error) && error.getCause() != null) { + error = error.getCause(); + } + return ErrorCauseExtractor.getDefault().extract(error); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletFilterMappingResolverFactory.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletFilterMappingResolverFactory.java new file mode 100644 index 000000000000..2c05bfc95db9 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletFilterMappingResolverFactory.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; + +public abstract class ServletFilterMappingResolverFactory + extends ServletMappingResolverFactory { + + protected abstract FILTERREGISTRATION getFilterRegistration(); + + protected abstract Collection getUrlPatternMappings( + FILTERREGISTRATION filterRegistration); + + protected abstract Collection getServletNameMappings( + FILTERREGISTRATION filterRegistration); + + protected abstract Collection getServletMappings(String servletName); + + @Override + @Nullable + protected Mappings getMappings() { + FILTERREGISTRATION filterRegistration = getFilterRegistration(); + if (filterRegistration == null) { + return null; + } + Set mappings = new HashSet<>(); + Collection urlPatternMappings = getUrlPatternMappings(filterRegistration); + if (urlPatternMappings != null) { + mappings.addAll(urlPatternMappings); + } + Collection servletNameMappings = getServletNameMappings(filterRegistration); + if (servletNameMappings != null) { + for (String servletName : servletNameMappings) { + Collection servletMappings = getServletMappings(servletName); + if (servletMappings != null) { + mappings.addAll(servletMappings); + } + } + } + + if (mappings.isEmpty()) { + return null; + } + + List mappingsList = new ArrayList<>(mappings); + // sort the longest mapping first + mappingsList.sort((s1, s2) -> s2.length() - s1.length()); + + return new Mappings(mappingsList); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletHelper.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletHelper.java new file mode 100644 index 000000000000..9458e8dbc8de --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletHelper.java @@ -0,0 +1,123 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; + +public class ServletHelper extends BaseServletHelper { + public static final String CONTEXT_ATTRIBUTE = ServletHelper.class.getName() + ".Context"; + + public ServletHelper( + Instrumenter, ServletResponseContext> instrumenter, + ServletAccessor accessor) { + super(instrumenter, accessor); + } + + 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 (!mustEndOnHandlerMethodExit(currentContext)) { + // We could be inside async dispatch. Unlike tomcat jetty does not call + // ServletAsyncListener.onError when exception is thrown inside async dispatch. + recordAsyncException(currentContext, throwable); + } + } + // also capture request parameters as servlet attributes + captureServletAttributes(currentContext, request); + } + + if (scope == null || context == null) { + return; + } + + ServletResponseContext responseContext = new ServletResponseContext<>(response); + if (throwable != null || mustEndOnHandlerMethodExit(context)) { + instrumenter.end(context, requestContext, responseContext, throwable); + } + } + + /** + * 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(Context context) { + // This request is handled asynchronously and startAsync instrumentation has already attached + // the listener. + return !isAsyncListenerAttached(context); + + // 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. + } + + /** + * Response object must be attached to a request prior to {@link #attachAsyncListener(REQUEST, + * Context)} being called, as otherwise in some environments it is not possible to access response + * from async event in listeners. + */ + public void setAsyncListenerResponse(Context context, RESPONSE response) { + ServletAsyncContext.setAsyncListenerResponse(context, response); + } + + @SuppressWarnings("unchecked") + public RESPONSE getAsyncListenerResponse(Context context) { + return (RESPONSE) ServletAsyncContext.getAsyncListenerResponse(context); + } + + public void attachAsyncListener(REQUEST request, Context context) { + if (isAsyncListenerAttached(context)) { + return; + } + + Object response = getAsyncListenerResponse(context); + + ServletRequestContext requestContext = new ServletRequestContext<>(request, null); + accessor.addRequestAsyncListener( + request, + new AsyncRequestCompletionListener<>(this, instrumenter, requestContext, context), + response); + ServletAsyncContext.setAsyncListenerAttached(context, true); + } + + private static boolean isAsyncListenerAttached(Context context) { + return ServletAsyncContext.isAsyncListenerAttached(context); + } + + public Runnable wrapAsyncRunnable(Runnable runnable) { + return AsyncRunnableWrapper.wrap(this, runnable); + } + + public void recordAsyncException(Context context, Throwable throwable) { + ServletAsyncContext.recordAsyncException(context, throwable); + } + + public Throwable getAsyncException(Context context) { + return ServletAsyncContext.getAsyncException(context); + } + + public Context getAsyncListenerContext(Context context) { + return ServletAsyncContext.getAsyncListenerContext(context); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletHttpAttributesGetter.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletHttpAttributesGetter.java new file mode 100644 index 000000000000..f4b9578b0f00 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletHttpAttributesGetter.java @@ -0,0 +1,140 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesGetter; +import java.util.List; +import javax.annotation.Nullable; + +public class ServletHttpAttributesGetter + implements HttpServerAttributesGetter< + ServletRequestContext, ServletResponseContext> { + + protected final ServletAccessor accessor; + + public ServletHttpAttributesGetter(ServletAccessor accessor) { + this.accessor = accessor; + } + + @Override + @Nullable + public String getHttpRequestMethod(ServletRequestContext requestContext) { + return accessor.getRequestMethod(requestContext.request()); + } + + @Override + @Nullable + public String getUrlScheme(ServletRequestContext requestContext) { + return accessor.getRequestScheme(requestContext.request()); + } + + @Nullable + @Override + public String getUrlPath(ServletRequestContext requestContext) { + return accessor.getRequestUri(requestContext.request()); + } + + @Nullable + @Override + public String getUrlQuery(ServletRequestContext requestContext) { + return accessor.getRequestQueryString(requestContext.request()); + } + + @Override + public List getHttpRequestHeader( + ServletRequestContext requestContext, String name) { + return accessor.getRequestHeaderValues(requestContext.request(), name); + } + + @Override + @Nullable + public Integer getHttpResponseStatusCode( + ServletRequestContext requestContext, + ServletResponseContext responseContext, + @Nullable Throwable error) { + RESPONSE response = responseContext.response(); + + // OpenLiberty might call the AsyncListener with an AsyncEvent that does not contain a response + // in some cases where the connection is dropped + if (response == null) { + return null; + } + + if (!accessor.isResponseCommitted(response) && 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 + public List getHttpResponseHeader( + ServletRequestContext requestContext, + ServletResponseContext responseContext, + String name) { + return accessor.getResponseHeaderValues(responseContext.response(), name); + } + + @Nullable + @Override + public String getNetworkProtocolName( + ServletRequestContext requestContext, + @Nullable ServletResponseContext responseContext) { + String protocol = accessor.getRequestProtocol(requestContext.request()); + if (protocol != null && protocol.startsWith("HTTP/")) { + return "http"; + } + return null; + } + + @Nullable + @Override + public String getNetworkProtocolVersion( + ServletRequestContext requestContext, + @Nullable ServletResponseContext responseContext) { + String protocol = accessor.getRequestProtocol(requestContext.request()); + if (protocol != null && protocol.startsWith("HTTP/")) { + return protocol.substring("HTTP/".length()); + } + return null; + } + + @Override + @Nullable + public String getNetworkPeerAddress( + ServletRequestContext requestContext, + @Nullable ServletResponseContext response) { + return accessor.getRequestRemoteAddr(requestContext.request()); + } + + @Override + @Nullable + public Integer getNetworkPeerPort( + ServletRequestContext requestContext, + @Nullable ServletResponseContext response) { + return accessor.getRequestRemotePort(requestContext.request()); + } + + @Nullable + @Override + public String getNetworkLocalAddress( + ServletRequestContext requestContext, + @Nullable ServletResponseContext response) { + return accessor.getRequestLocalAddr(requestContext.request()); + } + + @Nullable + @Override + public Integer getNetworkLocalPort( + ServletRequestContext requestContext, + @Nullable ServletResponseContext response) { + return accessor.getRequestLocalPort(requestContext.request()); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletInstrumenterBuilder.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletInstrumenterBuilder.java new file mode 100644 index 000000000000..4f4ed0324f35 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletInstrumenterBuilder.java @@ -0,0 +1,98 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesGetter; +import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor; +import java.util.ArrayList; +import java.util.List; + +public final class ServletInstrumenterBuilder { + + private ServletInstrumenterBuilder() {} + + private final List>> contextCustomizers = + new ArrayList<>(); + + private boolean propagateOperationListenersToOnEnd; + + public static ServletInstrumenterBuilder create() { + return new ServletInstrumenterBuilder<>(); + } + + @CanIgnoreReturnValue + public ServletInstrumenterBuilder addContextCustomizer( + ContextCustomizer> contextCustomizer) { + contextCustomizers.add(contextCustomizer); + return this; + } + + @CanIgnoreReturnValue + public ServletInstrumenterBuilder propagateOperationListenersToOnEnd() { + propagateOperationListenersToOnEnd = true; + return this; + } + + public Instrumenter, ServletResponseContext> build( + String instrumentationName, + ServletAccessor accessor, + SpanNameExtractor> spanNameExtractor, + HttpServerAttributesGetter, ServletResponseContext> + httpAttributesGetter) { + + DefaultHttpServerInstrumenterBuilder< + ServletRequestContext, ServletResponseContext> + serverBuilder = + DefaultHttpServerInstrumenterBuilder.create( + instrumentationName, + GlobalOpenTelemetry.get(), + httpAttributesGetter, + new ServletRequestGetter<>(accessor)); + serverBuilder.setSpanNameExtractor(e -> spanNameExtractor); + + return JavaagentHttpServerInstrumenters.create( + serverBuilder, + builder -> { + if (ServletRequestParametersExtractor.enabled()) { + AttributesExtractor, ServletResponseContext> + requestParametersExtractor = new ServletRequestParametersExtractor<>(accessor); + builder.addAttributesExtractor(requestParametersExtractor); + } + for (ContextCustomizer> contextCustomizer : + contextCustomizers) { + builder.addContextCustomizer(contextCustomizer); + } + + if (propagateOperationListenersToOnEnd) { + InstrumenterUtil.propagateOperationListenersToOnEnd(builder); + } + + builder + .addAttributesExtractor(new ServletAdditionalAttributesExtractor<>(accessor)) + .setErrorCauseExtractor(new ServletErrorCauseExtractor<>(accessor)); + }); + } + + public Instrumenter, ServletResponseContext> build( + String instrumentationName, ServletAccessor accessor) { + HttpServerAttributesGetter, ServletResponseContext> + httpAttributesGetter = new ServletHttpAttributesGetter<>(accessor); + SpanNameExtractor> spanNameExtractor = + HttpSpanNameExtractor.builder(httpAttributesGetter) + .setKnownMethods(AgentCommonConfig.get().getKnownHttpRequestMethods()) + .build(); + + return build(instrumentationName, accessor, spanNameExtractor, httpAttributesGetter); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletMappingResolverFactory.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletMappingResolverFactory.java new file mode 100644 index 000000000000..70062307bea9 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletMappingResolverFactory.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import java.util.Collection; +import javax.annotation.Nullable; + +public abstract class ServletMappingResolverFactory implements MappingResolver.Factory { + + private volatile MappingResolverHolder holder; + + @Nullable + protected abstract Mappings getMappings(); + + private MappingResolver build() { + Mappings mappings = getMappings(); + if (mappings == null) { + return null; + } + + return MappingResolver.build(mappings.getMappings()); + } + + @Override + @Nullable + public final MappingResolver get() { + // build MappingResolver if it is not already built, no need to synchronize as it can safely be + // built more than once + if (holder == null) { + holder = new MappingResolverHolder(build()); + } + + return holder.mappingResolver; + } + + // using a holder class to distinguish build() returning null from build() not called + private static class MappingResolverHolder { + final MappingResolver mappingResolver; + + MappingResolverHolder(MappingResolver mappingResolver) { + this.mappingResolver = mappingResolver; + } + } + + public static class Mappings { + private final Collection mappings; + + public Mappings(Collection mappings) { + this.mappings = mappings; + } + + public Collection getMappings() { + return mappings; + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletRequestContext.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletRequestContext.java new file mode 100644 index 000000000000..59f5141f83fd --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletRequestContext.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import javax.annotation.Nullable; + +public class ServletRequestContext { + private final T request; + private final Object servletOrFilter; + + public ServletRequestContext(T request) { + this(request, null); + } + + public ServletRequestContext(T request, Object servletOrFilter) { + this.request = request; + this.servletOrFilter = servletOrFilter; + } + + public T request() { + return request; + } + + @Nullable + public Object servletOrFilter() { + return servletOrFilter; + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletRequestGetter.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletRequestGetter.java new file mode 100644 index 000000000000..039f9599f0bf --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletRequestGetter.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.context.propagation.TextMapGetter; +import java.util.Iterator; + +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); + } + + @Override + public Iterator getAll(ServletRequestContext carrier, String key) { + return accessor.getRequestHeaderValues(carrier.request(), key).iterator(); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletRequestParametersExtractor.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletRequestParametersExtractor.java new file mode 100644 index 000000000000..6ea2935a7d56 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletRequestParametersExtractor.java @@ -0,0 +1,83 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import static java.util.Collections.emptyList; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiConsumer; +import javax.annotation.Nullable; + +public class ServletRequestParametersExtractor + implements AttributesExtractor< + ServletRequestContext, ServletResponseContext> { + private static final List CAPTURE_REQUEST_PARAMETERS = + AgentInstrumentationConfig.get() + .getList( + "otel.instrumentation.servlet.experimental.capture-request-parameters", emptyList()); + + private static final ConcurrentMap>> parameterKeysCache = + new ConcurrentHashMap<>(); + + private final ServletAccessor accessor; + + public ServletRequestParametersExtractor(ServletAccessor accessor) { + this.accessor = accessor; + } + + public static boolean enabled() { + return !CAPTURE_REQUEST_PARAMETERS.isEmpty(); + } + + public void setAttributes( + REQUEST request, BiConsumer>, List> consumer) { + for (String name : CAPTURE_REQUEST_PARAMETERS) { + List values = accessor.getRequestParameterValues(request, name); + if (!values.isEmpty()) { + consumer.accept(parameterAttributeKey(name), values); + } + } + } + + @Override + public void onStart( + AttributesBuilder attributes, + Context parentContext, + ServletRequestContext requestContext) {} + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + ServletRequestContext requestContext, + @Nullable ServletResponseContext responseContext, + @Nullable Throwable error) { + // request parameters are extracted at the end of the request to make sure that we don't access + // them before request encoding has been set + REQUEST request = requestContext.request(); + setAttributes(request, attributes::put); + } + + private static AttributeKey> parameterAttributeKey(String headerName) { + return parameterKeysCache.computeIfAbsent( + headerName, ServletRequestParametersExtractor::createKey); + } + + private static AttributeKey> createKey(String parameterName) { + // normalize parameter name similarly as is done with header names when header values are + // captured as span attributes + parameterName = parameterName.toLowerCase(Locale.ROOT); + String key = "servlet.request.parameter." + parameterName; + return AttributeKey.stringArrayKey(key); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletResponseContext.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletResponseContext.java new file mode 100644 index 000000000000..92c49182d881 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletResponseContext.java @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +public class ServletResponseContext { + private final T response; + // used for servlet 2.2 where request status can't be extracted from HttpServletResponse + private Integer status; + private Long timeout; + + public ServletResponseContext(T response) { + this.response = response; + } + + public T response() { + return response; + } + + 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-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletSpanNameProvider.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletSpanNameProvider.java new file mode 100644 index 000000000000..eee8ec2c667a --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletSpanNameProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0.copied; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteBiGetter; +import javax.annotation.Nullable; + +/** Helper class for constructing span name for given servlet/filter mapping and request. */ +public class ServletSpanNameProvider + implements HttpServerRouteBiGetter { + private final ServletAccessor servletAccessor; + + public ServletSpanNameProvider(ServletAccessor servletAccessor) { + this.servletAccessor = servletAccessor; + } + + @Override + @Nullable + public String get(Context context, MappingResolver mappingResolver, REQUEST request) { + String servletPath = servletAccessor.getRequestServletPath(request); + String pathInfo = servletAccessor.getRequestPathInfo(request); + String mapping = mappingResolver.resolve(servletPath, pathInfo); + // mapping was not found + if (mapping == null) { + return null; + } + + return ServletContextPath.prepend(context, mapping); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/AbstractServlet3Test.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/AbstractServlet3Test.java index 610393b94d13..390ec467953e 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/AbstractServlet3Test.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/AbstractServlet3Test.java @@ -18,10 +18,10 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.api.internal.HttpConstants; +import io.opentelemetry.instrumentation.servlet.v3_0.copied.HttpServerResponseCustomizerHolder; import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.trace.data.SpanData; import javax.servlet.Servlet; diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/Servlet3MappingResolverFactory.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/Servlet3MappingResolverFactory.java new file mode 100644 index 000000000000..573670876207 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/Servlet3MappingResolverFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.servlet.v3_0; + +import io.opentelemetry.instrumentation.servlet.v3_0.copied.ServletMappingResolverFactory; +import javax.annotation.Nullable; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletRegistration; + +public class Servlet3MappingResolverFactory extends ServletMappingResolverFactory { + private final ServletConfig servletConfig; + + public Servlet3MappingResolverFactory(ServletConfig servletConfig) { + this.servletConfig = servletConfig; + } + + @Override + @Nullable + public Mappings getMappings() { + String servletName = servletConfig.getServletName(); + ServletContext servletContext = servletConfig.getServletContext(); + if (servletName == null || servletContext == null) { + return null; + } + + ServletRegistration servletRegistration = servletContext.getServletRegistration(servletName); + if (servletRegistration == null) { + return null; + } + return new Mappings(servletRegistration.getMappings()); + } +} diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java index f096f8df0066..8d7240e06237 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestAgentHttpResponseCustomizer.java @@ -8,8 +8,8 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.context.Context; -import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizer; -import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator; +import io.opentelemetry.instrumentation.servlet.v3_0.copied.HttpServerResponseCustomizer; +import io.opentelemetry.instrumentation.servlet.v3_0.copied.HttpServerResponseMutator; public class TestAgentHttpResponseCustomizer implements HttpServerResponseCustomizer { diff --git a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestServlet3.java b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestServlet3.java index a8d2a01a6af3..eac7d502ed5a 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestServlet3.java +++ b/instrumentation/servlet/servlet-3.0/library/src/test/java/io/opentelemetry/instrumentation/servlet/v3_0/TestServlet3.java @@ -7,6 +7,7 @@ import static io.opentelemetry.instrumentation.servlet.v3_0.AbstractServlet3Test.HTML_PRINT_WRITER; import static io.opentelemetry.instrumentation.servlet.v3_0.AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM; +import static io.opentelemetry.instrumentation.servlet.v3_0.copied.Servlet3Singletons.SERVLET_MAPPING_RESOLVER; import static io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest.controller; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; @@ -16,11 +17,9 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.SERVLET_MAPPING_RESOLVER; import io.opentelemetry.instrumentation.testing.GlobalTraceUtil; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3MappingResolverFactory; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; From 4b6cd18e441c7535d97ab9dfa8d948aff0ef7234 Mon Sep 17 00:00:00 2001 From: Tyler Benson <734411+tylerbenson@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:50:00 -0500 Subject: [PATCH 07/10] Fix dependency declarations. --- instrumentation/servlet/servlet-3.0/library/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts index 48273c54b02f..1012c8a4c913 100644 --- a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts +++ b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts @@ -3,8 +3,8 @@ plugins { } dependencies { - library("javax.servlet:javax.servlet-api:3.0.1") - library("io.opentelemetry.semconv:opentelemetry-semconv-incubating") + compileOnly("javax.servlet:javax.servlet-api:3.0.1") + implementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") testLibrary("org.eclipse.jetty:jetty-server:8.0.0.v20110901") testLibrary("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901") From fc4f6b802469b467472003b73500b339e84bda77 Mon Sep 17 00:00:00 2001 From: Tyler Benson <734411+tylerbenson@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:57:39 -0500 Subject: [PATCH 08/10] Copy `enduser.id` key over and remove dependency. Incubating keys are internalized to avoid a dependency on a moving target. --- .../servlet/servlet-3.0/library/build.gradle.kts | 1 - .../servlet/v3_0/copied/BaseServletHelper.java | 5 ++--- .../v3_0/copied/ServletAdditionalAttributesExtractor.java | 7 +++++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts index 1012c8a4c913..77b729c88d2f 100644 --- a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts +++ b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts @@ -4,7 +4,6 @@ plugins { dependencies { compileOnly("javax.servlet:javax.servlet-api:3.0.1") - implementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") testLibrary("org.eclipse.jetty:jetty-server:8.0.0.v20110901") testLibrary("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901") diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/BaseServletHelper.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/BaseServletHelper.java index 02bc791e5e21..776b128b4562 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/BaseServletHelper.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/BaseServletHelper.java @@ -14,7 +14,6 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute; -import io.opentelemetry.semconv.incubating.EnduserIncubatingAttributes; import java.security.Principal; import java.util.function.Function; @@ -132,7 +131,7 @@ private void captureRequestParameters(Span serverSpan, REQUEST request) { } /** - * Capture {@link EnduserIncubatingAttributes#ENDUSER_ID} as span attributes when SERVER span is + * Capture {@link ServletAdditionalAttributesExtractor#ENDUSER_ID} as span attributes when SERVER span is * not create by servlet instrumentation. * *

When SERVER span is created by servlet instrumentation we register {@link @@ -148,7 +147,7 @@ private void captureEnduserId(Span serverSpan, REQUEST request) { if (principal != null) { String name = principal.getName(); if (name != null) { - serverSpan.setAttribute(EnduserIncubatingAttributes.ENDUSER_ID, name); + serverSpan.setAttribute(ServletAdditionalAttributesExtractor.ENDUSER_ID, name); } } } diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAdditionalAttributesExtractor.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAdditionalAttributesExtractor.java index 9ad95c8fc883..d595648db552 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAdditionalAttributesExtractor.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/ServletAdditionalAttributesExtractor.java @@ -6,12 +6,12 @@ package io.opentelemetry.instrumentation.servlet.v3_0.copied; import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.semconv.incubating.EnduserIncubatingAttributes; import java.security.Principal; import javax.annotation.Nullable; @@ -23,6 +23,9 @@ public class ServletAdditionalAttributesExtractor .getBoolean("otel.instrumentation.servlet.experimental-span-attributes", false); private static final AttributeKey SERVLET_TIMEOUT = longKey("servlet.timeout"); + // copied from EnduserIncubatingAttributes + static final AttributeKey ENDUSER_ID = stringKey("enduser.id"); + private final ServletAccessor accessor; public ServletAdditionalAttributesExtractor(ServletAccessor accessor) { @@ -48,7 +51,7 @@ public void onEnd( if (principal != null) { String name = principal.getName(); if (name != null) { - attributes.put(EnduserIncubatingAttributes.ENDUSER_ID, name); + attributes.put(ENDUSER_ID, name); } } } From a34d722f048884b1fd1cf3e0177702082c1ec82c Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:06:08 +0000 Subject: [PATCH 09/10] ./gradlew spotlessApply --- .../servlet/v3_0/copied/BaseServletHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/BaseServletHelper.java b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/BaseServletHelper.java index 776b128b4562..4aeadf98c297 100644 --- a/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/BaseServletHelper.java +++ b/instrumentation/servlet/servlet-3.0/library/src/main/java/io/opentelemetry/instrumentation/servlet/v3_0/copied/BaseServletHelper.java @@ -131,8 +131,8 @@ private void captureRequestParameters(Span serverSpan, REQUEST request) { } /** - * Capture {@link ServletAdditionalAttributesExtractor#ENDUSER_ID} as span attributes when SERVER span is - * not create by servlet instrumentation. + * Capture {@link ServletAdditionalAttributesExtractor#ENDUSER_ID} as span attributes when SERVER + * span is not create by servlet instrumentation. * *

When SERVER span is created by servlet instrumentation we register {@link * ServletAdditionalAttributesExtractor} as an attribute extractor. When SERVER span is not From 6ce59fa506d7e0b12b1ccc93df39dd0a72b6c152 Mon Sep 17 00:00:00 2001 From: Tyler Benson <734411+tylerbenson@users.noreply.github.com> Date: Fri, 7 Nov 2025 12:57:03 -0500 Subject: [PATCH 10/10] Fix latestDepTests and add readme. --- .../servlet/servlet-3.0/library/README.md | 52 +++++++++++++++++++ .../servlet-3.0/library/build.gradle.kts | 17 ++++++ 2 files changed, 69 insertions(+) create mode 100644 instrumentation/servlet/servlet-3.0/library/README.md diff --git a/instrumentation/servlet/servlet-3.0/library/README.md b/instrumentation/servlet/servlet-3.0/library/README.md new file mode 100644 index 000000000000..df294859efbe --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/library/README.md @@ -0,0 +1,52 @@ +# Library Instrumentation for Java Servlet version 3.0 and higher + +Provides OpenTelemetry instrumentation for Java Servlets through a servlet filter. + +## Quickstart + +### Add these dependencies to your project + +Replace `OPENTELEMETRY_VERSION` with +the [latest release](https://central.sonatype.com/artifact/io.opentelemetry.instrumentation/opentelemetry-lettuce-5.1). + +For Maven, add to your `pom.xml` dependencies: + +```xml + + + + io.opentelemetry.instrumentation + opentelemetry-servlet-3.0 + OPENTELEMETRY_VERSION + + +``` + +For Gradle, add to your dependencies: + +```kotlin +implementation("io.opentelemetry.instrumentation:opentelemetry-servlet-3.0:OPENTELEMETRY_VERSION") +``` + +### Usage + +Add the filter to your `web.xml` + +```xml + + + OpenTelemetryServletFilter + io.opentelemetry.instrumentation.servlet.v3_0.OpenTelemetryServletFilter + + true + + + OpenTelemetryServletFilter + /* + + +``` + +Note: GlobalOpenTelemetry must be set before filter initialization. If you are unable to ensure it +is set first, consider creating a subclass of `OpenTelemetryServletFilter` that handles +initialization of GlobalOpenTelemetry in a static initializer or constructor. diff --git a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts index 77b729c88d2f..019c57b6df50 100644 --- a/instrumentation/servlet/servlet-3.0/library/build.gradle.kts +++ b/instrumentation/servlet/servlet-3.0/library/build.gradle.kts @@ -9,6 +9,12 @@ dependencies { testLibrary("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901") testLibrary("org.apache.tomcat.embed:tomcat-embed-core:8.0.41") testLibrary("org.apache.tomcat.embed:tomcat-embed-jasper:8.0.41") + + latestDepTestLibrary("org.eclipse.jetty:jetty-server:10.+") // see servlet-5.0 module + latestDepTestLibrary("org.eclipse.jetty:jetty-servlet:10.+") // see servlet-5.0 module + + latestDepTestLibrary("org.apache.tomcat.embed:tomcat-embed-core:9.+") // see servlet-5.0 module + latestDepTestLibrary("org.apache.tomcat.embed:tomcat-embed-jasper:9.+") // see servlet-5.0 module } tasks { @@ -18,3 +24,14 @@ tasks { jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") } } + +// Servlet 3.0 in latest Jetty versions requires Java 11 +// However, projects that depend on this module are still be using Java 8 in testLatestDeps mode +// Therefore, we need a separate project for servlet 3.0 tests +val latestDepTest = findProperty("testLatestDeps") as Boolean + +if (latestDepTest) { + otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_11) + } +}