diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index c7881b6a3a1f..fbc8dea595d2 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -174,17 +174,26 @@ These are the supported libraries and frameworks: These are the application servers that the smoke tests are run against: -| Application server | Version | JVM | OS | -| ------------------------------------------------------------------------------------- | --------------------------- | ----------------- | ------------------------------------- | -| [Jetty](https://www.eclipse.org/jetty/) | 9.4.x, 10.0.x, 11.0.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | -| [Payara](https://www.payara.fish/) | 5.0.x, 5.1.x | OpenJDK 8, 11 | [`ubuntu-latest`], [`windows-latest`] | -| [Tomcat](http://tomcat.apache.org/) | 7.0.x | OpenJDK 8 | [`ubuntu-latest`], [`windows-latest`] | -| [Tomcat](http://tomcat.apache.org/) | 7.0.x, 8.5.x, 9.0.x, 10.0.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | -| [TomEE](https://tomee.apache.org/) | 7.x, 8.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | -| [Open Liberty](https://openliberty.io/) | 21.x, 22.x, 23.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | -| [Websphere Traditional](https://www.ibm.com/uk-en/cloud/websphere-application-server) | 8.5.5.x, 9.0.x | IBM JDK 8 | Red Hat Enterprise Linux 8.4 | -| [WildFly](https://www.wildfly.org/) | 13.x | OpenJDK 8 | [`ubuntu-latest`], [`windows-latest`] | -| [WildFly](https://www.wildfly.org/) | 17.x, 21.x, 25.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | +| Application server | Version | JVM | OS | +|---------------------------------------------------------------------------------------|------------------------------------------|------------------------------------------------|---------------------------------------| +| [Jetty](https://www.eclipse.org/jetty/) | 9.4.53 | OpenJDK 8, 11, 17, 21
OpenJ9 8, 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] | +| [Jetty](https://www.eclipse.org/jetty/) | 10.0.19, 11.0.19 | OpenJDK 11, 17, 21
OpenJ9 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] | +| [Jetty](https://www.eclipse.org/jetty/) | 12.0.6 | OpenJDK 17, 21
OpenJ9 17, 21 | [`ubuntu-latest`], [`windows-latest`] | +| [Open Liberty](https://openliberty.io/) | 20.0.0.12 | OpenJDK 8, 11
OpenJ9 8, 11 | [`ubuntu-latest`], [`windows-latest`] | +| [Open Liberty](https://openliberty.io/) | 21.0.0.12, 22.0.0.12 | OpenJDK 8, 11, 17
OpenJ9 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | +| [Open Liberty](https://openliberty.io/) | 23.0.0.12 | OpenJDK 8, 11, 17, 20
OpenJ9 8, 11, 17, 20 | [`ubuntu-latest`], [`windows-latest`] | +| [Payara](https://www.payara.fish/) | 5.2020.6, 5.2021.8 | OpenJDK 8, 11
OpenJ9 8, 11 | [`ubuntu-latest`], [`windows-latest`] | +| [Payara](https://www.payara.fish/) | 6.2023.12 | OpenJDK 11, 17
OpenJ9 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] | +| [Tomcat](http://tomcat.apache.org/) | 7.0.109 | OpenJDK 8
OpenJ9 8 | [`ubuntu-latest`], [`windows-latest`] | +| [Tomcat](http://tomcat.apache.org/) | 8.5.98, 9.0.85 | OpenJDK 8, 11, 17, 21
OpenJ9 8, 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] | +| [Tomcat](http://tomcat.apache.org/) | 10.1.18 | OpenJDK 11, 17, 21
OpenJ9 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] | +| [TomEE](https://tomee.apache.org/) | 7.0.9, 7.1.4 | OpenJDK 8
OpenJ9 8 | [`ubuntu-latest`], [`windows-latest`] | +| [TomEE](https://tomee.apache.org/) | 8.0.16 | OpenJDK 8, 11, 17, 21
OpenJ9 8, 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] | +| [TomEE](https://tomee.apache.org/) | 9.1.2 | OpenJDK 11, 17, 21
OpenJ9 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] | +| [Websphere Traditional](https://www.ibm.com/uk-en/cloud/websphere-application-server) | 8.5.5.22, 9.0.5.14 | IBM JDK 8 | Red Hat Enterprise Linux 8.4 | +| [WildFly](https://www.wildfly.org/) | 13.0.0.Final | OpenJDK 8
OpenJ9 8 | [`ubuntu-latest`], [`windows-latest`] | +| [WildFly](https://www.wildfly.org/) | 17.0.1.Final, 21.0.0.Final | OpenJDK 8, 11, 17, 21
OpenJ9 8, 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] | +| [WildFly](https://www.wildfly.org/) | 28.0.1.Final, 29.0.1.Final, 30.0.1.Final | OpenJDK 11, 17, 21
OpenJ9 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] | [`ubuntu-latest`]: https://github.com/actions/runner-images#available-images [`windows-latest`]: https://github.com/actions/runner-images#available-images @@ -193,10 +202,10 @@ These are the application servers that the smoke tests are run against: These are the JVMs and operating systems that the integration tests are run against: -| JVM | Versions | OS | -| ----------------------------------------------------------------------------------------- | --------- | ------------------------------------- | -| [OpenJDK (Eclipse Temurin)](https://adoptium.net/) | 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | -| [OpenJ9 (IBM Semeru Runtimes)](https://developer.ibm.com/languages/java/semeru-runtimes/) | 8, 11, 17 | [`ubuntu-latest`] | +| JVM | Versions | OS | +| ----------------------------------------------------------------------------------------- |---------------| ------------------------------------- | +| [OpenJDK (Eclipse Temurin)](https://adoptium.net/) | 8, 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] | +| [OpenJ9 (IBM Semeru Runtimes)](https://developer.ibm.com/languages/java/semeru-runtimes/) | 8, 11, 17 | [`ubuntu-latest`] | ## Disabled instrumentations diff --git a/instrumentation/jetty/jetty-11.0/javaagent/build.gradle.kts b/instrumentation/jetty/jetty-11.0/javaagent/build.gradle.kts index 3c172eb92ee8..6e133bb309de 100644 --- a/instrumentation/jetty/jetty-11.0/javaagent/build.gradle.kts +++ b/instrumentation/jetty/jetty-11.0/javaagent/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { bootstrap(project(":instrumentation:servlet:servlet-common:bootstrap")) testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent")) + testInstrumentation(project(":instrumentation:jetty:jetty-12.0:javaagent")) // jetty-servlet does not exist in jetty 12, so we don't need to explicitly pin it to 11.+ testLibrary("org.eclipse.jetty:jetty-servlet:11.0.0") diff --git a/instrumentation/jetty/jetty-11.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/JettyHandlerTest.java b/instrumentation/jetty/jetty-11.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/JettyHandlerTest.java index eb6358daa522..f8face51649a 100644 --- a/instrumentation/jetty/jetty-11.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/JettyHandlerTest.java +++ b/instrumentation/jetty/jetty-11.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/JettyHandlerTest.java @@ -40,7 +40,7 @@ import org.eclipse.jetty.server.handler.ErrorHandler; import org.junit.jupiter.api.extension.RegisterExtension; -public class JettyHandlerTest extends AbstractHttpServerTest { +class JettyHandlerTest extends AbstractHttpServerTest { @RegisterExtension static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent(); @@ -62,25 +62,17 @@ protected void handleErrorPage( private static final TestHandler testHandler = new TestHandler(); @Override - protected Server setupServer() { + protected Server setupServer() throws Exception { Server server = new Server(port); server.setHandler(testHandler); server.addBean(errorHandler); - try { - server.start(); - } catch (Exception e) { - throw new RuntimeException(e); - } + server.start(); return server; } @Override - protected void stopServer(Server server) { - try { - server.stop(); - } catch (Exception e) { - throw new RuntimeException(e); - } + protected void stopServer(Server server) throws Exception { + server.stop(); } @Override diff --git a/instrumentation/jetty/jetty-12.0/javaagent/build.gradle.kts b/instrumentation/jetty/jetty-12.0/javaagent/build.gradle.kts new file mode 100644 index 000000000000..4c97e1e4d052 --- /dev/null +++ b/instrumentation/jetty/jetty-12.0/javaagent/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("org.eclipse.jetty") + module.set("jetty-server") + versions.set("[12,)") + } +} + +dependencies { + library("org.eclipse.jetty:jetty-server:12.0.0") + + bootstrap(project(":instrumentation:servlet:servlet-common:bootstrap")) + implementation(project(":instrumentation:servlet:servlet-common:javaagent")) + + testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent")) + testInstrumentation(project(":instrumentation:jetty:jetty-11.0:javaagent")) + + testLibrary("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.0") +} + +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) +} diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Helper.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Helper.java new file mode 100644 index 000000000000..d17aba9c6d00 --- /dev/null +++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Helper.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jetty.v12_0; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge; +import io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper; +import javax.annotation.Nullable; +import org.eclipse.jetty.server.HttpStream; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; + +public class Jetty12Helper { + private final Instrumenter instrumenter; + + Jetty12Helper(Instrumenter instrumenter) { + this.instrumenter = instrumenter; + } + + public boolean shouldStart(Context parentContext, Request request) { + return instrumenter.shouldStart(parentContext, request); + } + + public Context start(Context parentContext, Request request, Response response) { + Context context = instrumenter.start(parentContext, request); + request.addFailureListener(throwable -> end(context, request, response, throwable)); + // detect request completion + // https://github.com/jetty/jetty.project/blob/52d94174e2c7a6e794c6377dcf9cd3ed0b9e1806/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/EventsHandler.java#L75 + request.addHttpStreamWrapper( + stream -> + new HttpStream.Wrapper(stream) { + @Override + public void succeeded() { + end(context, request, response, null); + super.succeeded(); + } + + @Override + public void failed(Throwable throwable) { + end(context, request, response, throwable); + super.failed(throwable); + } + }); + + return context; + } + + public void end(Context context, Request request, Response response, @Nullable Throwable error) { + if (error == null) { + error = AppServerBridge.getException(context); + } + if (error == null) { + error = (Throwable) request.getAttribute(ServletHelper.ASYNC_EXCEPTION_ATTRIBUTE); + } + + instrumenter.end(context, request, response, error); + } +} diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12HttpAttributesGetter.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12HttpAttributesGetter.java new file mode 100644 index 000000000000..456fae9e75b3 --- /dev/null +++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12HttpAttributesGetter.java @@ -0,0 +1,99 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jetty.v12_0; + +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesGetter; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; +import javax.annotation.Nullable; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; + +class Jetty12HttpAttributesGetter implements HttpServerAttributesGetter { + + @Override + public String getHttpRequestMethod(Request request) { + return request.getMethod(); + } + + @Override + public List getHttpRequestHeader(Request request, String name) { + return request.getHeaders().getValuesList(name); + } + + @Override + public Integer getHttpResponseStatusCode( + Request request, Response response, @Nullable Throwable error) { + if (!response.isCommitted() && error != null) { + return 500; + } + return response.getStatus(); + } + + @Override + public List getHttpResponseHeader(Request request, Response response, String name) { + return response.getHeaders().getValuesList(name); + } + + @Override + @Nullable + public String getUrlScheme(Request request) { + HttpURI uri = request.getHttpURI(); + return uri == null ? null : uri.getScheme(); + } + + @Nullable + @Override + public String getUrlPath(Request request) { + HttpURI uri = request.getHttpURI(); + return uri == null ? null : uri.getPath(); + } + + @Nullable + @Override + public String getUrlQuery(Request request) { + HttpURI uri = request.getHttpURI(); + return uri == null ? null : uri.getQuery(); + } + + @Nullable + @Override + public String getNetworkProtocolName(Request request, @Nullable Response unused) { + String protocol = request.getConnectionMetaData().getProtocol(); + if (protocol != null && protocol.startsWith("HTTP/")) { + return "http"; + } + return null; + } + + @Nullable + @Override + public String getNetworkProtocolVersion(Request request, @Nullable Response unused) { + String protocol = request.getConnectionMetaData().getProtocol(); + if (protocol.startsWith("HTTP/")) { + return protocol.substring("HTTP/".length()); + } + return null; + } + + @Override + @Nullable + public InetSocketAddress getNetworkPeerInetSocketAddress( + Request request, @Nullable Response unused) { + SocketAddress address = request.getConnectionMetaData().getRemoteSocketAddress(); + return address instanceof InetSocketAddress ? (InetSocketAddress) address : null; + } + + @Nullable + @Override + public InetSocketAddress getNetworkLocalInetSocketAddress( + Request request, @Nullable Response unused) { + SocketAddress address = request.getConnectionMetaData().getLocalSocketAddress(); + return address instanceof InetSocketAddress ? (InetSocketAddress) address : null; + } +} diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12IgnoredTypesConfigurer.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12IgnoredTypesConfigurer.java new file mode 100644 index 000000000000..24b5d7d45bf2 --- /dev/null +++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12IgnoredTypesConfigurer.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jetty.v12_0; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesBuilder; +import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesConfigurer; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; + +@AutoService(IgnoredTypesConfigurer.class) +public class Jetty12IgnoredTypesConfigurer implements IgnoredTypesConfigurer { + + @Override + public void configure(IgnoredTypesBuilder builder, ConfigProperties config) { + // handling pipelined request sends HttpConnection instance (implements Runnable) to executor + // while scope from the previous request is still active + builder.ignoreTaskClass("org.eclipse.jetty.server.internal.HttpConnection"); + } +} diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12InstrumentationModule.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12InstrumentationModule.java new file mode 100644 index 000000000000..7986a86bce78 --- /dev/null +++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12InstrumentationModule.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jetty.v12_0; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static java.util.Collections.singletonList; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.List; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumentationModule.class) +public class Jetty12InstrumentationModule extends InstrumentationModule { + + public Jetty12InstrumentationModule() { + super("jetty", "jetty-12.0"); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + return hasClassesNamed("org.eclipse.jetty.server.Request$Handler"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new Jetty12ServerInstrumentation()); + } +} diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ResponseMutator.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ResponseMutator.java new file mode 100644 index 000000000000..9a99adc1efa4 --- /dev/null +++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ResponseMutator.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jetty.v12_0; + +import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator; +import org.eclipse.jetty.server.Response; + +public enum Jetty12ResponseMutator implements HttpServerResponseMutator { + INSTANCE; + + @Override + public void appendHeader(Response response, String name, String value) { + response.getHeaders().add(name, value); + } +} diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ServerInstrumentation.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ServerInstrumentation.java new file mode 100644 index 000000000000..b0efc09e4e76 --- /dev/null +++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ServerInstrumentation.java @@ -0,0 +1,82 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jetty.v12_0; + +import static io.opentelemetry.javaagent.instrumentation.jetty.v12_0.Jetty12Singletons.helper; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; + +class Jetty12ServerInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("org.eclipse.jetty.server.Server"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("handle") + .and(takesArgument(0, named("org.eclipse.jetty.server.Request"))) + .and(takesArgument(1, named("org.eclipse.jetty.server.Response"))) + .and(takesArgument(2, named("org.eclipse.jetty.util.Callback"))) + .and(isPublic()), + this.getClass().getName() + "$HandlerAdvice"); + } + + @SuppressWarnings("unused") + public static class HandlerAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Object source, + @Advice.Argument(0) Request request, + @Advice.Argument(1) Response response, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + + Context parentContext = Java8BytecodeBridge.currentContext(); + if (!helper().shouldStart(parentContext, request)) { + return; + } + + context = helper().start(parentContext, request, response); + scope = context.makeCurrent(); + + HttpServerResponseCustomizerHolder.getCustomizer() + .customize(context, response, Jetty12ResponseMutator.INSTANCE); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Argument(0) Request request, + @Advice.Argument(1) Response response, + @Advice.Thrown Throwable throwable, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + if (scope == null) { + return; + } + scope.close(); + if (throwable != null) { + helper().end(context, request, response, throwable); + } + } + } +} diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Singletons.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Singletons.java new file mode 100644 index 000000000000..d51e70378a14 --- /dev/null +++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Singletons.java @@ -0,0 +1,71 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jetty.v12_0; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpExperimentalAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpServerExperimentalMetrics; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractor; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerMetrics; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute; +import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor; +import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor; +import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; +import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; + +public final class Jetty12Singletons { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jetty-12.0"; + + private static final Instrumenter INSTRUMENTER; + + static { + Jetty12HttpAttributesGetter httpAttributesGetter = new Jetty12HttpAttributesGetter(); + + InstrumenterBuilder builder = + Instrumenter.builder( + GlobalOpenTelemetry.get(), + INSTRUMENTATION_NAME, + HttpSpanNameExtractor.builder(httpAttributesGetter) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) + .build()) + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) + .addAttributesExtractor( + HttpServerAttributesExtractor.builder(httpAttributesGetter) + .setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders()) + .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) + .build()) + .addContextCustomizer( + HttpServerRoute.builder(httpAttributesGetter) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) + .build()) + .addContextCustomizer( + (context, request, attributes) -> + new AppServerBridge.Builder() + .captureServletAttributes() + .recordException() + .init(context)) + .addOperationMetrics(HttpServerMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpServerTelemetry()) { + builder + .addAttributesExtractor(HttpExperimentalAttributesExtractor.create(httpAttributesGetter)) + .addOperationMetrics(HttpServerExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildServerInstrumenter(Jetty12TextMapGetter.INSTANCE); + } + + private static final Jetty12Helper HELPER = new Jetty12Helper(INSTRUMENTER); + + public static Jetty12Helper helper() { + return HELPER; + } + + private Jetty12Singletons() {} +} diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12TextMapGetter.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12TextMapGetter.java new file mode 100644 index 000000000000..59e9248eb9b5 --- /dev/null +++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12TextMapGetter.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jetty.v12_0; + +import io.opentelemetry.context.propagation.TextMapGetter; +import org.eclipse.jetty.server.Request; + +enum Jetty12TextMapGetter implements TextMapGetter { + INSTANCE; + + @Override + public Iterable keys(Request carrier) { + return carrier.getHeaders().getFieldNamesCollection(); + } + + @Override + public String get(Request carrier, String key) { + return carrier.getHeaders().get(key); + } +} diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12HandlerTest.java b/instrumentation/jetty/jetty-12.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12HandlerTest.java new file mode 100644 index 000000000000..7087b84d2d87 --- /dev/null +++ b/instrumentation/jetty/jetty-12.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12HandlerTest.java @@ -0,0 +1,137 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jetty.v12_0; + +import static io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions.DEFAULT_HTTP_ATTRIBUTES; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +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.NOT_FOUND; +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 org.assertj.core.api.Assertions.assertThat; + +import com.google.common.collect.Sets; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest; +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.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.semconv.SemanticAttributes; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.Callback; +import org.junit.jupiter.api.extension.RegisterExtension; + +class Jetty12HandlerTest extends AbstractHttpServerTest { + + @RegisterExtension + static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent(); + + private final TestHandler testHandler = new TestHandler(); + + @Override + protected Server setupServer() throws Exception { + Server server = new Server(port); + server.setHandler(testHandler); + server.start(); + return server; + } + + @Override + protected void stopServer(Server server) throws Exception { + server.stop(); + } + + @Override + protected void configure(HttpServerTestOptions options) { + options.setHttpAttributes( + unused -> + Sets.difference( + DEFAULT_HTTP_ATTRIBUTES, Collections.singleton(SemanticAttributes.HTTP_ROUTE))); + options.setExpectedException(new IllegalStateException(EXCEPTION.getBody())); + options.setHasResponseCustomizer(endpoint -> endpoint != EXCEPTION); + } + + @Override + protected SpanDataAssert assertResponseSpan( + SpanDataAssert span, String method, ServerEndpoint endpoint) { + if (endpoint == REDIRECT) { + span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendRedirect")); + } else if (endpoint == ERROR) { + span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendError")); + } + span.hasKind(SpanKind.INTERNAL).hasAttributesSatisfying(Attributes::isEmpty); + return span; + } + + private void handleRequest(Request request, Response response) { + ServerEndpoint endpoint = ServerEndpoint.forPath(request.getHttpURI().getPath()); + controller( + endpoint, + () -> { + try { + response(request, response, endpoint); + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; + }); + } + + private void response(Request request, Response response, ServerEndpoint endpoint) + throws IOException { + if (SUCCESS.equals(endpoint)) { + response.setStatus(endpoint.getStatus()); + response.write(true, StandardCharsets.UTF_8.encode(endpoint.getBody()), Callback.NOOP); + } else if (QUERY_PARAM.equals(endpoint)) { + response.setStatus(endpoint.getStatus()); + response.write( + true, StandardCharsets.UTF_8.encode(request.getHttpURI().getQuery()), Callback.NOOP); + } else if (REDIRECT.equals(endpoint)) { + response.setStatus(endpoint.getStatus()); + response.getHeaders().add("Location", "http://localhost:" + port + endpoint.getBody()); + } else if (ERROR.equals(endpoint)) { + response.setStatus(endpoint.getStatus()); + response.write(true, StandardCharsets.UTF_8.encode(endpoint.getBody()), Callback.NOOP); + } else if (CAPTURE_HEADERS.equals(endpoint)) { + response.getHeaders().add("X-Test-Response", request.getHeaders().get("X-Test-Request")); + response.setStatus(endpoint.getStatus()); + response.write(true, StandardCharsets.UTF_8.encode(endpoint.getBody()), Callback.NOOP); + } else if (EXCEPTION.equals(endpoint)) { + throw new IllegalStateException(endpoint.getBody()); + } else if (INDEXED_CHILD.equals(endpoint)) { + INDEXED_CHILD.collectSpanAttributes( + name -> Request.extractQueryParameters(request).getValue(name)); + response.setStatus(endpoint.getStatus()); + response.write(true, StandardCharsets.UTF_8.encode(endpoint.getBody()), Callback.NOOP); + } else { + response.setStatus(NOT_FOUND.getStatus()); + response.write(true, StandardCharsets.UTF_8.encode(NOT_FOUND.getBody()), Callback.NOOP); + } + } + + private class TestHandler extends Handler.Abstract { + + @Override + public boolean handle(Request baseRequest, Response response, Callback callback) { + handleRequest(baseRequest, response); + + callback.succeeded(); + return true; + } + } +} diff --git a/instrumentation/jetty/jetty-8.0/javaagent/build.gradle.kts b/instrumentation/jetty/jetty-8.0/javaagent/build.gradle.kts index dbdb36ea17c0..49d4649d3603 100644 --- a/instrumentation/jetty/jetty-8.0/javaagent/build.gradle.kts +++ b/instrumentation/jetty/jetty-8.0/javaagent/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { testInstrumentation(project(":instrumentation:servlet:servlet-javax-common:javaagent")) testInstrumentation(project(":instrumentation:jetty:jetty-11.0:javaagent")) + testInstrumentation(project(":instrumentation:jetty:jetty-12.0:javaagent")) testLibrary("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901") diff --git a/instrumentation/jetty/jetty-8.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/JettyHandlerTest.java b/instrumentation/jetty/jetty-8.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/JettyHandlerTest.java index c532d9309b98..912875c09cd3 100644 --- a/instrumentation/jetty/jetty-8.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/JettyHandlerTest.java +++ b/instrumentation/jetty/jetty-8.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/JettyHandlerTest.java @@ -40,7 +40,7 @@ import org.eclipse.jetty.server.handler.ErrorHandler; import org.junit.jupiter.api.extension.RegisterExtension; -public class JettyHandlerTest extends AbstractHttpServerTest { +class JettyHandlerTest extends AbstractHttpServerTest { @RegisterExtension static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent(); @@ -62,25 +62,17 @@ protected void handleErrorPage( private static final TestHandler testHandler = new TestHandler(); @Override - protected Server setupServer() { + protected Server setupServer() throws Exception { Server server = new Server(port); server.setHandler(testHandler); server.addBean(errorHandler); - try { - server.start(); - } catch (Exception e) { - throw new RuntimeException(e); - } + server.start(); return server; } @Override - protected void stopServer(Server server) { - try { - server.stop(); - } catch (Exception e) { - throw new RuntimeException(e); - } + protected void stopServer(Server server) throws Exception { + server.stop(); } @Override diff --git a/instrumentation/servlet/servlet-5.0/javaagent/build.gradle.kts b/instrumentation/servlet/servlet-5.0/javaagent/build.gradle.kts index c1ccbd8d351e..0f7d1f12a242 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/build.gradle.kts +++ b/instrumentation/servlet/servlet-5.0/javaagent/build.gradle.kts @@ -17,7 +17,9 @@ dependencies { compileOnly("jakarta.servlet:jakarta.servlet-api:5.0.0") - testImplementation(project(":instrumentation:servlet:servlet-common:bootstrap")) + testInstrumentation(project(":instrumentation:jetty:jetty-11.0:javaagent")) + + testImplementation(project(":instrumentation:servlet:servlet-5.0:testing")) testLibrary("org.eclipse.jetty:jetty-server:11.0.0") testLibrary("org.eclipse.jetty:jetty-servlet:11.0.0") diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServlet5Test.groovy b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServlet5Test.groovy index f9825014e63f..d71f8ab8eab6 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServlet5Test.groovy +++ b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServlet5Test.groovy @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import io.opentelemetry.javaagent.instrumentation.servlet.v5_0.RequestDispatcherServlet import jakarta.servlet.Servlet import jakarta.servlet.ServletException import jakarta.servlet.http.HttpServletRequest @@ -10,6 +11,8 @@ import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.handler.ErrorHandler import org.eclipse.jetty.servlet.ServletContextHandler import spock.lang.IgnoreIf +import test.AbstractServlet5Test +import test.TestServlet5 import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS @@ -120,6 +123,13 @@ class JettyServlet5TestAsync extends JettyServlet5Test { boolean errorEndpointUsesSendError() { false } + + @Override + String getMetricsInstrumentationName() { + // with async requests the span is started in one instrumentation (server instrumentation) + // but ended from another (servlet instrumentation) + "io.opentelemetry.servlet-5.0" + } } @IgnoreIf({ !jvm.java11Compatible }) @@ -129,6 +139,13 @@ class JettyServlet5TestFakeAsync extends JettyServlet5Test { Class servlet() { TestServlet5.FakeAsync } + + @Override + String getMetricsInstrumentationName() { + // with async requests the span is started in one instrumentation (server instrumentation) + // but ended from another (servlet instrumentation) + "io.opentelemetry.servlet-5.0" + } } @IgnoreIf({ !jvm.java11Compatible }) @@ -220,6 +237,13 @@ class JettyServlet5TestDispatchImmediate extends JettyDispatchTest { addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet5.DispatchImmediate) addServlet(context, "/dispatch/recursive", TestServlet5.DispatchRecursive) } + + @Override + String getMetricsInstrumentationName() { + // with async requests the span is started in one instrumentation (server instrumentation) + // but ended from another (servlet instrumentation) + "io.opentelemetry.servlet-5.0" + } } @IgnoreIf({ !jvm.java11Compatible }) @@ -251,6 +275,13 @@ class JettyServlet5TestDispatchAsync extends JettyDispatchTest { boolean errorEndpointUsesSendError() { false } + + @Override + String getMetricsInstrumentationName() { + // with async requests the span is started in one instrumentation (server instrumentation) + // but ended from another (servlet instrumentation) + "io.opentelemetry.servlet-5.0" + } } abstract class JettyDispatchTest extends JettyServlet5Test { diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServletHandlerTest.groovy b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServletHandlerTest.groovy index 126deeb24b67..98eba9d1ed2c 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServletHandlerTest.groovy +++ b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServletHandlerTest.groovy @@ -13,6 +13,8 @@ import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.handler.ErrorHandler import org.eclipse.jetty.servlet.ServletHandler import spock.lang.IgnoreIf +import test.AbstractServlet5Test +import test.TestServlet5 import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy index e59fdb615ba1..51375320b93e 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy +++ b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy @@ -5,6 +5,7 @@ import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint +import io.opentelemetry.javaagent.instrumentation.servlet.v5_0.RequestDispatcherServlet import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse import jakarta.servlet.Servlet import jakarta.servlet.ServletException @@ -20,6 +21,8 @@ import org.apache.tomcat.JarScanFilter import org.apache.tomcat.JarScanType import spock.lang.Shared import spock.lang.Unroll +import test.AbstractServlet5Test +import test.TestServlet5 import java.nio.file.Files import java.util.concurrent.TimeUnit diff --git a/instrumentation/servlet/servlet-5.0/jetty12-testing/build.gradle.kts b/instrumentation/servlet/servlet-5.0/jetty12-testing/build.gradle.kts new file mode 100644 index 000000000000..610d0ef50601 --- /dev/null +++ b/instrumentation/servlet/servlet-5.0/jetty12-testing/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("otel.javaagent-testing") +} + +dependencies { + library("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.6") + + testInstrumentation(project(":instrumentation:servlet:servlet-5.0:javaagent")) + testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent")) + testInstrumentation(project(":instrumentation:jetty:jetty-11.0:javaagent")) + testInstrumentation(project(":instrumentation:jetty:jetty-12.0:javaagent")) + + testImplementation(project(":instrumentation:servlet:servlet-5.0:testing")) +} + +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) +} + +tasks { + withType().configureEach { + jvmArgs("-Dotel.instrumentation.servlet.experimental.capture-request-parameters=test-parameter") + } +} diff --git a/instrumentation/servlet/servlet-5.0/jetty12-testing/src/test/groovy/Jetty12Servlet5Test.groovy b/instrumentation/servlet/servlet-5.0/jetty12-testing/src/test/groovy/Jetty12Servlet5Test.groovy new file mode 100644 index 000000000000..545c1005e761 --- /dev/null +++ b/instrumentation/servlet/servlet-5.0/jetty12-testing/src/test/groovy/Jetty12Servlet5Test.groovy @@ -0,0 +1,241 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.javaagent.instrumentation.servlet.v5_0.RequestDispatcherServlet +import jakarta.servlet.Servlet +import jakarta.servlet.ServletException +import org.eclipse.jetty.ee10.servlet.ServletContextHandler +import org.eclipse.jetty.server.Request +import org.eclipse.jetty.server.Response +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.util.Callback +import test.AbstractServlet5Test +import test.TestServlet5 + +import java.nio.charset.StandardCharsets + +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 + +abstract class Jetty12Servlet5Test extends AbstractServlet5Test { + + @Override + boolean testNotFound() { + false + } + + @Override + Throwable expectedException() { + new ServletException(EXCEPTION.body) + } + + @Override + Object startServer(int port) { + def jettyServer = new Server(port) + jettyServer.connectors.each { + it.setHost('localhost') + } + + ServletContextHandler servletContext = new ServletContextHandler(contextPath) + servletContext.errorHandler = new Request.Handler() { + + @Override + boolean handle(Request request, Response response, Callback callback) throws Exception { + String message = (String) request.getAttribute("org.eclipse.jetty.server.error_message") + if (message != null) { + response.write(true, StandardCharsets.UTF_8.encode(message), Callback.NOOP) + } + callback.succeeded() + return true + } + } + + setupServlets(servletContext) + jettyServer.setHandler(servletContext) + + jettyServer.start() + + return jettyServer + } + + @Override + void stopServer(Object serverObject) { + Server server = (Server) serverObject + server.stop() + server.destroy() + } + + @Override + String getContextPath() { + return "/jetty-context" + } + + @Override + void addServlet(Object handlerObject, String path, Class servlet) { + ServletContextHandler handler = (ServletContextHandler) handlerObject + handler.addServlet(servlet, path) + } +} + +class JettyServlet5TestSync extends Jetty12Servlet5Test { + + @Override + Class servlet() { + TestServlet5.Sync + } +} + +class JettyServlet5TestAsync extends Jetty12Servlet5Test { + + @Override + Class servlet() { + TestServlet5.Async + } + + @Override + boolean errorEndpointUsesSendError() { + false + } +} + +class JettyServlet5TestFakeAsync extends Jetty12Servlet5Test { + + @Override + Class servlet() { + TestServlet5.FakeAsync + } +} + +class JettyServlet5TestForward extends JettyDispatchTest { + @Override + Class servlet() { + TestServlet5.Sync // dispatch to sync servlet + } + + @Override + protected void setupServlets(Object context) { + super.setupServlets(context) + + addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Forward) + } +} + +class JettyServlet5TestInclude extends JettyDispatchTest { + @Override + Class servlet() { + TestServlet5.Sync // dispatch to sync servlet + } + + @Override + boolean testRedirect() { + false + } + + @Override + boolean testCapturedHttpHeaders() { + false + } + + @Override + boolean testError() { + false + } + + @Override + protected void setupServlets(Object context) { + super.setupServlets(context) + + addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Include) + } +} + + +class JettyServlet5TestDispatchImmediate extends JettyDispatchTest { + @Override + Class servlet() { + TestServlet5.Sync + } + + @Override + protected void setupServlets(Object context) { + super.setupServlets(context) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet5.DispatchImmediate) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet5.DispatchImmediate) + addServlet(context, "/dispatch" + SUCCESS.path, TestServlet5.DispatchImmediate) + addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet5.DispatchImmediate) + addServlet(context, "/dispatch" + ERROR.path, TestServlet5.DispatchImmediate) + addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet5.DispatchImmediate) + addServlet(context, "/dispatch" + REDIRECT.path, TestServlet5.DispatchImmediate) + addServlet(context, "/dispatch" + AUTH_REQUIRED.path, TestServlet5.DispatchImmediate) + addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet5.DispatchImmediate) + addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet5.DispatchImmediate) + addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet5.DispatchImmediate) + addServlet(context, "/dispatch/recursive", TestServlet5.DispatchRecursive) + } +} + +class JettyServlet5TestDispatchAsync extends JettyDispatchTest { + @Override + Class servlet() { + TestServlet5.Async + } + + @Override + protected void setupServlets(Object context) { + super.setupServlets(context) + + addServlet(context, "/dispatch" + SUCCESS.path, TestServlet5.DispatchAsync) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet5.DispatchAsync) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet5.DispatchAsync) + addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet5.DispatchAsync) + addServlet(context, "/dispatch" + ERROR.path, TestServlet5.DispatchAsync) + addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet5.DispatchAsync) + addServlet(context, "/dispatch" + REDIRECT.path, TestServlet5.DispatchAsync) + addServlet(context, "/dispatch" + AUTH_REQUIRED.path, TestServlet5.DispatchAsync) + addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet5.DispatchAsync) + addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet5.DispatchAsync) + addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet5.DispatchAsync) + addServlet(context, "/dispatch/recursive", TestServlet5.DispatchRecursive) + } + + @Override + boolean errorEndpointUsesSendError() { + false + } +} + +abstract class JettyDispatchTest extends Jetty12Servlet5Test { + @Override + URI buildAddress() { + return new URI("http://localhost:$port$contextPath/dispatch/") + } +} diff --git a/instrumentation/servlet/servlet-5.0/testing/build.gradle.kts b/instrumentation/servlet/servlet-5.0/testing/build.gradle.kts new file mode 100644 index 000000000000..8066fc7218b7 --- /dev/null +++ b/instrumentation/servlet/servlet-5.0/testing/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("otel.java-conventions") +} + +dependencies { + api(project(":testing-common")) + api(project(":instrumentation:servlet:servlet-common:bootstrap")) + + compileOnly("jakarta.servlet:jakarta.servlet-api:5.0.0") +} diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/AbstractServlet5Test.groovy b/instrumentation/servlet/servlet-5.0/testing/src/main/groovy/test/AbstractServlet5Test.groovy similarity index 99% rename from instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/AbstractServlet5Test.groovy rename to instrumentation/servlet/servlet-5.0/testing/src/main/groovy/test/AbstractServlet5Test.groovy index 995033e14fde..f3ef78ac9274 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/AbstractServlet5Test.groovy +++ b/instrumentation/servlet/servlet-5.0/testing/src/main/groovy/test/AbstractServlet5Test.groovy @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +package test + import io.opentelemetry.api.trace.SpanKind import io.opentelemetry.instrumentation.api.internal.HttpConstants import io.opentelemetry.instrumentation.test.AgentTestTrait diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TestServlet5.groovy b/instrumentation/servlet/servlet-5.0/testing/src/main/groovy/test/TestServlet5.groovy similarity index 99% rename from instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TestServlet5.groovy rename to instrumentation/servlet/servlet-5.0/testing/src/main/groovy/test/TestServlet5.groovy index 68b167184ff9..48f60f121e74 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TestServlet5.groovy +++ b/instrumentation/servlet/servlet-5.0/testing/src/main/groovy/test/TestServlet5.groovy @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +package test + import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import jakarta.servlet.RequestDispatcher diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/test/java/RequestDispatcherServlet.java b/instrumentation/servlet/servlet-5.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/RequestDispatcherServlet.java similarity index 92% rename from instrumentation/servlet/servlet-5.0/javaagent/src/test/java/RequestDispatcherServlet.java rename to instrumentation/servlet/servlet-5.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/RequestDispatcherServlet.java index d36a7b2284f2..5ef11573b883 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/test/java/RequestDispatcherServlet.java +++ b/instrumentation/servlet/servlet-5.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/RequestDispatcherServlet.java @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +package io.opentelemetry.javaagent.instrumentation.servlet.v5_0; + import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletContext; @@ -14,12 +16,11 @@ import java.io.IOException; public class RequestDispatcherServlet { - /* There's something about the getRequestDispatcher call that breaks horribly when these classes - * are written in groovy. - */ @WebServlet(asyncSupported = true) public static class Forward extends HttpServlet { + private static final long serialVersionUID = 1L; + @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { @@ -32,6 +33,8 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) @WebServlet(asyncSupported = true) public static class Include extends HttpServlet { + private static final long serialVersionUID = 1L; + @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHelper.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHelper.java index 55dcec6ece91..5df62a1f626d 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHelper.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHelper.java @@ -14,7 +14,7 @@ public class ServletHelper extends BaseServletHelper