Auto-instrumentation for JaxWS (#2176)

* Initial JaxWS support with only manual tests and no async support.

* Add support for soap provider and tests.

* Initial JaxWS support with only manual tests and no async support.

* Add support for soap provider and tests.

* Rename jws 1.0 to 1.1 everywhere.

* Rename jws 1.0 to 1.1 everywhere.
This commit is contained in:
Vladimir Šor 2021-02-04 07:49:58 +02:00 committed by GitHub
parent 77522355a2
commit a0d66784d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 526 additions and 4 deletions

View File

@ -0,0 +1,15 @@
apply from: "$rootDir/gradle/instrumentation.gradle"
muzzle {
pass {
group = "javax.xml.ws"
module = "jaxws-api"
versions = "[2.0,]"
skipVersions += '2.1-1' // contains broken dependency
}
}
dependencies {
library group: 'javax.xml.ws', name: 'jaxws-api', version: '2.0'
implementation project(":instrumentation:jaxws:jaxws-common:javaagent")
}

View File

@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxws.v2_0;
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)
public class JaxWsInstrumentationModule extends InstrumentationModule {
public JaxWsInstrumentationModule() {
super("jaxws", "jaxws-2.0");
}
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new WebServiceProviderInstrumentation());
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxws.v2_0;
import static io.opentelemetry.javaagent.instrumentation.jaxws.common.JaxWsTracer.tracer;
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.hasInterface;
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher.hasClassesNamed;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.nameMatches;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.instrumentation.api.CallDepthThreadLocalMap;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import javax.xml.ws.Provider;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
public class WebServiceProviderInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("javax.xml.ws.Provider");
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return hasInterface(named("javax.xml.ws.Provider"));
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return Collections.singletonMap(
isMethod().and(isPublic()).and(nameMatches("invoke")).and(takesArguments(1)),
WebServiceProviderInstrumentation.class.getName() + "$InvokeAdvice");
}
public static class InvokeAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void startSpan(
@Advice.This Object target,
@Advice.Origin Method method,
@Advice.Local("otelSpan") Span span,
@Advice.Local("otelScope") Scope scope) {
if (CallDepthThreadLocalMap.incrementCallDepth(Provider.class) > 0) {
return;
}
span = tracer().startSpan(target.getClass(), method);
scope = span.makeCurrent();
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelSpan") Span span,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}
CallDepthThreadLocalMap.reset(Provider.class);
scope.close();
if (throwable == null) {
tracer().end(span);
} else {
tracer().endExceptionally(span, throwable);
}
}
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxws.v2_0
import io.opentelemetry.instrumentation.test.AgentTestRunner
class JaxWsAnnotationsTest extends AgentTestRunner {
def "Web service providers generate spans"() {
when:
new SoapProvider().invoke(null)
then:
assertTraces(1, {
trace(0, 1) {
span(0) {
name 'SoapProvider.invoke'
attributes {
attribute('code.namespace', 'io.opentelemetry.javaagent.instrumentation.jaxws.v2_0.SoapProvider')
attribute('code.function', 'invoke')
}
}
}
})
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxws.v2_0;
import javax.xml.ws.Provider;
public class SoapProvider implements Provider<SoapProvider.Message> {
@Override
public Message invoke(Message message) {
return message;
}
public static class Message {}
}

View File

@ -0,0 +1,2 @@
apply from: "$rootDir/gradle/instrumentation.gradle"

View File

@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxws.common;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.lang.reflect.Method;
public class JaxWsTracer extends BaseTracer {
private static final JaxWsTracer TRACER = new JaxWsTracer();
public static JaxWsTracer tracer() {
return TRACER;
}
@Override
protected String getInstrumentationName() {
return "io.opentelemetry.javaagent.jaxws";
}
public Span startSpan(Class<?> target, Method method) {
String spanName = spanNameForMethod(target, method);
Context context = Context.current();
Span serverSpan = BaseTracer.getCurrentServerSpan(context);
if (serverSpan != null) {
serverSpan.updateName(spanName);
}
return tracer
.spanBuilder(spanName)
.setAttribute(SemanticAttributes.CODE_NAMESPACE, method.getDeclaringClass().getName())
.setAttribute(SemanticAttributes.CODE_FUNCTION, method.getName())
.startSpan();
}
}

View File

@ -0,0 +1,14 @@
apply from: "$rootDir/gradle/instrumentation.gradle"
muzzle {
pass {
group = "javax.jws"
module = "javax.jws-api"
versions = "[1.1,]"
}
}
dependencies {
library group: 'javax.jws', name: 'javax.jws-api', version: '1.1'
implementation project(":instrumentation:jaxws:jaxws-common:javaagent")
}

View File

@ -0,0 +1,92 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxws.jws.v1_1;
import static io.opentelemetry.javaagent.instrumentation.jaxws.common.JaxWsTracer.tracer;
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.hasInterface;
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.hasSuperMethod;
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.methodIsDeclaredByType;
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher.hasClassesNamed;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.inheritsAnnotation;
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.instrumentation.api.CallDepthThreadLocalMap;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.lang.reflect.Method;
import java.util.Map;
import javax.jws.WebService;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
public class JwsAnnotationsInstrumentation implements TypeInstrumentation {
public static final String JWS_WEB_SERVICE_ANNOTATION = "javax.jws.WebService";
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed(JWS_WEB_SERVICE_ANNOTATION);
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return hasInterface(isAnnotatedWith(named(JWS_WEB_SERVICE_ANNOTATION)))
.or(isAnnotatedWith(named(JWS_WEB_SERVICE_ANNOTATION)));
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
// JaxWS WebService methods are defined either by implementing an interface annotated
// with @WebService or by any public method from a class annotated with @WebService.
return singletonMap(
isMethod()
.and(isPublic())
.and(
hasSuperMethod(
methodIsDeclaredByType(inheritsAnnotation(named(JWS_WEB_SERVICE_ANNOTATION))))),
JwsAnnotationsInstrumentation.class.getName() + "$JwsAnnotationsAdvice");
}
public static class JwsAnnotationsAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void startSpan(
@Advice.This Object target,
@Advice.Origin Method method,
@Advice.Local("otelSpan") Span span,
@Advice.Local("otelScope") Scope scope) {
if (CallDepthThreadLocalMap.incrementCallDepth(WebService.class) > 0) {
return;
}
span = tracer().startSpan(target.getClass(), method);
scope = span.makeCurrent();
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelSpan") Span span,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}
CallDepthThreadLocalMap.reset(WebService.class);
scope.close();
if (throwable == null) {
tracer().end(span);
} else {
tracer().endExceptionally(span, throwable);
}
}
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxws.jws.v1_1;
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)
public class JwsInstrumentationModule extends InstrumentationModule {
public JwsInstrumentationModule() {
super("jaxws", "jws-1.1");
}
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new JwsAnnotationsInstrumentation());
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxws.jws.v1_1
import io.opentelemetry.instrumentation.test.AgentTestRunner
class JwsAnnotationsTest extends AgentTestRunner {
def "WebService on a class generates spans only for public methods"() {
when:
new WebServiceClass().doSomethingPublic()
new WebServiceClass().doSomethingPackagePrivate()
new WebServiceClass().doSomethingProtected()
then:
assertTraces(1, {
trace(0, 1) {
span(0) {
name "WebServiceClass.doSomethingPublic"
attributes {
attribute('code.function', 'doSomethingPublic')
attribute('code.namespace', 'io.opentelemetry.javaagent.instrumentation.jaxws.jws.v1_1.WebServiceClass')
}
}
}
})
}
def "WebService via interface generates spans only for methods of the interface"() {
when:
new WebServiceFromInterface().partOfPublicInterface()
new WebServiceFromInterface().notPartOfPublicInterface()
new WebServiceFromInterface().notPartOfAnything()
then:
assertTraces(1, {
trace(0, 1) {
span(0) {
name "WebServiceFromInterface.partOfPublicInterface"
attributes {
attribute('code.function', 'partOfPublicInterface')
attribute('code.namespace', 'io.opentelemetry.javaagent.instrumentation.jaxws.jws.v1_1.WebServiceFromInterface')
}
}
}
})
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxws.jws.v1_1;
import javax.jws.WebService;
// This is pure java to not have any groovy generated public method surprises
@WebService
public class WebServiceClass {
public void doSomethingPublic() {}
protected void doSomethingProtected() {}
void doSomethingPackagePrivate() {}
}

View File

@ -0,0 +1,13 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxws.jws.v1_1;
import javax.jws.WebService;
@WebService
public interface WebServiceDefinitionInterface {
void partOfPublicInterface();
}

View File

@ -0,0 +1,15 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jaxws.jws.v1_1;
public class WebServiceFromInterface implements WebServiceDefinitionInterface {
@Override
public void partOfPublicInterface() {}
public void notPartOfPublicInterface() {}
void notPartOfAnything() {}
}

View File

@ -40,7 +40,27 @@ public class AgentElementMatchers {
.and(new SafeHasSuperTypeMatcher<>(new SafeErasureMatcher<>(matcher), false));
}
// TODO: add javadoc
/**
* Matches method's declaring class against a given type matcher.
*
* @param matcher type matcher to match method's declaring type against.
* @param <T> Type of the matched object
* @return a matcher that matches method's declaring class against a given type matcher.
*/
public static <T extends MethodDescription> ElementMatcher.Junction<T> methodIsDeclaredByType(
ElementMatcher<? super TypeDescription> matcher) {
return new MethodDeclaringTypeMatcher<>(matcher);
}
/**
* Matches a method and all its declarations up the class hierarchy including interfaces using
* provided matcher.
*
* @param matcher method matcher to apply to method declarations up the hierarchy.
* @param <T> Type of the matched object
* @return A matcher that matches a method and all its declarations up the class hierarchy
* including interfaces.
*/
public static <T extends MethodDescription> ElementMatcher.Junction<T> hasSuperMethod(
ElementMatcher<? super MethodDescription> matcher) {
return new HasSuperMethodMatcher<>(matcher);

View File

@ -15,7 +15,12 @@ import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.matcher.ElementMatcher;
// TODO: add javadoc
/**
* Matches a method and all its declarations up the class hierarchy including interfaces using
* provided matcher.
*
* @param <T> Type of the matched method.
*/
class HasSuperMethodMatcher<T extends MethodDescription>
extends ElementMatcher.Junction.AbstractBase<T> {

View File

@ -0,0 +1,53 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.bytebuddy.matcher;
import java.util.Objects;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
/**
* Matches method's declaring class against a given type matcher.
*
* @param <T> Type of the matched object
*/
public class MethodDeclaringTypeMatcher<T extends MethodDescription>
extends ElementMatcher.Junction.AbstractBase<T> {
private final ElementMatcher<? super TypeDescription> matcher;
public MethodDeclaringTypeMatcher(ElementMatcher<? super TypeDescription> matcher) {
this.matcher = matcher;
}
@Override
public boolean matches(T target) {
return matcher.matches(target.getDeclaringType().asErasure());
}
@Override
public String toString() {
return "methodDeclaringTypeMatcher(matcher=" + matcher + ')';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof MethodDeclaringTypeMatcher)) {
return false;
}
MethodDeclaringTypeMatcher<?> that = (MethodDeclaringTypeMatcher<?>) o;
return Objects.equals(matcher, that.matcher);
}
@Override
public int hashCode() {
return Objects.hash(matcher);
}
}

View File

@ -189,8 +189,8 @@ public class AdditionalLibraryIgnoresMatcher<T extends TypeDescription>
return false;
}
// xml-apis, xerces, xalan
if (name.startsWith("javax.xml.")
// xml-apis, xerces, xalan, but not xml web-services
if ((name.startsWith("javax.xml.") && !name.startsWith("javax.xml.ws."))
|| name.startsWith("org.apache.bcel.")
|| name.startsWith("org.apache.html.")
|| name.startsWith("org.apache.regexp.")

View File

@ -118,6 +118,9 @@ include ':instrumentation:jaxrs-client:jaxrs-client-1.1:javaagent'
include ':instrumentation:jaxrs-client:jaxrs-client-2.0:jaxrs-client-2.0-common:javaagent'
include ':instrumentation:jaxrs-client:jaxrs-client-2.0:jaxrs-client-2.0-jersey-2.0:javaagent'
include ':instrumentation:jaxrs-client:jaxrs-client-2.0:jaxrs-client-2.0-resteasy-2.0:javaagent'
include ':instrumentation:jaxws:jaxws-2.0:javaagent'
include ':instrumentation:jaxws:jaxws-common:javaagent'
include ':instrumentation:jaxws:jws-1.1:javaagent'
include ':instrumentation:jdbc:javaagent'
include ':instrumentation:jdbc:javaagent-unittests'
include ':instrumentation:jedis:jedis-1.4:javaagent'