Skip to content

Commit b605e52

Browse files
committed
feat: support instrumentation for jsonrpc4j
1 parent 2e959d3 commit b605e52

29 files changed

+968
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("com.github.briandilley.jsonrpc4j")
8+
module.set("jsonrpc4j")
9+
versions.set("[1.6,)")
10+
assertInverse.set(true)
11+
}
12+
}
13+
14+
val jsonrpcVersion = "1.6"
15+
16+
dependencies {
17+
implementation(project(":instrumentation:jsonrpc4j-1.6:library"))
18+
implementation("com.github.briandilley.jsonrpc4j:jsonrpc4j:$jsonrpcVersion")
19+
testImplementation(project(":instrumentation:jsonrpc4j-1.6:testing"))
20+
}
21+
22+
23+
tasks {
24+
test {
25+
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
26+
jvmArgs("-Dotel.javaagent.experimental.thread-propagation-debugger.enabled=false")
27+
jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true")
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6;
2+
3+
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
4+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
5+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
6+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
7+
import static net.bytebuddy.matcher.ElementMatchers.named;
8+
import static net.bytebuddy.matcher.ElementMatchers.returns;
9+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
10+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
11+
12+
import io.opentelemetry.context.Context;
13+
import io.opentelemetry.context.Scope;
14+
import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.SimpleJsonRpcRequest;
15+
import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.SimpleJsonRpcResponse;
16+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
17+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
18+
import net.bytebuddy.asm.Advice;
19+
import net.bytebuddy.description.type.TypeDescription;
20+
import net.bytebuddy.matcher.ElementMatcher;
21+
22+
public class JsonRpcClientBuilderInstrumentation implements TypeInstrumentation {
23+
24+
@Override
25+
public ElementMatcher<ClassLoader> classLoaderOptimization() {
26+
return hasClassesNamed("com.googlecode.jsonrpc4j.IJsonRpcClient");
27+
}
28+
29+
@Override
30+
public ElementMatcher<TypeDescription> typeMatcher() {
31+
// match JsonRpcHttpClient and JsonRpcRestClient
32+
return implementsInterface(named("com.googlecode.jsonrpc4j.IJsonRpcClient"));
33+
}
34+
35+
@Override
36+
public void transform(TypeTransformer transformer) {
37+
transformer.applyAdviceToMethod(
38+
isMethod()
39+
.and(named("invoke"))
40+
.and(takesArguments(4))
41+
.and(takesArgument(0, String.class))
42+
.and(takesArgument(1, Object.class))
43+
.and(takesArgument(2, named("java.lang.reflect.Type")))
44+
.and(takesArgument(3, named("java.util.Map")))
45+
.and(returns(Object.class)),
46+
this.getClass().getName() + "$InvokeAdvice");
47+
}
48+
49+
@SuppressWarnings("unused")
50+
public static class InvokeAdvice {
51+
52+
@Advice.OnMethodEnter(suppress = Throwable.class)
53+
public static void onEnter(
54+
@Advice.Argument(0) String methodName,
55+
@Advice.Argument(1) Object argument,
56+
@Advice.Local("otelContext") Context context,
57+
@Advice.Local("otelScope") Scope scope) {
58+
Context parentContext = currentContext();
59+
SimpleJsonRpcRequest request = new SimpleJsonRpcRequest(
60+
methodName,
61+
argument
62+
);
63+
if (!JsonRpcSingletons.CLIENT_INSTRUMENTER.shouldStart(parentContext, request)) {
64+
return;
65+
}
66+
67+
context = JsonRpcSingletons.CLIENT_INSTRUMENTER.start(parentContext, request);
68+
scope = context.makeCurrent();
69+
}
70+
71+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
72+
public static void onExit(
73+
@Advice.Argument(0) String methodName,
74+
@Advice.Argument(1) Object argument,
75+
@Advice.Return Object result,
76+
@Advice.Thrown Throwable throwable,
77+
@Advice.Local("otelContext") Context context,
78+
@Advice.Local("otelScope") Scope scope) {
79+
if (scope == null) {
80+
return;
81+
}
82+
83+
scope.close();
84+
JsonRpcSingletons.CLIENT_INSTRUMENTER.end(context, new SimpleJsonRpcRequest(methodName, argument), new SimpleJsonRpcResponse(result), throwable);
85+
}
86+
}
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6;
2+
3+
import static java.util.Arrays.asList;
4+
5+
import com.google.auto.service.AutoService;
6+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
7+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
8+
import java.util.List;
9+
10+
@AutoService(InstrumentationModule.class)
11+
public class JsonRpcInstrumentationModule extends InstrumentationModule {
12+
public JsonRpcInstrumentationModule() {
13+
super("jsonrpc4j", "jsonrpc4j-1.6");
14+
}
15+
16+
@Override
17+
public List<TypeInstrumentation> typeInstrumentations() {
18+
return asList(
19+
new JsonRpcServerBuilderInstrumentation(),
20+
new JsonServiceExporterBuilderInstrumentation(),
21+
new JsonRpcClientBuilderInstrumentation()
22+
);
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6;
2+
3+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
4+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
5+
import static net.bytebuddy.matcher.ElementMatchers.named;
6+
7+
import com.googlecode.jsonrpc4j.InvocationListener;
8+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
9+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
10+
import net.bytebuddy.description.type.TypeDescription;
11+
import io.opentelemetry.instrumentation.api.util.VirtualField;
12+
import net.bytebuddy.matcher.ElementMatcher;
13+
import com.googlecode.jsonrpc4j.JsonRpcBasicServer;
14+
import net.bytebuddy.asm.Advice;
15+
16+
public class JsonRpcServerBuilderInstrumentation implements TypeInstrumentation {
17+
18+
19+
@Override
20+
public ElementMatcher<ClassLoader> classLoaderOptimization() {
21+
return hasClassesNamed("com.googlecode.jsonrpc4j.JsonRpcBasicServer");
22+
}
23+
24+
@Override
25+
public ElementMatcher<TypeDescription> typeMatcher() {
26+
return named("com.googlecode.jsonrpc4j.JsonRpcBasicServer");
27+
}
28+
29+
@Override
30+
public void transform(TypeTransformer transformer) {
31+
transformer.applyAdviceToMethod(
32+
isConstructor(),
33+
this.getClass().getName() + "$ConstructorAdvice");
34+
}
35+
36+
@SuppressWarnings("unused")
37+
public static class ConstructorAdvice {
38+
39+
@Advice.OnMethodExit(suppress = Throwable.class)
40+
public static void setInvocationListener(
41+
@Advice.This JsonRpcBasicServer jsonRpcServer,
42+
@Advice.FieldValue("invocationListener") InvocationListener invocationListener) {
43+
VirtualField<JsonRpcBasicServer, Boolean> instrumented =
44+
VirtualField.find(JsonRpcBasicServer.class, Boolean.class);
45+
if (!Boolean.TRUE.equals(instrumented.get(jsonRpcServer))) {
46+
jsonRpcServer.setInvocationListener(JsonRpcSingletons.SERVER_INVOCATION_LISTENER);
47+
instrumented.set(jsonRpcServer, true);
48+
}
49+
}
50+
}
51+
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6;
2+
3+
import com.googlecode.jsonrpc4j.InvocationListener;
4+
import io.opentelemetry.api.GlobalOpenTelemetry;
5+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
6+
import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.JsonRpcTelemetry;
7+
import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.SimpleJsonRpcRequest;
8+
import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.SimpleJsonRpcResponse;
9+
10+
11+
public final class JsonRpcSingletons {
12+
13+
public static final InvocationListener SERVER_INVOCATION_LISTENER;
14+
15+
public static final Instrumenter<SimpleJsonRpcRequest, SimpleJsonRpcResponse> CLIENT_INSTRUMENTER;
16+
17+
18+
static {
19+
JsonRpcTelemetry telemetry =
20+
JsonRpcTelemetry.builder(GlobalOpenTelemetry.get())
21+
.build();
22+
23+
SERVER_INVOCATION_LISTENER = telemetry.newServerInvocationListener();
24+
CLIENT_INSTRUMENTER = telemetry.getClientInstrumenter();
25+
}
26+
27+
28+
private JsonRpcSingletons() {}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6;
2+
3+
import com.googlecode.jsonrpc4j.JsonRpcServer;
4+
import com.googlecode.jsonrpc4j.spring.JsonServiceExporter;
5+
import io.opentelemetry.instrumentation.api.util.VirtualField;
6+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
7+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
8+
import net.bytebuddy.asm.Advice;
9+
import net.bytebuddy.description.type.TypeDescription;
10+
import net.bytebuddy.matcher.ElementMatcher;
11+
12+
13+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
14+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
15+
import static net.bytebuddy.matcher.ElementMatchers.named;
16+
17+
public class JsonServiceExporterBuilderInstrumentation implements TypeInstrumentation {
18+
@Override
19+
public ElementMatcher<ClassLoader> classLoaderOptimization() {
20+
return hasClassesNamed("com.googlecode.jsonrpc4j.spring.JsonServiceExporter");
21+
}
22+
23+
@Override
24+
public ElementMatcher<TypeDescription> typeMatcher() {
25+
return named("com.googlecode.jsonrpc4j.spring.JsonServiceExporter");
26+
}
27+
28+
@Override
29+
public void transform(TypeTransformer transformer) {
30+
transformer.applyAdviceToMethod(
31+
isMethod().and(named("exportService")),
32+
this.getClass().getName() + "$ExportAdvice");
33+
}
34+
35+
@SuppressWarnings("unused")
36+
public static class ExportAdvice {
37+
38+
@Advice.OnMethodExit(suppress = Throwable.class)
39+
public static void setInvocationListener(
40+
@Advice.This JsonServiceExporter exporter,
41+
@Advice.FieldValue("jsonRpcServer") JsonRpcServer jsonRpcServer) {
42+
VirtualField<JsonRpcServer, Boolean> instrumented =
43+
VirtualField.find(JsonRpcServer.class, Boolean.class);
44+
if (!Boolean.TRUE.equals(instrumented.get(jsonRpcServer))) {
45+
jsonRpcServer.setInvocationListener(JsonRpcSingletons.SERVER_INVOCATION_LISTENER);
46+
instrumented.set(jsonRpcServer, true);
47+
}
48+
}
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6;
2+
3+
import com.googlecode.jsonrpc4j.JsonRpcBasicServer;
4+
import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.AbstractJsonRpcTest;
5+
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
6+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
7+
import org.junit.jupiter.api.extension.RegisterExtension;
8+
9+
public class AgentJsonRpcTest extends AbstractJsonRpcTest {
10+
11+
12+
@RegisterExtension
13+
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
14+
15+
16+
@Override
17+
protected InstrumentationExtension testing() {
18+
return testing;
19+
}
20+
21+
@Override
22+
protected JsonRpcBasicServer configureServer(JsonRpcBasicServer server) {
23+
return server;
24+
}
25+
26+
27+
28+
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
plugins {
2+
id("otel.library-instrumentation")
3+
}
4+
5+
val jsonrpcVersion = "1.6"
6+
7+
dependencies {
8+
implementation("com.github.briandilley.jsonrpc4j:jsonrpc4j:$jsonrpcVersion")
9+
10+
implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3")
11+
12+
testImplementation(project(":instrumentation:jsonrpc4j-1.6:testing"))
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.opentelemetry.instrumentation.jsonrpc4j.v1_6;
2+
3+
import io.opentelemetry.api.common.AttributesBuilder;
4+
import io.opentelemetry.context.Context;
5+
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
6+
import javax.annotation.Nullable;
7+
8+
// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
9+
final class JsonRpcClientAttributesExtractor implements AttributesExtractor<SimpleJsonRpcRequest, SimpleJsonRpcResponse> {
10+
11+
// private final JsonRpcClientAttributesGetter getter;
12+
//
13+
//
14+
// JsonRpcClientAttributesExtractor(JsonRpcClientAttributesGetter getter) {
15+
// this.getter = getter;
16+
// }
17+
18+
@Override
19+
public void onStart(AttributesBuilder attributes, Context parentContext,
20+
SimpleJsonRpcRequest jsonRpcRequest) {
21+
attributes.put("rpc.jsonrpc.version", "2.0");
22+
}
23+
24+
@Override
25+
public void onEnd(
26+
AttributesBuilder attributes,
27+
Context context,
28+
SimpleJsonRpcRequest jsonRpcRequest,
29+
@Nullable SimpleJsonRpcResponse jsonRpcResponse,
30+
@Nullable Throwable error) {
31+
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.opentelemetry.instrumentation.jsonrpc4j.v1_6;
2+
3+
import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter;
4+
5+
// Check https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#attributes
6+
// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
7+
public enum JsonRpcClientAttributesGetter implements RpcAttributesGetter<SimpleJsonRpcRequest> {
8+
INSTANCE;
9+
10+
@Override
11+
public String getSystem(SimpleJsonRpcRequest request) {
12+
return "jsonrpc";
13+
}
14+
15+
@Override
16+
public String getService(SimpleJsonRpcRequest request) {
17+
// TODO
18+
return "NOT_IMPLEMENTED";
19+
}
20+
21+
@Override
22+
public String getMethod(SimpleJsonRpcRequest request) {
23+
return request.getMethodName();
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.opentelemetry.instrumentation.jsonrpc4j.v1_6;
2+
3+
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
4+
5+
public class JsonRpcClientSpanNameExtractor implements SpanNameExtractor<SimpleJsonRpcRequest> {
6+
@Override
7+
public String extract(SimpleJsonRpcRequest request) {
8+
return request.getMethodName();
9+
}
10+
}

0 commit comments

Comments
 (0)