Move helper class to spring package so that loadClass can find it (#3718)
* Move helper class to spring package so that loadClass can find it * spotless * Add tests * Add comments * remove unneeded dependency * comments
This commit is contained in:
parent
8d90462a28
commit
38c8f8940c
|
@ -0,0 +1,18 @@
|
||||||
|
plugins {
|
||||||
|
id("otel.javaagent-instrumentation")
|
||||||
|
}
|
||||||
|
|
||||||
|
muzzle {
|
||||||
|
pass {
|
||||||
|
group.set("org.springframework")
|
||||||
|
module.set("spring-web")
|
||||||
|
versions.set("[3.1.0.RELEASE,]")
|
||||||
|
// these versions depend on javax.faces:jsf-api:1.1 which was released as pom only
|
||||||
|
skip("1.2.1", "1.2.2", "1.2.3", "1.2.4")
|
||||||
|
assertInverse.set(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly("org.springframework:spring-web:3.1.0.RELEASE")
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.springweb;
|
||||||
|
|
||||||
|
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 SpringWebInstrumentationModule extends InstrumentationModule {
|
||||||
|
public SpringWebInstrumentationModule() {
|
||||||
|
super("spring-web", "spring-web-3.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
|
||||||
|
// class added in 3.1
|
||||||
|
return hasClassesNamed("org.springframework.web.method.HandlerMethod");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TypeInstrumentation> typeInstrumentations() {
|
||||||
|
return singletonList(new WebApplicationContextInstrumentation());
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.javaagent.instrumentation.springwebmvc;
|
package io.opentelemetry.javaagent.instrumentation.springweb;
|
||||||
|
|
||||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
|
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
|
||||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
|
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
|
||||||
|
@ -11,6 +11,7 @@ import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||||
|
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_SINGLETON;
|
||||||
|
|
||||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||||
|
@ -19,9 +20,10 @@ import net.bytebuddy.description.type.TypeDescription;
|
||||||
import net.bytebuddy.matcher.ElementMatcher;
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
|
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This instrumentation adds the HandlerMappingResourceNameFilter definition to the spring context
|
* This instrumentation adds the OpenTelemetryHandlerMappingFilter definition to the spring context
|
||||||
* When the context is created, the filter will be added to the beginning of the filter chain.
|
* When the context is created, the filter will be added to the beginning of the filter chain.
|
||||||
*/
|
*/
|
||||||
public class WebApplicationContextInstrumentation implements TypeInstrumentation {
|
public class WebApplicationContextInstrumentation implements TypeInstrumentation {
|
||||||
|
@ -59,10 +61,29 @@ public class WebApplicationContextInstrumentation implements TypeInstrumentation
|
||||||
public static void onEnter(@Advice.Argument(0) ConfigurableListableBeanFactory beanFactory) {
|
public static void onEnter(@Advice.Argument(0) ConfigurableListableBeanFactory beanFactory) {
|
||||||
if (beanFactory instanceof BeanDefinitionRegistry
|
if (beanFactory instanceof BeanDefinitionRegistry
|
||||||
&& !beanFactory.containsBean("otelAutoDispatcherFilter")) {
|
&& !beanFactory.containsBean("otelAutoDispatcherFilter")) {
|
||||||
|
try {
|
||||||
|
// Firstly check whether DispatcherServlet is present. We need to load an instrumented
|
||||||
|
// class from spring-webmvc to trigger injection that makes
|
||||||
|
// OpenTelemetryHandlerMappingFilter available.
|
||||||
|
beanFactory
|
||||||
|
.getBeanClassLoader()
|
||||||
|
.loadClass("org.springframework.web.servlet.DispatcherServlet");
|
||||||
|
|
||||||
|
// Now attempt to load our injected instrumentation class.
|
||||||
|
Class<?> clazz =
|
||||||
|
beanFactory
|
||||||
|
.getBeanClassLoader()
|
||||||
|
.loadClass("org.springframework.web.servlet.OpenTelemetryHandlerMappingFilter");
|
||||||
|
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
|
||||||
|
beanDefinition.setScope(SCOPE_SINGLETON);
|
||||||
|
beanDefinition.setBeanClass(clazz);
|
||||||
|
beanDefinition.setBeanClassName(clazz.getName());
|
||||||
|
|
||||||
((BeanDefinitionRegistry) beanFactory)
|
((BeanDefinitionRegistry) beanFactory)
|
||||||
.registerBeanDefinition(
|
.registerBeanDefinition("otelAutoDispatcherFilter", beanDefinition);
|
||||||
"otelAutoDispatcherFilter", new HandlerMappingResourceNameFilter.BeanDefinition());
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,17 +15,6 @@ muzzle {
|
||||||
extraDependency("javax.servlet:javax.servlet-api:3.0.1")
|
extraDependency("javax.servlet:javax.servlet-api:3.0.1")
|
||||||
assertInverse.set(true)
|
assertInverse.set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: webmvc depends on web, so we need a separate instrumentation for spring-web specifically.
|
|
||||||
fail {
|
|
||||||
group.set("org.springframework")
|
|
||||||
module.set("spring-web")
|
|
||||||
versions.set("[,]")
|
|
||||||
// these versions depend on org.springframework:spring-web which has a bad dependency on
|
|
||||||
// javax.faces:jsf-api:1.1 which was released as pom only
|
|
||||||
skip("1.2.1", "1.2.2", "1.2.3", "1.2.4")
|
|
||||||
extraDependency("javax.servlet:javax.servlet-api:3.0.1")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val versions: Map<String, String> by project
|
val versions: Map<String, String> by project
|
||||||
|
@ -36,12 +25,11 @@ dependencies {
|
||||||
// compileOnly("org.springframework:spring-webmvc:2.5.6")
|
// compileOnly("org.springframework:spring-webmvc:2.5.6")
|
||||||
// compileOnly("javax.servlet:servlet-api:2.4")
|
// compileOnly("javax.servlet:servlet-api:2.4")
|
||||||
|
|
||||||
testImplementation(project(":testing-common"))
|
|
||||||
|
|
||||||
// Include servlet instrumentation for verifying the tomcat requests
|
// Include servlet instrumentation for verifying the tomcat requests
|
||||||
testInstrumentation(project(":instrumentation:servlet:servlet-3.0:javaagent"))
|
testInstrumentation(project(":instrumentation:servlet:servlet-3.0:javaagent"))
|
||||||
testInstrumentation(project(":instrumentation:servlet:servlet-javax-common:javaagent"))
|
testInstrumentation(project(":instrumentation:servlet:servlet-javax-common:javaagent"))
|
||||||
testInstrumentation(project(":instrumentation:tomcat:tomcat-7.0:javaagent"))
|
testInstrumentation(project(":instrumentation:tomcat:tomcat-7.0:javaagent"))
|
||||||
|
testInstrumentation(project(":instrumentation:spring:spring-web-3.1:javaagent"))
|
||||||
|
|
||||||
testImplementation("javax.validation:validation-api:1.1.0.Final")
|
testImplementation("javax.validation:validation-api:1.1.0.Final")
|
||||||
testImplementation("org.hibernate:hibernate-validator:5.4.2.Final")
|
testImplementation("org.hibernate:hibernate-validator:5.4.2.Final")
|
||||||
|
|
|
@ -24,6 +24,7 @@ import net.bytebuddy.matcher.ElementMatcher;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.web.servlet.HandlerMapping;
|
import org.springframework.web.servlet.HandlerMapping;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
import org.springframework.web.servlet.OpenTelemetryHandlerMappingFilter;
|
||||||
|
|
||||||
public class DispatcherServletInstrumentation implements TypeInstrumentation {
|
public class DispatcherServletInstrumentation implements TypeInstrumentation {
|
||||||
|
|
||||||
|
@ -62,8 +63,8 @@ public class DispatcherServletInstrumentation implements TypeInstrumentation {
|
||||||
@Advice.Argument(0) ApplicationContext springCtx,
|
@Advice.Argument(0) ApplicationContext springCtx,
|
||||||
@Advice.FieldValue("handlerMappings") List<HandlerMapping> handlerMappings) {
|
@Advice.FieldValue("handlerMappings") List<HandlerMapping> handlerMappings) {
|
||||||
if (springCtx.containsBean("otelAutoDispatcherFilter")) {
|
if (springCtx.containsBean("otelAutoDispatcherFilter")) {
|
||||||
HandlerMappingResourceNameFilter filter =
|
OpenTelemetryHandlerMappingFilter filter =
|
||||||
(HandlerMappingResourceNameFilter) springCtx.getBean("otelAutoDispatcherFilter");
|
(OpenTelemetryHandlerMappingFilter) springCtx.getBean("otelAutoDispatcherFilter");
|
||||||
if (handlerMappings != null && filter != null) {
|
if (handlerMappings != null && filter != null) {
|
||||||
filter.setHandlerMappings(handlerMappings);
|
filter.setHandlerMappings(handlerMappings);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,14 @@ public class SpringWebMvcInstrumentationModule extends InstrumentationModule {
|
||||||
super("spring-webmvc", "spring-webmvc-3.1");
|
super("spring-webmvc", "spring-webmvc-3.1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHelperClass(String className) {
|
||||||
|
return className.startsWith(
|
||||||
|
"org.springframework.web.servlet.OpenTelemetryHandlerMappingFilter");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TypeInstrumentation> typeInstrumentations() {
|
public List<TypeInstrumentation> typeInstrumentations() {
|
||||||
return asList(
|
return asList(new DispatcherServletInstrumentation(), new HandlerAdapterInstrumentation());
|
||||||
new WebApplicationContextInstrumentation(),
|
|
||||||
new DispatcherServletInstrumentation(),
|
|
||||||
new HandlerAdapterInstrumentation());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,13 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.javaagent.instrumentation.springwebmvc;
|
package org.springframework.web.servlet;
|
||||||
|
|
||||||
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTROLLER;
|
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTROLLER;
|
||||||
|
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
|
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
|
||||||
|
import io.opentelemetry.javaagent.instrumentation.springwebmvc.SpringWebMvcServerSpanNaming;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -20,13 +21,10 @@ import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.ServletResponse;
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
|
||||||
import org.springframework.web.servlet.HandlerMapping;
|
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||||
|
|
||||||
public class HandlerMappingResourceNameFilter implements Filter, Ordered {
|
public class OpenTelemetryHandlerMappingFilter implements Filter, Ordered {
|
||||||
private volatile List<HandlerMapping> handlerMappings;
|
private volatile List<HandlerMapping> handlerMappings;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -122,12 +120,4 @@ public class HandlerMappingResourceNameFilter implements Filter, Ordered {
|
||||||
// Run after all HIGHEST_PRECEDENCE items
|
// Run after all HIGHEST_PRECEDENCE items
|
||||||
return Ordered.HIGHEST_PRECEDENCE + 1;
|
return Ordered.HIGHEST_PRECEDENCE + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BeanDefinition extends GenericBeanDefinition {
|
|
||||||
public BeanDefinition() {
|
|
||||||
setScope(SCOPE_SINGLETON);
|
|
||||||
setBeanClass(HandlerMappingResourceNameFilter.class);
|
|
||||||
setBeanClassName(HandlerMappingResourceNameFilter.class.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
plugins {
|
||||||
|
id("otel.javaagent-testing")
|
||||||
|
}
|
||||||
|
|
||||||
|
val testServer by configurations.creating
|
||||||
|
val appLibrary by configurations.creating
|
||||||
|
|
||||||
|
configurations.named("testCompileOnly") {
|
||||||
|
extendsFrom(appLibrary)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
appLibrary("org.springframework:spring-webmvc:3.1.0.RELEASE")
|
||||||
|
testImplementation("javax.servlet:javax.servlet-api:3.1.0")
|
||||||
|
|
||||||
|
val arquillianVersion = "1.4.0.Final"
|
||||||
|
testImplementation("org.jboss.arquillian.junit:arquillian-junit-container:${arquillianVersion}")
|
||||||
|
testImplementation("org.jboss.arquillian.protocol:arquillian-protocol-servlet:${arquillianVersion}")
|
||||||
|
testImplementation("org.jboss.arquillian.spock:arquillian-spock-container:1.0.0.CR1")
|
||||||
|
testImplementation("org.jboss.shrinkwrap:shrinkwrap-impl-base:1.2.6")
|
||||||
|
|
||||||
|
testRuntimeOnly("org.wildfly.arquillian:wildfly-arquillian-container-embedded:2.2.0.Final")
|
||||||
|
|
||||||
|
testInstrumentation(project(":instrumentation:servlet:servlet-3.0:javaagent"))
|
||||||
|
testInstrumentation(project(":instrumentation:spring:spring-webmvc-3.1:javaagent"))
|
||||||
|
testInstrumentation(project(":instrumentation:spring:spring-web-3.1:javaagent"))
|
||||||
|
|
||||||
|
// wildfly version used to run tests
|
||||||
|
testServer("org.wildfly:wildfly-dist:18.0.0.Final@zip")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
// extract wildfly dist, path is used from arquillian.xml
|
||||||
|
val setupServer by registering(Copy::class) {
|
||||||
|
from(zipTree(testServer.singleFile))
|
||||||
|
into(file("build/server/"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// logback-classic contains /META-INF/services/javax.servlet.ServletContainerInitializer
|
||||||
|
// that breaks deploy on embedded wildfly
|
||||||
|
// create a copy of logback-classic jar that does not have this file
|
||||||
|
val modifyLogbackJar by registering(Jar::class) {
|
||||||
|
destinationDirectory.set(file("$buildDir/tmp"))
|
||||||
|
archiveFileName.set("logback-classic-modified.jar")
|
||||||
|
exclude("/META-INF/services/javax.servlet.ServletContainerInitializer")
|
||||||
|
doFirst {
|
||||||
|
configurations.configureEach {
|
||||||
|
if (name.toLowerCase().endsWith("testruntimeclasspath")) {
|
||||||
|
val logbackJar = find { it.name.contains("logback-classic") }
|
||||||
|
from(zipTree(logbackJar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val copyDependencies by registering(Copy::class) {
|
||||||
|
// test looks for spring jars that are bundled inside deployed application from this directory
|
||||||
|
from(appLibrary).into("$buildDir/app-libs")
|
||||||
|
}
|
||||||
|
|
||||||
|
named<Test>("test") {
|
||||||
|
dependsOn(modifyLogbackJar)
|
||||||
|
dependsOn(setupServer)
|
||||||
|
dependsOn(copyDependencies)
|
||||||
|
|
||||||
|
doFirst {
|
||||||
|
// --add-modules is unrecognized on jdk8, ignore it instead of failing
|
||||||
|
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
|
||||||
|
// needed for java 11 to avoid org.jboss.modules.ModuleNotFoundException: java.se
|
||||||
|
jvmArgs("--add-modules=java.se")
|
||||||
|
// add offset to default port values
|
||||||
|
jvmArgs("-Djboss.socket.binding.port-offset=300")
|
||||||
|
|
||||||
|
// remove logback-classic from classpath
|
||||||
|
classpath = classpath.filter {
|
||||||
|
!it.absolutePath.contains("logback-classic")
|
||||||
|
}
|
||||||
|
// add modified copy of logback-classic to classpath
|
||||||
|
classpath = classpath.plus(files("$buildDir/tmp/logback-classic-modified.jar"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
|
||||||
|
import static io.opentelemetry.api.trace.SpanKind.SERVER
|
||||||
|
|
||||||
|
import com.example.hello.HelloController
|
||||||
|
import com.example.hello.TestFilter
|
||||||
|
import io.opentelemetry.api.trace.StatusCode
|
||||||
|
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||||
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
||||||
|
import io.opentelemetry.testing.internal.armeria.client.WebClient
|
||||||
|
import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse
|
||||||
|
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.Archive
|
||||||
|
import org.jboss.shrinkwrap.api.ShrinkWrap
|
||||||
|
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive
|
||||||
|
import org.jboss.shrinkwrap.api.spec.WebArchive
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(ArquillianSputnik)
|
||||||
|
@RunAsClient
|
||||||
|
// Test that OpenTelemetryHandlerMappingFilter injection works when spring libraries are in various
|
||||||
|
// locations inside deployment.
|
||||||
|
abstract class OpenTelemetryHandlerMappingFilterTest extends AgentInstrumentationSpecification {
|
||||||
|
|
||||||
|
static WebClient client = WebClient.of()
|
||||||
|
|
||||||
|
@ArquillianResource
|
||||||
|
static URI url
|
||||||
|
|
||||||
|
def getAddress(String service) {
|
||||||
|
return url.resolve(service).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
def "test success"() {
|
||||||
|
when:
|
||||||
|
AggregatedHttpResponse response = client.get(getAddress("hello/world")).aggregate().join()
|
||||||
|
|
||||||
|
then:
|
||||||
|
response.status().code() == 200
|
||||||
|
response.contentUtf8() == "hello world"
|
||||||
|
|
||||||
|
and:
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 2) {
|
||||||
|
span(0) {
|
||||||
|
name "/hello/{name}"
|
||||||
|
kind SERVER
|
||||||
|
hasNoParent()
|
||||||
|
}
|
||||||
|
span(1) {
|
||||||
|
name "HelloController.hello"
|
||||||
|
kind INTERNAL
|
||||||
|
childOf(span(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "test exception"() {
|
||||||
|
when:
|
||||||
|
AggregatedHttpResponse response = client.get(getAddress("hello/exception")).aggregate().join()
|
||||||
|
|
||||||
|
then:
|
||||||
|
response.status().code() == 500
|
||||||
|
|
||||||
|
and:
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
name "/hello/{name}"
|
||||||
|
kind SERVER
|
||||||
|
status StatusCode.ERROR
|
||||||
|
hasNoParent()
|
||||||
|
|
||||||
|
event(0) {
|
||||||
|
eventName(SemanticAttributes.EXCEPTION_EVENT_NAME)
|
||||||
|
attributes {
|
||||||
|
"${SemanticAttributes.EXCEPTION_TYPE.key}" "javax.servlet.ServletException"
|
||||||
|
"${SemanticAttributes.EXCEPTION_MESSAGE.key}" "exception"
|
||||||
|
"${SemanticAttributes.EXCEPTION_STACKTRACE.key}" { it == null || it instanceof String }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// spring is inside ear/lib
|
||||||
|
class LibsInEarTest extends OpenTelemetryHandlerMappingFilterTest {
|
||||||
|
@Deployment
|
||||||
|
static Archive<?> createDeployment() {
|
||||||
|
WebArchive war = ShrinkWrap.create(WebArchive, "test.war")
|
||||||
|
.addAsWebInfResource("web.xml")
|
||||||
|
.addAsWebInfResource("dispatcher-servlet.xml")
|
||||||
|
.addAsWebInfResource("applicationContext.xml")
|
||||||
|
.addClass(HelloController)
|
||||||
|
.addClass(TestFilter)
|
||||||
|
|
||||||
|
EnterpriseArchive ear = ShrinkWrap.create(EnterpriseArchive)
|
||||||
|
.setApplicationXML("application.xml")
|
||||||
|
.addAsModule(war)
|
||||||
|
.addAsLibraries(new File("build/app-libs").listFiles())
|
||||||
|
|
||||||
|
return ear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// spring is inside war/WEB-INF/lib
|
||||||
|
class LibsInWarTest extends OpenTelemetryHandlerMappingFilterTest {
|
||||||
|
@Deployment
|
||||||
|
static Archive<?> createDeployment() {
|
||||||
|
WebArchive war = ShrinkWrap.create(WebArchive, "test.war")
|
||||||
|
.addAsWebInfResource("web.xml")
|
||||||
|
.addAsWebInfResource("dispatcher-servlet.xml")
|
||||||
|
.addAsWebInfResource("applicationContext.xml")
|
||||||
|
.addClass(HelloController)
|
||||||
|
.addClass(TestFilter)
|
||||||
|
.addAsLibraries(new File("build/app-libs").listFiles())
|
||||||
|
|
||||||
|
EnterpriseArchive ear = ShrinkWrap.create(EnterpriseArchive)
|
||||||
|
.setApplicationXML("application.xml")
|
||||||
|
.addAsModule(war)
|
||||||
|
|
||||||
|
return ear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything except spring-webmvc is in ear/lib, spring-webmvc is in war/WEB-INF/lib
|
||||||
|
class MixedLibsTest extends OpenTelemetryHandlerMappingFilterTest {
|
||||||
|
@Deployment
|
||||||
|
static Archive<?> createDeployment() {
|
||||||
|
WebArchive war = ShrinkWrap.create(WebArchive, "test.war")
|
||||||
|
.addAsWebInfResource("web.xml")
|
||||||
|
.addAsWebInfResource("dispatcher-servlet.xml")
|
||||||
|
.addAsWebInfResource("applicationContext.xml")
|
||||||
|
.addClass(HelloController)
|
||||||
|
.addClass(TestFilter)
|
||||||
|
.addAsLibraries(new File("build/app-libs").listFiles(new FilenameFilter() {
|
||||||
|
@Override
|
||||||
|
boolean accept(File dir, String name) {
|
||||||
|
return name.contains("spring-webmvc")
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
EnterpriseArchive ear = ShrinkWrap.create(EnterpriseArchive)
|
||||||
|
.setApplicationXML("application.xml")
|
||||||
|
.addAsModule(war)
|
||||||
|
.addAsLibraries(new File("build/app-libs").listFiles(new FilenameFilter() {
|
||||||
|
@Override
|
||||||
|
boolean accept(File dir, String name) {
|
||||||
|
return !name.contains("spring-webmvc")
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return ear
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.hello;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class HelloController {
|
||||||
|
|
||||||
|
@RequestMapping("/hello/{name}")
|
||||||
|
@ResponseBody
|
||||||
|
public String hello(@PathVariable("name") String name) {
|
||||||
|
return "hello " + name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.hello;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("testFilter")
|
||||||
|
public class TestFilter implements Filter {
|
||||||
|
public TestFilter() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||||
|
// To test OpenTelemetryHandlerMappingFilter we need to stop the request before it reaches
|
||||||
|
// HandlerAdapter which gives server span the same name as OpenTelemetryHandlerMappingFilter.
|
||||||
|
// Throwing an exception from servlet filter works for that.
|
||||||
|
if (httpServletRequest.getRequestURI().contains("exception")) {
|
||||||
|
throw new ServletException("exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<application xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/application_7.xsd" version="7">
|
||||||
|
<display-name>hello-ear</display-name>
|
||||||
|
<module>
|
||||||
|
<web>
|
||||||
|
<web-uri>test.war</web-uri>
|
||||||
|
<context-root>/</context-root>
|
||||||
|
</web>
|
||||||
|
</module>
|
||||||
|
<library-directory>lib</library-directory>
|
||||||
|
</application>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||||
|
xmlns:context="http://www.springframework.org/schema/context"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
|
||||||
|
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
|
||||||
|
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
|
||||||
|
|
||||||
|
<mvc:annotation-driven/>
|
||||||
|
|
||||||
|
<context:component-scan base-package="com.example.hello"/>
|
||||||
|
</beans>
|
|
@ -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>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||||
|
xmlns:context="http://www.springframework.org/schema/context"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
|
||||||
|
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
|
||||||
|
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
|
||||||
|
|
||||||
|
</beans>
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
|
||||||
|
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||||
|
version="3.1">
|
||||||
|
|
||||||
|
<servlet>
|
||||||
|
<servlet-name>dispatcher</servlet-name>
|
||||||
|
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||||
|
<load-on-startup>1</load-on-startup>
|
||||||
|
</servlet>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Spring boot automatically registers a servlet filter when spring bean implements
|
||||||
|
javax.servlet.Filter but vanilla spring does not so we need to add a placeholder
|
||||||
|
for our servlet filter here.
|
||||||
|
-->
|
||||||
|
<filter>
|
||||||
|
<filter-name>otelAutoDispatcherFilter</filter-name>
|
||||||
|
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<filter-name>testFilter</filter-name>
|
||||||
|
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>dispatcher</servlet-name>
|
||||||
|
<url-pattern>/</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
|
|
||||||
|
<filter-mapping>
|
||||||
|
<filter-name>otelAutoDispatcherFilter</filter-name>
|
||||||
|
<servlet-name>dispatcher</servlet-name>
|
||||||
|
</filter-mapping>
|
||||||
|
|
||||||
|
<filter-mapping>
|
||||||
|
<filter-name>testFilter</filter-name>
|
||||||
|
<servlet-name>dispatcher</servlet-name>
|
||||||
|
</filter-mapping>
|
||||||
|
|
||||||
|
<listener>
|
||||||
|
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||||
|
</listener>
|
||||||
|
</web-app>
|
|
@ -306,10 +306,12 @@ include(":instrumentation:spring:spring-integration-4.1:library")
|
||||||
include(":instrumentation:spring:spring-integration-4.1:testing")
|
include(":instrumentation:spring:spring-integration-4.1:testing")
|
||||||
include(":instrumentation:spring:spring-rabbit-1.0:javaagent")
|
include(":instrumentation:spring:spring-rabbit-1.0:javaagent")
|
||||||
include(":instrumentation:spring:spring-scheduling-3.1:javaagent")
|
include(":instrumentation:spring:spring-scheduling-3.1:javaagent")
|
||||||
|
include(":instrumentation:spring:spring-web-3.1:javaagent")
|
||||||
include(":instrumentation:spring:spring-web-3.1:library")
|
include(":instrumentation:spring:spring-web-3.1:library")
|
||||||
include(":instrumentation:spring:spring-web-3.1:testing")
|
include(":instrumentation:spring:spring-web-3.1:testing")
|
||||||
include(":instrumentation:spring:spring-webmvc-3.1:javaagent")
|
include(":instrumentation:spring:spring-webmvc-3.1:javaagent")
|
||||||
include(":instrumentation:spring:spring-webmvc-3.1:library")
|
include(":instrumentation:spring:spring-webmvc-3.1:library")
|
||||||
|
include(":instrumentation:spring:spring-webmvc-3.1:wildfly-testing")
|
||||||
include(":instrumentation:spring:spring-webflux-5.0:javaagent")
|
include(":instrumentation:spring:spring-webflux-5.0:javaagent")
|
||||||
include(":instrumentation:spring:spring-webflux-5.0:library")
|
include(":instrumentation:spring:spring-webflux-5.0:library")
|
||||||
include(":instrumentation:spring:spring-ws-2.0:javaagent")
|
include(":instrumentation:spring:spring-ws-2.0:javaagent")
|
||||||
|
|
Loading…
Reference in New Issue