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

feat: support instrumentation for jsonrpc4j #13008

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ These are the supported libraries and frameworks:
| [Jedis](https://github.com/xetorthio/jedis) | 1.4+ | N/A | [Database Client Spans] |
| [JMS](https://javaee.github.io/javaee-spec/javadocs/javax/jms/package-summary.html) | 1.1+ | N/A | [Messaging Spans] |
| [Jodd Http](https://http.jodd.org/) | 4.2+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
| [JSON-RPC for Java](https://github.com/briandilley/jsonrpc4j) | 1.3.3+ | N/A | [RPC Client Spans], [RPC Client Metrics], [RPC Server Spans], [RPC Server Metrics] |
| [JSP](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/jsp/package-summary.html) | 2.3.x only | N/A | Controller Spans [3] |
| [Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) | 1.0+ | N/A | Context propagation |
| [Ktor](https://github.com/ktorio/ktor) | 1.0+ | [opentelemetry-ktor-1.0](../instrumentation/ktor/ktor-1.0/library),<br>[opentelemetry-ktor-2.0](../instrumentation/ktor/ktor-2.0/library),<br>[opentelemetry-ktor-3.0](../instrumentation/ktor/ktor-3.0/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] |
Expand Down
24 changes: 24 additions & 0 deletions instrumentation/jsonrpc4j-1.3/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("com.github.briandilley.jsonrpc4j")
module.set("jsonrpc4j")
versions.set("[1.3.3,)")
assertInverse.set(true)
}
}

dependencies {
implementation(project(":instrumentation:jsonrpc4j-1.3:library"))

library("com.github.briandilley.jsonrpc4j:jsonrpc4j:1.3.3")

testImplementation(project(":instrumentation:jsonrpc4j-1.3:testing"))
testImplementation("com.fasterxml.jackson.core:jackson-databind:2.13.3")
testImplementation("org.eclipse.jetty:jetty-server:9.4.49.v20220914")
testImplementation("org.eclipse.jetty:jetty-servlet:9.4.49.v20220914")
testImplementation("javax.portlet:portlet-api:2.0")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import javax.annotation.Nullable;

// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
final class JsonRpcClientAttributesExtractor
implements AttributesExtractor<JsonRpcClientRequest, JsonRpcClientResponse> {

// copied from RpcIncubatingAttributes
private static final AttributeKey<String> RPC_JSONRPC_VERSION =
AttributeKey.stringKey("rpc.jsonrpc.version");

@Override
public void onStart(
AttributesBuilder attributes, Context parentContext, JsonRpcClientRequest jsonRpcRequest) {
attributes.put(RPC_JSONRPC_VERSION, "2.0");
}

@Override
public void onEnd(
AttributesBuilder attributes,
Context context,
JsonRpcClientRequest jsonRpcRequest,
@Nullable JsonRpcClientResponse jsonRpcResponse,
@Nullable Throwable error) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;

import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter;

// Check
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#attributes
// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
enum JsonRpcClientAttributesGetter implements RpcAttributesGetter<JsonRpcClientRequest> {
INSTANCE;

@Override
public String getSystem(JsonRpcClientRequest request) {
return "jsonrpc";
}

@Override
public String getService(JsonRpcClientRequest request) {
if (request.getMethod() != null) {
return request.getMethod().getDeclaringClass().getName();
}
return null;
}

@Override
public String getMethod(JsonRpcClientRequest request) {
return request.getMethodName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3.JsonRpcSingletons.CLIENT_INSTRUMENTER;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

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 java.lang.reflect.Type;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

public class JsonRpcClientInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("com.googlecode.jsonrpc4j.IJsonRpcClient");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
// match JsonRpcHttpClient and JsonRpcRestClient
return implementsInterface(named("com.googlecode.jsonrpc4j.IJsonRpcClient"));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("invoke"))
.and(takesArguments(4))
.and(takesArgument(0, String.class))
.and(takesArgument(1, Object.class))
.and(takesArgument(2, Type.class))
.and(takesArgument(3, Map.class))
.and(returns(Object.class)),
this.getClass().getName() + "$InvokeAdvice");
}

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

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) String methodName,
@Advice.Argument(1) Object argument,
@Advice.Argument(3) Map<String, String> extraHeaders,
@Advice.Local("otelRequest") JsonRpcClientRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = Context.current();
request = new JsonRpcClientRequest(methodName, argument);
if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, request)) {
return;
}

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

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(
@Advice.Argument(0) String methodName,
@Advice.Argument(1) Object argument,
@Advice.Argument(3) Map<String, String> extraHeaders,
@Advice.Return Object result,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelRequest") JsonRpcClientRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}

scope.close();
CLIENT_INSTRUMENTER.end(context, request, new JsonRpcClientResponse(result), throwable);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;

import java.lang.reflect.Method;

public final class JsonRpcClientRequest {

private final String methodName;
private final Object argument;
private Method method;

public JsonRpcClientRequest(String methodName, Object argument) {
this.methodName = methodName;
this.argument = argument;
}

public JsonRpcClientRequest(Method method, Object argument) {
this.method = method;
this.methodName = method.getName();
this.argument = argument;
}

public String getMethodName() {
return methodName;
}

public Object getArgument() {
return argument;
}

public Method getMethod() {
return method;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;

public final class JsonRpcClientResponse {

private final Object result;

public JsonRpcClientResponse(Object result) {
this.result = result;
}

public Object getResult() {
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;

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

final class JsonRpcClientSpanNameExtractor implements SpanNameExtractor<JsonRpcClientRequest> {
@Override
public String extract(JsonRpcClientRequest request) {
if (request.getMethod() == null) {
return request.getMethodName();
}
Method method = request.getMethod();
return String.format("%s/%s", method.getDeclaringClass().getName(), method.getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;

public final class JsonRpcClientTelemetry {
public static JsonRpcClientTelemetry create(OpenTelemetry openTelemetry) {
return builder(openTelemetry).build();
}

public static JsonRpcClientTelemetryBuilder builder(OpenTelemetry openTelemetry) {
return new JsonRpcClientTelemetryBuilder(openTelemetry);
}

private final Instrumenter<JsonRpcClientRequest, JsonRpcClientResponse> clientInstrumenter;

JsonRpcClientTelemetry(
Instrumenter<JsonRpcClientRequest, JsonRpcClientResponse> clientInstrumenter) {
this.clientInstrumenter = clientInstrumenter;
}

public Instrumenter<JsonRpcClientRequest, JsonRpcClientResponse> getClientInstrumenter() {
return clientInstrumenter;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientAttributesExtractor;
import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import java.util.ArrayList;
import java.util.List;

public class JsonRpcClientTelemetryBuilder {

private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jsonrpc4j-1.3";

private final OpenTelemetry openTelemetry;

private final List<
AttributesExtractor<? super JsonRpcClientRequest, ? super JsonRpcClientResponse>>
additionalClientExtractors = new ArrayList<>();

JsonRpcClientTelemetryBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}

/**
* Adds an extra client-only {@link AttributesExtractor} to invoke to set attributes to
* instrumented items. The {@link AttributesExtractor} will be executed after all default
* extractors.
*/
@CanIgnoreReturnValue
public JsonRpcClientTelemetryBuilder addClientAttributeExtractor(
AttributesExtractor<? super JsonRpcClientRequest, ? super JsonRpcClientResponse>
attributesExtractor) {
additionalClientExtractors.add(attributesExtractor);
return this;
}

public JsonRpcClientTelemetry build() {
SpanNameExtractor<JsonRpcClientRequest> clientSpanNameExtractor =
new JsonRpcClientSpanNameExtractor();

InstrumenterBuilder<JsonRpcClientRequest, JsonRpcClientResponse> clientInstrumenterBuilder =
Instrumenter.builder(openTelemetry, INSTRUMENTATION_NAME, clientSpanNameExtractor);

JsonRpcClientAttributesGetter clientRpcAttributesGetter =
JsonRpcClientAttributesGetter.INSTANCE;

clientInstrumenterBuilder
.addAttributesExtractor(RpcClientAttributesExtractor.create(clientRpcAttributesGetter))
.addAttributesExtractors(additionalClientExtractors)
.addAttributesExtractor(new JsonRpcClientAttributesExtractor())
.addOperationMetrics(RpcClientMetrics.get());

return new JsonRpcClientTelemetry(
clientInstrumenterBuilder.buildInstrumenter(SpanKindExtractor.alwaysClient()));
}
}
Loading
Loading