JAX-RS ApplicationPath annotation (#2824)

This commit is contained in:
Lauri Tulmin 2021-04-21 19:07:54 +03:00 committed by GitHub
parent c4071c1c94
commit a3c0b44b31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 845 additions and 89 deletions

View File

@ -0,0 +1,22 @@
ext {
skipPublish = true
}
apply from: "$rootDir/gradle/java.gradle"
// add repo for org.gradle:gradle-tooling-api which org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-gradle-depchain depends on
repositories {
maven { url 'https://repo.gradle.org/gradle/libs-releases' }
}
dependencies {
compileOnly "javax:javaee-api:7.0"
api project(':testing-common')
implementation deps.opentelemetryApi
def arquillianVersion = '1.4.0.Final'
implementation "org.jboss.arquillian.junit:arquillian-junit-container:${arquillianVersion}"
implementation "org.jboss.arquillian.protocol:arquillian-protocol-servlet:${arquillianVersion}"
implementation 'org.jboss.arquillian.spock:arquillian-spock-container:1.0.0.CR1'
api "org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-gradle-depchain:3.1.3"
}

View File

@ -0,0 +1,82 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import static io.opentelemetry.api.trace.SpanKind.SERVER
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import io.opentelemetry.instrumentation.test.utils.OkHttpUtils
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jboss.arquillian.container.test.api.Deployment
import org.jboss.arquillian.container.test.api.RunAsClient
import org.jboss.arquillian.spock.ArquillianSputnik
import org.jboss.arquillian.test.api.ArquillianResource
import org.jboss.shrinkwrap.api.ShrinkWrap
import org.jboss.shrinkwrap.api.asset.EmptyAsset
import org.jboss.shrinkwrap.api.spec.WebArchive
import org.junit.runner.RunWith
import spock.lang.Unroll
import test.CdiRestResource
import test.EjbRestResource
import test.RestApplication
@RunWith(ArquillianSputnik)
@RunAsClient
abstract class ArquillianRestTest extends AgentInstrumentationSpecification {
static OkHttpClient client = OkHttpUtils.client()
@ArquillianResource
static URI url
@Deployment
static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive)
.addClass(RestApplication)
.addClass(CdiRestResource)
.addClass(EjbRestResource)
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
}
def getContextRoot() {
return url.getPath()
}
@Unroll
def "test #path"() {
when:
Request request = new Request.Builder().url(HttpUrl.get(url.resolve(path))).build()
Response response = client.newCall(request).execute()
then:
response.withCloseable {
assert response.code() == 200
assert response.body().string() == "hello"
true
}
and:
assertTraces(1) {
trace(0, 2) {
span(0) {
name getContextRoot() + path
kind SERVER
hasNoParent()
}
span(1) {
name className + ".hello"
childOf span(0)
}
}
}
where:
path | className
"rest-app/cdiHello" | "CdiRestResource"
"rest-app/ejbHello" | "EjbRestResource"
}
}

View File

