Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring RMI instrumentation #5033

Merged
merged 11 commits into from
Jan 14, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;

Expand All @@ -28,7 +29,8 @@
public class RemoteServerInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return implementsInterface(named("java.rmi.Remote"));
return implementsInterface(named("java.rmi.Remote"))
.and(not(nameStartsWith("org.springframework.remoting")));
mateuszrzeszutek marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("org.springframework")
module.set("spring-context")
versions.set("[4.0.0.RELEASE,)")
}
}

dependencies {
compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")

bootstrap(project(":instrumentation:rmi:bootstrap"))
testInstrumentation(project(":instrumentation:rmi:javaagent"))

library("org.springframework:spring-context:4.0.0.RELEASE")
library("org.springframework:spring-aop:4.0.0.RELEASE")
testLibrary("org.springframework.boot:spring-boot:1.1.0.RELEASE")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.springrmi;

import static java.util.Arrays.asList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.instrumentation.springrmi.client.ClientInstrumentation;
import io.opentelemetry.javaagent.instrumentation.springrmi.server.ServerInstrumentation;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class SpringRmiInstrumentationModule extends InstrumentationModule {

public SpringRmiInstrumentationModule() {
super("spring-rmi", "spring-rmi-4.0");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new ClientInstrumentation(), new ServerInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.springrmi;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.rpc.RpcSpanNameExtractor;
import io.opentelemetry.instrumentation.api.util.ClassAndMethod;
import io.opentelemetry.javaagent.instrumentation.springrmi.client.ClientAttributesExtractor;
import io.opentelemetry.javaagent.instrumentation.springrmi.server.ServerAttributesExtractor;
import java.lang.reflect.Method;

public final class SpringRmiSingletons {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-rmi-4.0";

private static final Instrumenter<Method, Void> CLIENT_INSTRUMENTER = buildClientInstrumenter();
private static final Instrumenter<ClassAndMethod, Void> SERVER_INSTRUMENTER =
buildServerInstrumenter();

private static Instrumenter<Method, Void> buildClientInstrumenter() {
ClientAttributesExtractor attributesExtractor = new ClientAttributesExtractor();

return Instrumenter.<Method, Void>builder(
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
RpcSpanNameExtractor.create(attributesExtractor))
.addAttributesExtractor(attributesExtractor)
.newInstrumenter(SpanKindExtractor.alwaysClient());
}

private static Instrumenter<ClassAndMethod, Void> buildServerInstrumenter() {
ServerAttributesExtractor attributesExtractor = new ServerAttributesExtractor();

return Instrumenter.<ClassAndMethod, Void>builder(
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
RpcSpanNameExtractor.create(attributesExtractor))
.addAttributesExtractor(attributesExtractor)
.newInstrumenter(SpanKindExtractor.alwaysServer());
}

public static Instrumenter<Method, Void> clientInstrumenter() {
return CLIENT_INSTRUMENTER;
}

public static Instrumenter<ClassAndMethod, Void> serverInstrumenter() {
return SERVER_INSTRUMENTER;
}

private SpringRmiSingletons() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.springrmi.client;

import io.opentelemetry.instrumentation.api.instrumenter.rpc.RpcAttributesExtractor;
import java.lang.reflect.Method;

public final class ClientAttributesExtractor extends RpcAttributesExtractor<Method, Void> {

@Override
protected String system(Method method) {
return "spring_rmi";
}

@Override
protected String service(Method method) {
return method.getDeclaringClass().getName();
}

@Override
protected String method(Method method) {
return method.getName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.springrmi.client;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.instrumentation.springrmi.SpringRmiSingletons.clientInstrumenter;
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.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import java.lang.reflect.Method;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.aopalliance.intercept.MethodInvocation;

public class ClientInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("org.springframework.remoting.rmi.RmiClientInterceptor");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return extendsClass(named("org.springframework.remoting.rmi.RmiClientInterceptor"));
mateuszrzeszutek marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("invoke"))
.and(takesArgument(0, named("org.aopalliance.intercept.MethodInvocation"))),
this.getClass().getName() + "$InvokeMethodAdvice");
}

@SuppressWarnings("unused")
public static class InvokeMethodAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) MethodInvocation methodInv,
@Advice.Local("method") Method method,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {

method = methodInv.getMethod();
Context parentContext = Java8BytecodeBridge.currentContext();
if (!clientInstrumenter().shouldStart(parentContext, method)) {
return;
}

context = clientInstrumenter().start(parentContext, method);
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Local("method") Method method,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {

if (scope == null) {
return;
}
scope.close();
clientInstrumenter().end(context, method, null, throwable);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.springrmi.server;

import io.opentelemetry.instrumentation.api.instrumenter.rpc.RpcAttributesExtractor;
import io.opentelemetry.instrumentation.api.util.ClassAndMethod;

public final class ServerAttributesExtractor extends RpcAttributesExtractor<ClassAndMethod, Void> {

@Override
protected String system(ClassAndMethod classAndMethod) {
return "spring_rmi";
}

@Override
protected String service(ClassAndMethod classAndMethod) {
return classAndMethod.declaringClass().getName();
}

@Override
protected String method(ClassAndMethod classAndMethod) {
return classAndMethod.methodName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.springrmi.server;

import static io.opentelemetry.javaagent.bootstrap.rmi.ThreadLocalContext.THREAD_LOCAL_CONTEXT;
import static io.opentelemetry.javaagent.instrumentation.springrmi.SpringRmiSingletons.serverInstrumenter;
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.instrumentation.api.util.ClassAndMethod;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.springframework.remoting.rmi.RmiBasedExporter;
import org.springframework.remoting.support.RemoteInvocation;

public class ServerInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.springframework.remoting.rmi.RmiBasedExporter");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("invoke"))
.and(takesArgument(0, named("org.springframework.remoting.support.RemoteInvocation"))),
this.getClass().getName() + "$InvokeMethodAdvice");
}

@SuppressWarnings("unused")
public static class InvokeMethodAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.This RmiBasedExporter thisObject,
@Advice.Argument(0) RemoteInvocation remoteInv,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelRequest") ClassAndMethod request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {

callDepth = CallDepth.forClass(RmiBasedExporter.class);
if (callDepth.getAndIncrement() > 0) {
return;
}

Context parentContext = THREAD_LOCAL_CONTEXT.getAndResetContext();
Class<?> serverClass = thisObject.getService().getClass();
String methodName = remoteInv.getMethodName();
request = ClassAndMethod.create(serverClass, methodName);

if (!serverInstrumenter().shouldStart(parentContext, request)) {
return;
}

context = serverInstrumenter().start(parentContext, request);
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelRequest") ClassAndMethod request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {

if (callDepth.decrementAndGet() > 0) {
return;
}
if (scope == null) {
return;
}
scope.close();
serverInstrumenter().end(context, request, null, throwable);
}
}
}
Loading