@ -11,7 +11,7 @@ import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/")
@ApplicationPath("/rest-app/")
public class RestApplication extends Application {
@Override

View File

@ -0,0 +1,15 @@
<arquillian xmlns="http://jboss.org/schema/arquillian"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.org/schema/arquillian
http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
<defaultProtocol type="Servlet 3.0" />
<container qualifier="wildfly-embedded" default="true">
<configuration>
<property name="jbossHome">build/server/wildfly-18.0.0.Final</property>
<property name="modulePath">build/server/wildfly-18.0.0.Final/modules</property>
</configuration>
</container>
</arquillian>

View File

@ -14,6 +14,7 @@ import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
import io.opentelemetry.instrumentation.api.tracer.ServerSpan;
import io.opentelemetry.javaagent.instrumentation.api.ClassHierarchyIterable;
import io.opentelemetry.javaagent.instrumentation.api.jaxrs.JaxrsContextPath;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
@ -66,6 +67,7 @@ public class JaxRsAnnotationsTracer extends BaseTracer {
// empty when method and class don't have a jax-rs path annotation, this can happen when
// creating an "abort" span, see RequestContextHelper.
if (!pathBasedSpanName.isEmpty()) {
pathBasedSpanName = JaxrsContextPath.prepend(context, pathBasedSpanName);
pathBasedSpanName = ServletContextPath.prepend(context, pathBasedSpanName);
}
if (serverSpan == null) {

View File

@ -7,11 +7,20 @@ muzzle {
group = "org.apache.cxf"
module = "cxf-rt-frontend-jaxrs"
versions = "[3.2,)"
extraDependency "javax.servlet:javax.servlet-api:3.1.0"
}
pass {
group = "org.apache.tomee"
module = "openejb-cxf-rs"
// earlier versions of tomee use cxf older than 3.2
versions = "(8,)"
extraDependency "javax.servlet:javax.servlet-api:3.1.0"
}
}
dependencies {
compileOnly group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.0'
compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'
library group: 'org.apache.cxf', name: 'cxf-rt-frontend-jaxrs', version: '3.2.0'
implementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent')
@ -21,6 +30,7 @@ dependencies {
testImplementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-testing')
testImplementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.3'
testImplementation group: 'org.eclipse.jetty', name: 'jetty-webapp', version: '9.4.6.v20170531'
testLibrary group: 'org.apache.cxf', name: 'cxf-rt-transports-http-jetty', version: '3.2.0'
testLibrary group: 'org.apache.cxf', name: 'cxf-rt-ws-policy', version: '3.2.0'

View File

@ -5,10 +5,11 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static java.util.Arrays.asList;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.Collections;
import java.util.List;
@AutoService(InstrumentationModule.class)
@ -19,6 +20,9 @@ public class CxfInstrumentationModule extends InstrumentationModule {
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new CxfRequestContextInstrumentation());
return asList(
new CxfRequestContextInstrumentation(),
new CxfServletControllerInstrumentation(),
new CxfRsHttpListenerInstrumentation());
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.instrumentation.api.jaxrs.JaxrsContextPath;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
// TomEE specific instrumentation
public class CxfRsHttpListenerInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.apache.openejb.server.cxf.rs.CxfRsHttpListener");
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isMethod().and(named("doInvoke")),
CxfRsHttpListenerInstrumentation.class.getName() + "$InvokeAdvice");
}
public static class InvokeAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.FieldValue("pattern") String pattern, @Advice.Local("otelScope") Scope scope) {
Context context = JaxrsContextPath.init(Java8BytecodeBridge.currentContext(), pattern);
if (context != null) {
scope = context.makeCurrent();
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(@Advice.Local("otelScope") Scope scope) {
if (scope != null) {
scope.close();
}
}
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
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.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.instrumentation.api.jaxrs.JaxrsContextPath;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.Collections;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
public class CxfServletControllerInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.apache.cxf.transport.servlet.ServletController");
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return Collections.singletonMap(
isMethod()
.and(isPublic())
.and(named("invokeDestination"))
.and(takesArgument(0, named("javax.servlet.http.HttpServletRequest"))),
CxfServletControllerInstrumentation.class.getName() + "$InvokeAdvice");
}
public static class InvokeAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) HttpServletRequest httpServletRequest,
@Advice.Local("otelScope") Scope scope) {
Context context =
JaxrsContextPath.init(
Java8BytecodeBridge.currentContext(), httpServletRequest.getServletPath());
if (context != null) {
scope = context.makeCurrent();
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(@Advice.Local("otelScope") Scope scope) {
if (scope != null) {
scope.close();
}
}
}
}

View File

@ -8,6 +8,7 @@ import javax.ws.rs.core.Response
import javax.ws.rs.ext.ExceptionMapper
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean
import org.apache.cxf.endpoint.Server
import test.JaxRsTestApplication
class CxfHttpServerTest extends JaxRsHttpServerTest<Server> {

View File

@ -0,0 +1,8 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
class CxfJettyHttpServerTest extends JaxRsJettyHttpServerTest {
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" metadata-complete="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<servlet>
<servlet-name>CXFNonSpringJaxrsServlet</servlet-name>
<servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>test.JaxRsApplicationPathTestApplication</param-value>
</init-param>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>CXFNonSpringJaxrsServlet</servlet-name>
<url-pattern>/rest-app/*</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -7,12 +7,21 @@ muzzle {
group = "org.glassfish.jersey.core"
module = "jersey-server"
versions = "[2.0,3.0.0)"
extraDependency "javax.servlet:javax.servlet-api:3.1.0"
}
pass {
group = "org.glassfish.jersey.containers"
module = "jersey-container-servlet"
versions = "[2.0,3.0.0)"
extraDependency "javax.servlet:javax.servlet-api:3.1.0"
}
}
dependencies {
compileOnly group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.0'
compileOnly group: 'org.glassfish.jersey.core', name: 'jersey-server', version: '2.0'
compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'
library group: 'org.glassfish.jersey.core', name: 'jersey-server', version: '2.0'
library group: 'org.glassfish.jersey.containers', name: 'jersey-container-servlet', version: '2.0'
implementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent')
@ -20,6 +29,7 @@ dependencies {
testInstrumentation project(':instrumentation:servlet:servlet-javax-common:javaagent')
testImplementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-testing')
testImplementation group: 'org.eclipse.jetty', name: 'jetty-webapp', version: '8.0.0.v20110901'
// First version with DropwizardTestSupport:
testLibrary group: 'io.dropwizard', name: 'dropwizard-testing', version: '0.8.0'

View File

@ -5,10 +5,11 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static java.util.Arrays.asList;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.Collections;
import java.util.List;
@AutoService(InstrumentationModule.class)
@ -19,6 +20,7 @@ public class JerseyInstrumentationModule extends InstrumentationModule {
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new JerseyRequestContextInstrumentation());
return asList(
new JerseyRequestContextInstrumentation(), new JerseyServletContainerInstrumentation());
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
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.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.instrumentation.api.jaxrs.JaxrsContextPath;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
public class JerseyServletContainerInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.glassfish.jersey.servlet.ServletContainer");
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isMethod()
.and(named("service"))
.and(takesArgument(0, named("javax.servlet.http.HttpServletRequest")))
.and(takesArgument(1, named("javax.servlet.http.HttpServletResponse"))),
JerseyServletContainerInstrumentation.class.getName() + "$ServiceAdvice");
}
public static class ServiceAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) HttpServletRequest httpServletRequest,
@Advice.Local("otelScope") Scope scope) {
Context context =
JaxrsContextPath.init(
Java8BytecodeBridge.currentContext(), httpServletRequest.getServletPath());
if (context != null) {
scope = context.makeCurrent();
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(@Advice.Local("otelScope") Scope scope) {
if (scope != null) {
scope.close();
}
}
}
}

View File

@ -8,6 +8,7 @@ import org.eclipse.jetty.server.Server
import org.eclipse.jetty.servlet.ServletContextHandler
import org.glassfish.jersey.server.ResourceConfig
import org.glassfish.jersey.servlet.ServletContainer
import test.JaxRsTestApplication
class JerseyHttpServerTest extends JaxRsHttpServerTest<Server> {

View File

@ -0,0 +1,12 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
class JerseyJettyHttpServerTest extends JaxRsJettyHttpServerTest {
@Override
boolean asyncCancelHasSendError() {
true
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import javax.servlet.ServletContextEvent
import javax.servlet.ServletContextListener
import org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer
import test.JaxRsApplicationPathTestApplication
// ServletContainerInitializer isn't automatically called due to the way this test is set up
// so we call it ourself
class JerseyStartupListener implements ServletContextListener {
@Override
void contextInitialized(ServletContextEvent servletContextEvent) {
new JerseyServletContainerInitializer().onStartup(Collections.singleton(JaxRsApplicationPathTestApplication),
servletContextEvent.getServletContext())
}
@Override
void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" metadata-complete="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<listener>
<listener-class>JerseyStartupListener</listener-class>
</listener>
</web-app>

View File

@ -0,0 +1,20 @@
ext {
skipPublish = true
}
apply from: "$rootDir/gradle/instrumentation.gradle"
// add repo for org.gradle:gradle-tooling-api which org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-gradle-depchain
// which is used by jaxrs-2.0-arquillian-testing depends on
repositories {
maven { url 'https://repo.gradle.org/gradle/libs-releases' }
}
dependencies {
testImplementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-arquillian-testing')
testImplementation "fish.payara.arquillian:arquillian-payara-server-embedded:2.4.1"
testImplementation 'fish.payara.extras:payara-embedded-web:5.2021.2'
testInstrumentation project(':instrumentation:servlet:servlet-3.0:javaagent')
testInstrumentation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent')
testInstrumentation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-jersey-2.0:javaagent')
}

View File

@ -0,0 +1,7 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
class PayaraRestTest extends ArquillianRestTest {
}

View File

@ -0,0 +1,14 @@
<arquillian xmlns="http://jboss.org/schema/arquillian"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.org/schema/arquillian
http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
<defaultProtocol type="Servlet 3.0" />
<container qualifier="payara-embedded" default="true">
<configuration>
<!-- by default runs on port 8181 -->
</configuration>
</container>
</arquillian>

View File

@ -30,11 +30,13 @@ dependencies {
testInstrumentation project(':instrumentation:servlet:servlet-javax-common:javaagent')
testImplementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-testing')
testImplementation group: 'org.eclipse.jetty', name: 'jetty-webapp', version: '8.0.0.v20110901'
testLibrary(group: 'org.jboss.resteasy', name: 'resteasy-undertow', version: '3.0.4.Final') {
exclude group: 'org.jboss.resteasy', module: 'resteasy-client'
}
testLibrary group: 'io.undertow', name: 'undertow-servlet', version: '1.0.0.Final'
testLibrary group: 'org.jboss.resteasy', name: 'resteasy-servlet-initializer', version: '3.0.4.Final'
latestDepTestLibrary group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.+'
latestDepTestLibrary(group: 'org.jboss.resteasy', name: 'resteasy-undertow', version: '3.+') {

View File

@ -5,10 +5,11 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static java.util.Arrays.asList;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.Collections;
import java.util.List;
@AutoService(InstrumentationModule.class)
@ -19,6 +20,8 @@ public class Resteasy30InstrumentationModule extends InstrumentationModule {
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new Resteasy30RequestContextInstrumentation());
return asList(
new Resteasy30RequestContextInstrumentation(),
new Resteasy30ServletContainerDispatcherInstrumentation());
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.instrumentation.api.jaxrs.JaxrsContextPath;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.Collections;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
public class Resteasy30ServletContainerDispatcherInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher");
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return Collections.singletonMap(
isMethod().and(named("service")),
Resteasy30ServletContainerDispatcherInstrumentation.class.getName() + "$ServiceAdvice");
}
public static class ServiceAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.FieldValue("servletMappingPrefix") String servletMappingPrefix,
@Advice.Local("otelScope") Scope scope) {
Context context =
JaxrsContextPath.init(Java8BytecodeBridge.currentContext(), servletMappingPrefix);
if (context != null) {
scope = context.makeCurrent();
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(@Advice.Local("otelScope") Scope scope) {
if (scope != null) {
scope.close();
}
}
}
}

View File

@ -5,6 +5,7 @@
import io.undertow.Undertow
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer
import test.JaxRsTestApplication
class ResteasyHttpServerTest extends JaxRsHttpServerTest<UndertowJaxrsServer> {

View File

@ -0,0 +1,8 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
class ResteasyJettyHttpServerTest extends JaxRsJettyHttpServerTest {
}

View File

@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import javax.servlet.ServletContextEvent
import javax.servlet.ServletContextListener
import org.jboss.resteasy.plugins.servlet.ResteasyServletInitializer
import test.JaxRsApplicationPathTestApplication
// ServletContainerInitializer isn't automatically called due to the way this test is set up
// so we call it ourself
class ResteasyStartupListener implements ServletContextListener {
@Override
void contextInitialized(ServletContextEvent servletContextEvent) {
new ResteasyServletInitializer().onStartup(Collections.singleton(JaxRsApplicationPathTestApplication),
servletContextEvent.getServletContext())
}
@Override
void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" metadata-complete="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<listener>
<listener-class>ResteasyStartupListener</listener-class>
</listener>
</web-app>

View File

@ -30,10 +30,12 @@ dependencies {
testInstrumentation project(':instrumentation:servlet:servlet-javax-common:javaagent')
testImplementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-testing')
testImplementation group: 'org.eclipse.jetty', name: 'jetty-webapp', version: '8.0.0.v20110901'
testLibrary(group: 'org.jboss.resteasy', name: 'resteasy-undertow', version: '3.1.0.Final') {
exclude group: 'org.jboss.resteasy', module: 'resteasy-client'
}
testLibrary group: 'org.jboss.resteasy', name: 'resteasy-servlet-initializer', version: '3.1.0.Final'
// artifact name changed from 'resteasy-jaxrs' to 'resteasy-core' starting from version 4.0.0
latestDepTestLibrary group: 'org.jboss.resteasy', name: 'resteasy-core', version: '+'

View File

@ -5,10 +5,11 @@
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static java.util.Arrays.asList;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.Collections;
import java.util.List;
@AutoService(InstrumentationModule.class)
@ -19,6 +20,8 @@ public class Resteasy31InstrumentationModule extends InstrumentationModule {
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new Resteasy31RequestContextInstrumentation());
return asList(
new Resteasy31RequestContextInstrumentation(),
new Resteasy31ServletContainerDispatcherInstrumentation());
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.instrumentation.api.jaxrs.JaxrsContextPath;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.Collections;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
public class Resteasy31ServletContainerDispatcherInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher");
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return Collections.singletonMap(
isMethod().and(named("service")),
Resteasy31ServletContainerDispatcherInstrumentation.class.getName() + "$ServiceAdvice");
}
public static class ServiceAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.FieldValue("servletMappingPrefix") String servletMappingPrefix,
@Advice.Local("otelScope") Scope scope) {
Context context =
JaxrsContextPath.init(Java8BytecodeBridge.currentContext(), servletMappingPrefix);
if (context != null) {
scope = context.makeCurrent();
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(@Advice.Local("otelScope") Scope scope) {
if (scope != null) {
scope.close();
}
}
}
}

View File

@ -5,6 +5,7 @@
import io.undertow.Undertow
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer
import test.JaxRsTestApplication
class ResteasyHttpServerTest extends JaxRsHttpServerTest<UndertowJaxrsServer> {

View File

@ -0,0 +1,8 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
class ResteasyJettyHttpServerTest extends JaxRsJettyHttpServerTest {
}

View File

@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import javax.servlet.ServletContextEvent
import javax.servlet.ServletContextListener
import org.jboss.resteasy.plugins.servlet.ResteasyServletInitializer
import test.JaxRsApplicationPathTestApplication
// ServletContainerInitializer isn't automatically called due to the way this test is set up
// so we call it ourself
class ResteasyStartupListener implements ServletContextListener {
@Override
void contextInitialized(ServletContextEvent servletContextEvent) {
new ResteasyServletInitializer().onStartup(Collections.singleton(JaxRsApplicationPathTestApplication),
servletContextEvent.getServletContext())
}
@Override
void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" metadata-complete="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<listener>
<listener-class>ResteasyStartupListener</listener-class>
</listener>
</web-app>

View File

@ -13,4 +13,6 @@ dependencies {
implementation project(':javaagent-api')
implementation project(':instrumentation-api')
implementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent')
compileOnly group: 'org.eclipse.jetty', name: 'jetty-webapp', version: '8.0.0.v20110901'
}

View File

@ -24,6 +24,7 @@ import okhttp3.HttpUrl
import okhttp3.Request
import okhttp3.Response
import spock.lang.Unroll
import test.JaxRsTestResource
abstract class JaxRsHttpServerTest<S> extends HttpServerTest<S> implements AgentTestTrait {
@Unroll
@ -225,7 +226,7 @@ abstract class JaxRsHttpServerTest<S> extends HttpServerTest<S> implements Agent
}
childOf((SpanData) parent)
attributes {
"${SemanticAttributes.CODE_NAMESPACE.key}" "JaxRsTestResource"
"${SemanticAttributes.CODE_NAMESPACE.key}" "test.JaxRsTestResource"
"${SemanticAttributes.CODE_FUNCTION.key}" methodName
if (isCancelled) {
"jaxrs.canceled" true

View File

@ -0,0 +1,41 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import static org.eclipse.jetty.util.resource.Resource.newResource
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.webapp.WebAppContext
class JaxRsJettyHttpServerTest extends JaxRsHttpServerTest<Server> {
@Override
Server startServer(int port) {
WebAppContext webAppContext = new WebAppContext()
webAppContext.setContextPath("/")
// set up test application
webAppContext.setBaseResource(newResource("src/test/webapp"))
def jettyServer = new Server(port)
jettyServer.connectors.each {
it.setHost('localhost')
}
jettyServer.setHandler(webAppContext)
jettyServer.start()
return jettyServer
}
@Override
void stopServer(Server server) {
server.stop()
server.destroy()
}
@Override
String getContextPath() {
"/rest-app"
}
}

View File

@ -3,6 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
package test
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM
@ -15,6 +17,7 @@ import io.opentelemetry.instrumentation.test.base.HttpServerTest
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionStage
import java.util.concurrent.CyclicBarrier
import javax.ws.rs.ApplicationPath
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.PathParam
@ -148,4 +151,8 @@ class JaxRsTestApplication extends Application {
classes.add(JaxRsTestExceptionMapper)
return classes
}
}
@ApplicationPath("/rest-app")
class JaxRsApplicationPathTestApplication extends JaxRsTestApplication {
}

View File

@ -0,0 +1,21 @@
ext {
skipPublish = true
}
apply from: "$rootDir/gradle/instrumentation.gradle"
// add repo for org.gradle:gradle-tooling-api which org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-gradle-depchain
// which is used by jaxrs-2.0-arquillian-testing depends on
repositories {
maven { url 'https://repo.gradle.org/gradle/libs-releases' }
}
dependencies {
testImplementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-arquillian-testing')
testImplementation "org.apache.tomee:arquillian-tomee-embedded:8.0.6"
testImplementation "org.apache.tomee:tomee-embedded:8.0.6"
testImplementation "org.apache.tomee:tomee-jaxrs:8.0.6"
testInstrumentation project(':instrumentation:servlet:servlet-3.0:javaagent')
testInstrumentation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent')
testInstrumentation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-cxf-3.2:javaagent')
}

View File

@ -0,0 +1,11 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import javax.enterprise.inject.Vetoed
// exclude this class from CDI as it causes NullPointerException when tomee is run with jdk8
@Vetoed
class TomeeRestTest extends ArquillianRestTest {
}

View File

@ -0,0 +1,15 @@
<arquillian xmlns="http://jboss.org/schema/arquillian"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.org/schema/arquillian
http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
<defaultProtocol type="Servlet 3.0" />
<container qualifier="tomee-embedded" default="true">
<configuration>
<property name="httpPort">-1</property>
<property name="stopPort">-1</property>
</configuration>
</container>
</arquillian>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</Pattern>
</layout>
</appender>
<root level="WARN">
<appender-ref ref="console"/>
</root>
<logger name="io.opentelemetry" level="debug"/>
</configuration>

View File

@ -3,6 +3,8 @@ ext {
}
apply from: "$rootDir/gradle/instrumentation.gradle"
// add repo for org.gradle:gradle-tooling-api which org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-gradle-depchain
// which is used by jaxrs-2.0-arquillian-testing depends on
repositories {
maven { url 'https://repo.gradle.org/gradle/libs-releases' }
}
@ -14,16 +16,12 @@ configurations {
dependencies {
testImplementation "javax:javaee-api:7.0"
def arquillianVersion = '1.4.0.Final'
testImplementation "org.jboss.arquillian.junit:arquillian-junit-container:${arquillianVersion}"
testImplementation "org.jboss.arquillian.protocol:arquillian-protocol-servlet:${arquillianVersion}"
testImplementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-arquillian-testing')
testImplementation "org.wildfly.arquillian:wildfly-arquillian-container-embedded:2.2.0.Final"
testImplementation 'org.jboss.arquillian.spock:arquillian-spock-container:1.0.0.CR1'
testImplementation "org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-gradle-depchain:3.1.3"
testImplementation "org.glassfish.jersey.core:jersey-client:2.8"
testInstrumentation project(':instrumentation:servlet:servlet-3.0:javaagent')
testInstrumentation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent')
testInstrumentation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-resteasy-3.0:javaagent')
// wildfly version used to run tests
testServer "org.wildfly:wildfly-dist:18.0.0.Final@zip"

View File

@ -3,75 +3,5 @@
* SPDX-License-Identifier: Apache-2.0
*/
import static io.opentelemetry.api.trace.SpanKind.SERVER
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import javax.ws.rs.client.Client
import javax.ws.rs.client.WebTarget
import org.glassfish.jersey.client.JerseyClientBuilder
import org.jboss.arquillian.container.test.api.Deployment
import org.jboss.arquillian.container.test.api.RunAsClient
import org.jboss.arquillian.spock.ArquillianSputnik
import org.jboss.arquillian.test.api.ArquillianResource
import org.jboss.shrinkwrap.api.ShrinkWrap
import org.jboss.shrinkwrap.api.asset.EmptyAsset
import org.jboss.shrinkwrap.api.spec.WebArchive
import org.junit.runner.RunWith
import test.CdiRestResource
import test.EjbRestResource
import test.RestApplication
@RunWith(ArquillianSputnik)
@RunAsClient
class WildflyRestTest extends AgentInstrumentationSpecification {
@ArquillianResource
static URI url
@Deployment
static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive)
.addClass(RestApplication)
.addClass(CdiRestResource)
.addClass(EjbRestResource)
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
}
def getContextRoot() {
return url.getPath()
}
def "test #path"() {
when:
Client client = JerseyClientBuilder.newClient()
WebTarget webTarget = client.target(url)
String result = webTarget.path(path)
.request()
.get()
.readEntity(String)
then:
result == "hello"
and:
assertTraces(1) {
trace(0, 2) {
span(0) {
name getContextRoot() + path
kind SERVER
hasNoParent()
}
span(1) {
name className + ".hello"
childOf span(0)
}
}
}
where:
path | className
"cdiHello" | "CdiRestResource"
"ejbHello" | "EjbRestResource"
}
class WildflyRestTest extends ArquillianRestTest {
}

View File

@ -0,0 +1,48 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.api.jaxrs;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Helper container for storing context path for jax-rs requests. Jax-rs context path is the path
* where jax-rs servlet is mapped or the value of ApplicationPath annotation. Span name is built by
* combining servlet context path from {@link
* io.opentelemetry.instrumentation.api.servlet.ServletContextPath} jax-rs context path and the Path
* annotation from called method or class.
*/
public final class JaxrsContextPath {
private static final ContextKey<String> CONTEXT_KEY =
ContextKey.named("opentelemetry-jaxrs-context-path-key");
private JaxrsContextPath() {}
public static @Nullable Context init(Context context, String path) {
if (path == null || path.isEmpty() || "/".equals(path)) {
return null;
}
// normalize path to have a leading slash and no trailing slash
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
return context.with(CONTEXT_KEY, path);
}
public static String prepend(Context context, String spanName) {
String value = context.get(CONTEXT_KEY);
// checking isEmpty just to avoid unnecessary string concat / allocation
if (value != null && !value.isEmpty()) {
return value + spanName;
} else {
return spanName;
}
}
}

View File

@ -190,6 +190,12 @@ public class GlobalIgnoresMatcher extends ElementMatcher.Junction.AbstractBase<T
|| name.contains(".asm.")
|| name.contains("$__sisu")
|| name.contains("$$EnhancerByProxool$$")
// glassfish ejb proxy
// We skip instrumenting these because some instrumentations e.g. jax-rs instrument methods
// that are annotated with @Path in an interface implemented by the class. We don't really
// want to instrument these methods in generated classes as this would create spans that
// have the generated class name in them instead of the actual class that handles the call.
|| name.contains("__EJB31_Generated__")
|| name.startsWith("org.springframework.core.$Proxy")) {
return true;
}

View File

@ -127,12 +127,15 @@ include ':instrumentation:http-url-connection:javaagent'
include ':instrumentation:hystrix-1.4:javaagent'
include ':instrumentation:java-httpclient:javaagent'
include ':instrumentation:jaxrs:jaxrs-1.0:javaagent'
include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-arquillian-testing'
include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent'
include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-cxf-3.2:javaagent'
include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-jersey-2.0:javaagent'
include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-payara-testing'
include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-resteasy-3.0:javaagent'
include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-resteasy-3.1:javaagent'
include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-testing'
include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-tomee-testing'
include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-wildfly-testing'
include ':instrumentation:jaxrs-client:jaxrs-client-1.1:javaagent'
include ':instrumentation:jaxrs-client:jaxrs-client-2.0:jaxrs-client-2.0-common:javaagent'

View File

@ -283,7 +283,7 @@ abstract class HttpServerTest<SERVER> extends InstrumentationSpecification imple
response.withCloseable {
assert response.code() == REDIRECT.status
assert response.header("location") == REDIRECT.body ||
response.header("location") == "${address.resolve(REDIRECT.body)}"
new URI(response.header("location")).normalize().toString() == "${address.resolve(REDIRECT.body)}"
true
}