From 8289a288ab477129f0f56bf5da76aa255b404d7b Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Wed, 1 Apr 2026 00:07:24 +0200 Subject: [PATCH 1/8] Add SOFA RPC instrumentation (Bolt, H2C, Triple protocols) --- .../instrumentation/sofarpc/build.gradle | 1 + .../sofarpc/sofarpc-5.0/build.gradle | 20 ++++ .../AbstractClusterInstrumentation.java | 66 ++++++++++ .../ProviderProxyInvokerInstrumentation.java | 74 ++++++++++++ .../sofarpc/SofaRpcClientDecorator.java | 64 ++++++++++ .../sofarpc/SofaRpcExtractAdapter.java | 25 ++++ .../sofarpc/SofaRpcInjectAdapter.java | 14 +++ .../sofarpc/SofaRpcModule.java | 31 +++++ .../sofarpc/SofaRpcServerDecorator.java | 65 ++++++++++ .../sofarpc/SofaRpcTest.groovy | 113 ++++++++++++++++++ settings.gradle.kts | 2 + 11 files changed, 475 insertions(+) create mode 100644 dd-java-agent/instrumentation/sofarpc/build.gradle create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/AbstractClusterInstrumentation.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcClientDecorator.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcExtractAdapter.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcInjectAdapter.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcServerDecorator.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy diff --git a/dd-java-agent/instrumentation/sofarpc/build.gradle b/dd-java-agent/instrumentation/sofarpc/build.gradle new file mode 100644 index 00000000000..5e69c67bd78 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/build.gradle @@ -0,0 +1 @@ +apply from: "$rootDir/gradle/java.gradle" diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle new file mode 100644 index 00000000000..12a076d09ff --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle @@ -0,0 +1,20 @@ +muzzle { + pass { + group = "com.alipay.sofa" + module = "sofa-rpc-all" + versions = "[5.0.0,)" + assertInverse = true + } +} + +apply from: "$rootDir/gradle/java.gradle" + +addTestSuiteForDir('latestDepTest', 'test') + +dependencies { + compileOnly group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.6.0" + + testImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.6.0" + + latestDepTestImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "+" +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/AbstractClusterInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/AbstractClusterInstrumentation.java new file mode 100644 index 00000000000..62371f7495f --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/AbstractClusterInstrumentation.java @@ -0,0 +1,66 @@ +package datadog.trace.instrumentation.sofarpc; + +import static datadog.context.propagation.Propagators.defaultPropagator; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.instrumentation.sofarpc.SofaRpcClientDecorator.DECORATE; +import static datadog.trace.instrumentation.sofarpc.SofaRpcClientDecorator.SOFA_RPC_CLIENT; +import static datadog.trace.instrumentation.sofarpc.SofaRpcInjectAdapter.SETTER; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.alipay.sofa.rpc.core.request.SofaRequest; +import com.alipay.sofa.rpc.core.response.SofaResponse; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import net.bytebuddy.asm.Advice; + +public class AbstractClusterInstrumentation + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + @Override + public String instrumentedType() { + return "com.alipay.sofa.rpc.client.AbstractCluster"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod() + .and(named("invoke")) + .and(takesArguments(1)) + .and(takesArgument(0, named("com.alipay.sofa.rpc.core.request.SofaRequest"))), + getClass().getName() + "$InvokeAdvice"); + } + + public static class InvokeAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope enter(@Advice.Argument(0) SofaRequest request) { + AgentSpan span = startSpan(SOFA_RPC_CLIENT); + DECORATE.afterStart(span); + DECORATE.onRequest(span, request); + AgentScope scope = activateSpan(span); + defaultPropagator().inject(span, request, SETTER); + return scope; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void exit( + @Advice.Enter AgentScope scope, + @Advice.Return SofaResponse response, + @Advice.Thrown Throwable throwable) { + if (scope == null) { + return; + } + AgentSpan span = scope.span(); + DECORATE.onResponse(span, response); + DECORATE.onError(span, throwable); + DECORATE.beforeFinish(span); + span.finish(); + scope.close(); + } + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java new file mode 100644 index 00000000000..6ebd85a8d82 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java @@ -0,0 +1,74 @@ +package datadog.trace.instrumentation.sofarpc; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.instrumentation.sofarpc.SofaRpcExtractAdapter.GETTER; +import static datadog.trace.instrumentation.sofarpc.SofaRpcServerDecorator.DECORATE; +import static datadog.trace.instrumentation.sofarpc.SofaRpcServerDecorator.SOFA_RPC_SERVER; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.alipay.sofa.rpc.core.request.SofaRequest; +import com.alipay.sofa.rpc.core.response.SofaResponse; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import net.bytebuddy.asm.Advice; + +public class ProviderProxyInvokerInstrumentation + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + @Override + public String instrumentedType() { + return "com.alipay.sofa.rpc.server.ProviderProxyInvoker"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod() + .and(named("invoke")) + .and(takesArguments(1)) + .and(takesArgument(0, named("com.alipay.sofa.rpc.core.request.SofaRequest"))), + getClass().getName() + "$InvokeAdvice"); + } + + public static class InvokeAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope enter(@Advice.Argument(0) SofaRequest request) { + // Triple protocol uses gRPC transport: the gRPC instrumentation already handles + // context propagation and creates a grpc.server span. Detect this by checking + // whether an active span is already present on the current thread — if so, skip + // creating a duplicate sofarpc.request span that would start an orphan trace. + if (activeSpan() != null) { + return null; + } + AgentSpanContext parentContext = extractContextAndGetSpanContext(request, GETTER); + AgentSpan span = startSpan(SOFA_RPC_SERVER, parentContext); + DECORATE.afterStart(span); + DECORATE.onRequest(span, request); + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void exit( + @Advice.Enter AgentScope scope, + @Advice.Return SofaResponse response, + @Advice.Thrown Throwable throwable) { + if (scope == null) { + return; + } + AgentSpan span = scope.span(); + DECORATE.onResponse(span, response); + DECORATE.onError(span, throwable); + DECORATE.beforeFinish(span); + span.finish(); + scope.close(); + } + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcClientDecorator.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcClientDecorator.java new file mode 100644 index 00000000000..2d61eecefb7 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcClientDecorator.java @@ -0,0 +1,64 @@ +package datadog.trace.instrumentation.sofarpc; + +import com.alipay.sofa.rpc.core.request.SofaRequest; +import com.alipay.sofa.rpc.core.response.SofaResponse; +import datadog.trace.api.naming.SpanNaming; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import datadog.trace.bootstrap.instrumentation.decorator.ClientDecorator; + +public class SofaRpcClientDecorator extends ClientDecorator { + + public static final CharSequence SOFA_RPC_CLIENT = + UTF8BytesString.create( + SpanNaming.instance().namingSchema().client().operationForProtocol("sofarpc")); + + private static final CharSequence COMPONENT_NAME = UTF8BytesString.create("sofarpc-client"); + + public static final SofaRpcClientDecorator DECORATE = new SofaRpcClientDecorator(); + + @Override + protected String[] instrumentationNames() { + return new String[] {"sofarpc"}; + } + + @Override + protected CharSequence component() { + return COMPONENT_NAME; + } + + @Override + protected CharSequence spanType() { + return InternalSpanTypes.RPC; + } + + @Override + protected String service() { + return null; + } + + public AgentSpan onRequest(AgentSpan span, SofaRequest request) { + if (request == null) { + return span; + } + String serviceName = request.getTargetServiceUniqueName(); + String methodName = request.getMethodName(); + span.setTag(Tags.RPC_SERVICE, serviceName); + if (serviceName != null && methodName != null) { + span.setResourceName(serviceName + "/" + methodName); + } else if (methodName != null) { + span.setResourceName(methodName); + } + return span; + } + + public AgentSpan onResponse(AgentSpan span, SofaResponse response) { + if (response != null && response.isError()) { + span.setError(true); + span.setTag("error.message", response.getErrorMsg()); + } + return span; + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcExtractAdapter.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcExtractAdapter.java new file mode 100644 index 00000000000..b5c36c45258 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcExtractAdapter.java @@ -0,0 +1,25 @@ +package datadog.trace.instrumentation.sofarpc; + +import com.alipay.sofa.rpc.core.request.SofaRequest; +import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; +import java.util.Map; + +public final class SofaRpcExtractAdapter implements AgentPropagation.ContextVisitor { + + public static final SofaRpcExtractAdapter GETTER = new SofaRpcExtractAdapter(); + + @Override + public void forEachKey(SofaRequest carrier, AgentPropagation.KeyClassifier classifier) { + Map props = carrier.getRequestProps(); + if (props == null || props.isEmpty()) { + return; + } + for (Map.Entry entry : props.entrySet()) { + if (entry.getValue() instanceof String) { + if (!classifier.accept(entry.getKey(), (String) entry.getValue())) { + return; + } + } + } + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcInjectAdapter.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcInjectAdapter.java new file mode 100644 index 00000000000..f5f8e4ebe0e --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcInjectAdapter.java @@ -0,0 +1,14 @@ +package datadog.trace.instrumentation.sofarpc; + +import com.alipay.sofa.rpc.core.request.SofaRequest; +import datadog.context.propagation.CarrierSetter; + +public final class SofaRpcInjectAdapter implements CarrierSetter { + + public static final SofaRpcInjectAdapter SETTER = new SofaRpcInjectAdapter(); + + @Override + public void set(SofaRequest carrier, String key, String value) { + carrier.addRequestProp(key, value); + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java new file mode 100644 index 00000000000..90c4556c054 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java @@ -0,0 +1,31 @@ +package datadog.trace.instrumentation.sofarpc; + +import static java.util.Arrays.asList; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import java.util.List; + +@AutoService(InstrumenterModule.class) +public class SofaRpcModule extends InstrumenterModule.Tracing { + + public SofaRpcModule() { + super("sofarpc"); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".SofaRpcClientDecorator", + packageName + ".SofaRpcServerDecorator", + packageName + ".SofaRpcInjectAdapter", + packageName + ".SofaRpcExtractAdapter", + }; + } + + @Override + public List typeInstrumentations() { + return asList(new AbstractClusterInstrumentation(), new ProviderProxyInvokerInstrumentation()); + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcServerDecorator.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcServerDecorator.java new file mode 100644 index 00000000000..6bb14cf6796 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcServerDecorator.java @@ -0,0 +1,65 @@ +package datadog.trace.instrumentation.sofarpc; + +import com.alipay.sofa.rpc.core.request.SofaRequest; +import com.alipay.sofa.rpc.core.response.SofaResponse; +import datadog.trace.api.naming.SpanNaming; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import datadog.trace.bootstrap.instrumentation.decorator.ServerDecorator; + +public class SofaRpcServerDecorator extends ServerDecorator { + + public static final CharSequence SOFA_RPC_SERVER = + UTF8BytesString.create( + SpanNaming.instance().namingSchema().server().operationForProtocol("sofarpc")); + + private static final CharSequence COMPONENT_NAME = UTF8BytesString.create("sofarpc-server"); + + public static final SofaRpcServerDecorator DECORATE = new SofaRpcServerDecorator(); + + @Override + protected String[] instrumentationNames() { + return new String[] {"sofarpc"}; + } + + @Override + protected CharSequence component() { + return COMPONENT_NAME; + } + + @Override + protected CharSequence spanType() { + return InternalSpanTypes.RPC; + } + + @Override + public AgentSpan afterStart(AgentSpan span) { + span.setMeasured(true); + return super.afterStart(span); + } + + public AgentSpan onRequest(AgentSpan span, SofaRequest request) { + if (request == null) { + return span; + } + String serviceName = request.getTargetServiceUniqueName(); + String methodName = request.getMethodName(); + span.setTag(Tags.RPC_SERVICE, serviceName); + if (serviceName != null && methodName != null) { + span.setResourceName(serviceName + "/" + methodName); + } else if (methodName != null) { + span.setResourceName(methodName); + } + return span; + } + + public AgentSpan onResponse(AgentSpan span, SofaResponse response) { + if (response != null && response.isError()) { + span.setError(true); + span.setTag("error.message", response.getErrorMsg()); + } + return span; + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy new file mode 100644 index 00000000000..79808fe58ed --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy @@ -0,0 +1,113 @@ +package datadog.trace.instrumentation.sofarpc + +import com.alipay.sofa.rpc.bootstrap.ProviderBootstrap +import com.alipay.sofa.rpc.config.ApplicationConfig +import com.alipay.sofa.rpc.config.ConsumerConfig +import com.alipay.sofa.rpc.config.ProviderConfig +import com.alipay.sofa.rpc.config.ServerConfig +import datadog.trace.agent.test.InstrumentationSpecification +import datadog.trace.bootstrap.instrumentation.api.Tags +import spock.lang.Shared + +class SofaRpcTest extends InstrumentationSpecification { + + @Shared + int port = 12201 + + @Shared + ProviderBootstrap providerBootstrap + + @Shared + GreeterService greeterService + + def setupSpec() { + ApplicationConfig appConfig = new ApplicationConfig().setAppName("test-server") + + ServerConfig serverConfig = + new ServerConfig() + .setProtocol("bolt") + .setHost("127.0.0.1") + .setPort(port) + + ProviderConfig providerConfig = + new ProviderConfig() + .setApplication(appConfig) + .setInterfaceId(GreeterService.name) + .setRef(new GreeterServiceImpl()) + .setServer(serverConfig) + .setRegister(false) + + providerBootstrap = providerConfig.export() + + ConsumerConfig consumerConfig = + new ConsumerConfig() + .setApplication(new ApplicationConfig().setAppName("test-client")) + .setInterfaceId(GreeterService.name) + .setDirectUrl("bolt://127.0.0.1:${port}") + .setProtocol("bolt") + .setRegister(false) + .setSubscribe(false) + + greeterService = consumerConfig.refer() + } + + def cleanupSpec() { + providerBootstrap?.unExport() + } + + def "client and server spans created for synchronous Bolt RPC call"() { + setup: + String serviceUniqueName = GreeterService.name + ":1.0" + + when: + String reply = greeterService.sayHello("World") + + then: + reply == "Hello, World" + + and: + // Client and server are in the same JVM but use real TCP sockets (Bolt), + // so each side produces its own local trace entry. + assertTraces(2) { + trace(1) { + span { + operationName "sofarpc.request" + resourceName "${serviceUniqueName}/sayHello" + spanType "rpc" + errored false + tags { + "$Tags.RPC_SERVICE" serviceUniqueName + "component" "sofarpc-client" + "span.kind" "client" + defaultTags() + } + } + } + trace(1) { + span { + operationName "sofarpc.request" + resourceName "${serviceUniqueName}/sayHello" + spanType "rpc" + errored false + tags { + "$Tags.RPC_SERVICE" serviceUniqueName + "component" "sofarpc-server" + "span.kind" "server" + defaultTags(true) // distributed root span — parent context propagated via Bolt headers + } + } + } + } + } + + interface GreeterService { + String sayHello(String name) + } + + static class GreeterServiceImpl implements GreeterService { + @Override + String sayHello(String name) { + return "Hello, ${name}" + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 074514e7126..0a1f22be364 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -560,6 +560,8 @@ include( ":dd-java-agent:instrumentation:servlet:javax-servlet:javax-servlet-iast", ":dd-java-agent:instrumentation:slick-3.2", ":dd-java-agent:instrumentation:snakeyaml-1.33", + ":dd-java-agent:instrumentation:sofarpc", + ":dd-java-agent:instrumentation:sofarpc:sofarpc-5.0", ":dd-java-agent:instrumentation:spark:spark-common", ":dd-java-agent:instrumentation:spark:spark_2.12", ":dd-java-agent:instrumentation:spark:spark_2.13", From 2711638cef8504d539edec8b709d921b6144995d Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Wed, 15 Apr 2026 02:01:47 +0200 Subject: [PATCH 2/8] Fix SOFA RPC instrumentation: protocol context, server error detection, scope/span ordering --- .../AbstractClusterInstrumentation.java | 12 +++++- .../BoltServerProcessorInstrumentation.java | 40 +++++++++++++++++++ .../sofarpc/H2cServerTaskInstrumentation.java | 36 +++++++++++++++++ .../ProviderProxyInvokerInstrumentation.java | 26 +++++++----- .../sofarpc/SofaRpcClientDecorator.java | 2 + .../sofarpc/SofaRpcModule.java | 8 +++- .../sofarpc/SofaRpcProtocolContext.java | 21 ++++++++++ .../sofarpc/SofaRpcServerDecorator.java | 12 +++++- .../sofarpc/TripleServerInstrumentation.java | 40 +++++++++++++++++++ 9 files changed, 184 insertions(+), 13 deletions(-) create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/BoltServerProcessorInstrumentation.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/H2cServerTaskInstrumentation.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcProtocolContext.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/AbstractClusterInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/AbstractClusterInstrumentation.java index 62371f7495f..41b52996c5f 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/AbstractClusterInstrumentation.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/AbstractClusterInstrumentation.java @@ -11,6 +11,8 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import com.alipay.sofa.rpc.client.AbstractCluster; +import com.alipay.sofa.rpc.config.ConsumerConfig; import com.alipay.sofa.rpc.core.request.SofaRequest; import com.alipay.sofa.rpc.core.response.SofaResponse; import datadog.trace.agent.tooling.Instrumenter; @@ -38,10 +40,16 @@ public void methodAdvice(MethodTransformer transformer) { public static class InvokeAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static AgentScope enter(@Advice.Argument(0) SofaRequest request) { + public static AgentScope enter( + @Advice.This AbstractCluster self, @Advice.Argument(0) SofaRequest request) { + ConsumerConfig config = self.getConsumerConfig(); + String protocol = config != null ? config.getProtocol() : null; AgentSpan span = startSpan(SOFA_RPC_CLIENT); DECORATE.afterStart(span); DECORATE.onRequest(span, request); + if (protocol != null) { + span.setTag("sofarpc.protocol", protocol); + } AgentScope scope = activateSpan(span); defaultPropagator().inject(span, request, SETTER); return scope; @@ -59,8 +67,8 @@ public static void exit( DECORATE.onResponse(span, response); DECORATE.onError(span, throwable); DECORATE.beforeFinish(span); - span.finish(); scope.close(); + span.finish(); } } } diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/BoltServerProcessorInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/BoltServerProcessorInstrumentation.java new file mode 100644 index 00000000000..35622f2a105 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/BoltServerProcessorInstrumentation.java @@ -0,0 +1,40 @@ +package datadog.trace.instrumentation.sofarpc; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import net.bytebuddy.asm.Advice; + +public class BoltServerProcessorInstrumentation + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + @Override + public String instrumentedType() { + return "com.alipay.sofa.rpc.server.bolt.BoltServerProcessor"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod() + .and(named("handleRequest")) + .and(takesArguments(3)) + .and(takesArgument(2, named("com.alipay.sofa.rpc.core.request.SofaRequest"))), + getClass().getName() + "$HandleRequestAdvice"); + } + + public static class HandleRequestAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void enter() { + SofaRpcProtocolContext.set("bolt"); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void exit() { + SofaRpcProtocolContext.clear(); + } + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/H2cServerTaskInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/H2cServerTaskInstrumentation.java new file mode 100644 index 00000000000..caf84fb6219 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/H2cServerTaskInstrumentation.java @@ -0,0 +1,36 @@ +package datadog.trace.instrumentation.sofarpc; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import net.bytebuddy.asm.Advice; + +public class H2cServerTaskInstrumentation + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + @Override + public String instrumentedType() { + return "com.alipay.sofa.rpc.server.http.AbstractHttpServerTask"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod().and(named("run")).and(takesNoArguments()), + getClass().getName() + "$RunAdvice"); + } + + public static class RunAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void enter() { + SofaRpcProtocolContext.set("h2c"); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void exit() { + SofaRpcProtocolContext.clear(); + } + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java index 6ebd85a8d82..252a75c4f88 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java @@ -3,7 +3,6 @@ import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; import static datadog.trace.instrumentation.sofarpc.SofaRpcExtractAdapter.GETTER; import static datadog.trace.instrumentation.sofarpc.SofaRpcServerDecorator.DECORATE; @@ -41,17 +40,26 @@ public void methodAdvice(MethodTransformer transformer) { public static class InvokeAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static AgentScope enter(@Advice.Argument(0) SofaRequest request) { - // Triple protocol uses gRPC transport: the gRPC instrumentation already handles - // context propagation and creates a grpc.server span. Detect this by checking - // whether an active span is already present on the current thread — if so, skip - // creating a duplicate sofarpc.request span that would start an orphan trace. - if (activeSpan() != null) { + // Protocol is set in thread-local by transport-specific instrumentation before this call. + // If null, the transport is not instrumented — skip. + String protocol = SofaRpcProtocolContext.get(); + if (protocol == null) { return null; } - AgentSpanContext parentContext = extractContextAndGetSpanContext(request, GETTER); - AgentSpan span = startSpan(SOFA_RPC_SERVER, parentContext); + AgentSpan span; + if ("bolt".equals(protocol) || "h2c".equals(protocol)) { + // Bolt propagates Datadog trace headers via SofaRequest.requestProps — extract from there. + AgentSpanContext parentContext = extractContextAndGetSpanContext(request, GETTER); + span = startSpan(SOFA_RPC_SERVER, parentContext); + } else { + // For Triple and other protocols, trace context is propagated at the transport layer + // (e.g. gRPC Metadata). The transport instrumentation already activated the parent span, + // so startSpan without an explicit parent inherits it automatically. + span = startSpan(SOFA_RPC_SERVER); + } DECORATE.afterStart(span); DECORATE.onRequest(span, request); + span.setTag("sofarpc.protocol", protocol); return activateSpan(span); } @@ -67,8 +75,8 @@ public static void exit( DECORATE.onResponse(span, response); DECORATE.onError(span, throwable); DECORATE.beforeFinish(span); - span.finish(); scope.close(); + span.finish(); } } } diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcClientDecorator.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcClientDecorator.java index 2d61eecefb7..c53ff49eafe 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcClientDecorator.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcClientDecorator.java @@ -40,12 +40,14 @@ protected String service() { } public AgentSpan onRequest(AgentSpan span, SofaRequest request) { + span.setTag("rpc.system", "sofarpc"); if (request == null) { return span; } String serviceName = request.getTargetServiceUniqueName(); String methodName = request.getMethodName(); span.setTag(Tags.RPC_SERVICE, serviceName); + // peer.service is derived automatically by PeerServiceCalculator from rpc.service. if (serviceName != null && methodName != null) { span.setResourceName(serviceName + "/" + methodName); } else if (methodName != null) { diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java index 90c4556c054..5714d34465b 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java @@ -21,11 +21,17 @@ public String[] helperClassNames() { packageName + ".SofaRpcServerDecorator", packageName + ".SofaRpcInjectAdapter", packageName + ".SofaRpcExtractAdapter", + packageName + ".SofaRpcProtocolContext", }; } @Override public List typeInstrumentations() { - return asList(new AbstractClusterInstrumentation(), new ProviderProxyInvokerInstrumentation()); + return asList( + new AbstractClusterInstrumentation(), + new BoltServerProcessorInstrumentation(), + new H2cServerTaskInstrumentation(), + new TripleServerInstrumentation(), + new ProviderProxyInvokerInstrumentation()); } } diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcProtocolContext.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcProtocolContext.java new file mode 100644 index 00000000000..09bbafae508 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcProtocolContext.java @@ -0,0 +1,21 @@ +package datadog.trace.instrumentation.sofarpc; + +/** Thread-local carrier for the SOFA RPC transport protocol name. */ +public final class SofaRpcProtocolContext { + + private static final ThreadLocal PROTOCOL = new ThreadLocal<>(); + + private SofaRpcProtocolContext() {} + + public static void set(String protocol) { + PROTOCOL.set(protocol); + } + + public static String get() { + return PROTOCOL.get(); + } + + public static void clear() { + PROTOCOL.remove(); + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcServerDecorator.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcServerDecorator.java index 6bb14cf6796..c5dc40af715 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcServerDecorator.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcServerDecorator.java @@ -41,6 +41,7 @@ public AgentSpan afterStart(AgentSpan span) { } public AgentSpan onRequest(AgentSpan span, SofaRequest request) { + span.setTag("rpc.system", "sofarpc"); if (request == null) { return span; } @@ -56,9 +57,18 @@ public AgentSpan onRequest(AgentSpan span, SofaRequest request) { } public AgentSpan onResponse(AgentSpan span, SofaResponse response) { - if (response != null && response.isError()) { + if (response == null) { + return span; + } + if (response.isError()) { + // RPC-layer error (timeout, serialization failure, etc.) span.setError(true); span.setTag("error.message", response.getErrorMsg()); + } else if (response.getAppResponse() instanceof Throwable) { + // Application exception: ProviderProxyInvoker catches it and stores in appResponse + Throwable t = (Throwable) response.getAppResponse(); + span.setError(true); + span.setTag("error.message", t.getMessage()); } return span; } diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java new file mode 100644 index 00000000000..38abc4fb22b --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java @@ -0,0 +1,40 @@ +package datadog.trace.instrumentation.sofarpc; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import net.bytebuddy.asm.Advice; + +public class TripleServerInstrumentation + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + @Override + public String instrumentedType() { + return "com.alipay.sofa.rpc.server.triple.UniqueIdInvoker"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod() + .and(named("invoke")) + .and(takesArguments(1)) + .and(takesArgument(0, named("com.alipay.sofa.rpc.core.request.SofaRequest"))), + getClass().getName() + "$InvokeAdvice"); + } + + public static class InvokeAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void enter() { + SofaRpcProtocolContext.set("tri"); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void exit() { + SofaRpcProtocolContext.clear(); + } + } +} From 530087ef89709f4b525f8c39b70d56d98636c91e Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Wed, 15 Apr 2026 02:03:33 +0200 Subject: [PATCH 3/8] Add integration tests for SOFA RPC Bolt: happy path, distributed trace, server error --- .../sofarpc/SofaRpcTest.groovy | 133 +++++++++++++++++- 1 file changed, 126 insertions(+), 7 deletions(-) diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy index 79808fe58ed..c572b499bc9 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy @@ -9,20 +9,33 @@ import datadog.trace.agent.test.InstrumentationSpecification import datadog.trace.bootstrap.instrumentation.api.Tags import spock.lang.Shared +import static datadog.trace.agent.test.utils.TraceUtils.basicSpan +import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace + class SofaRpcTest extends InstrumentationSpecification { @Shared int port = 12201 + @Shared + int errorPort = 12202 + @Shared ProviderBootstrap providerBootstrap + @Shared + ProviderBootstrap errorProviderBootstrap + @Shared GreeterService greeterService + @Shared + FaultyService faultyService + def setupSpec() { ApplicationConfig appConfig = new ApplicationConfig().setAppName("test-server") + // Happy-path server: Bolt on port 12201 ServerConfig serverConfig = new ServerConfig() .setProtocol("bolt") @@ -39,7 +52,7 @@ class SofaRpcTest extends InstrumentationSpecification { providerBootstrap = providerConfig.export() - ConsumerConfig consumerConfig = + greeterService = new ConsumerConfig() .setApplication(new ApplicationConfig().setAppName("test-client")) .setInterfaceId(GreeterService.name) @@ -47,12 +60,39 @@ class SofaRpcTest extends InstrumentationSpecification { .setProtocol("bolt") .setRegister(false) .setSubscribe(false) + .refer() + + // Error-path server: Bolt on port 12202, separate interface to avoid registry conflict + ServerConfig errorServerConfig = + new ServerConfig() + .setProtocol("bolt") + .setHost("127.0.0.1") + .setPort(errorPort) + + ProviderConfig errorProviderConfig = + new ProviderConfig() + .setApplication(appConfig) + .setInterfaceId(FaultyService.name) + .setRef(new FaultyServiceImpl()) + .setServer(errorServerConfig) + .setRegister(false) - greeterService = consumerConfig.refer() + errorProviderBootstrap = errorProviderConfig.export() + + faultyService = + new ConsumerConfig() + .setApplication(new ApplicationConfig().setAppName("test-client")) + .setInterfaceId(FaultyService.name) + .setDirectUrl("bolt://127.0.0.1:${errorPort}") + .setProtocol("bolt") + .setRegister(false) + .setSubscribe(false) + .refer() } def cleanupSpec() { providerBootstrap?.unExport() + errorProviderBootstrap?.unExport() } def "client and server spans created for synchronous Bolt RPC call"() { @@ -60,40 +100,106 @@ class SofaRpcTest extends InstrumentationSpecification { String serviceUniqueName = GreeterService.name + ":1.0" when: - String reply = greeterService.sayHello("World") + // runUnderTrace gives the client trace 2 spans (caller + sofarpc.request), making it + // unambiguously distinguishable from the 1-span server trace in assertTraces below. + String reply = runUnderTrace("caller") { greeterService.sayHello("World") } then: reply == "Hello, World" and: - // Client and server are in the same JVM but use real TCP sockets (Bolt), - // so each side produces its own local trace entry. assertTraces(2) { - trace(1) { + // trace(0): client side — 2 spans [caller, sofarpc.request(client)] + trace(2) { + basicSpan(it, "caller") span { operationName "sofarpc.request" resourceName "${serviceUniqueName}/sayHello" spanType "rpc" errored false + childOf span(0) tags { "$Tags.RPC_SERVICE" serviceUniqueName + "rpc.system" "sofarpc" + "sofarpc.protocol" "bolt" "component" "sofarpc-client" "span.kind" "client" + peerServiceFrom(Tags.RPC_SERVICE) defaultTags() } } } + // trace(1): server side — 1 span [sofarpc.request(server)], child of client span trace(1) { span { operationName "sofarpc.request" resourceName "${serviceUniqueName}/sayHello" spanType "rpc" errored false + childOf trace(0).get(1) tags { "$Tags.RPC_SERVICE" serviceUniqueName + "rpc.system" "sofarpc" + "sofarpc.protocol" "bolt" "component" "sofarpc-server" "span.kind" "server" - defaultTags(true) // distributed root span — parent context propagated via Bolt headers + defaultTags(true) + } + } + } + } + } + + def "server error is marked on server span"() { + setup: + String serviceUniqueName = FaultyService.name + ":1.0" + + when: + // SOFA RPC Bolt propagates server exceptions back to the client as a SofaRpcException. + // The client-side AbstractCluster.invoke() returns the SofaResponse to the proxy layer, + // which then throws — after our instrumentation's OnMethodExit has already closed the scope. + // So the CLIENT span is not errored; only the SERVER span reflects the error. + faultyService.fail() + + then: + thrown(Exception) + + and: + assertTraces(2) { + // Traces sorted by root-span start time. Client span starts first (initiates the call), + // so the client trace is trace(0) and the server trace is trace(1). + trace(1) { + span { + operationName "sofarpc.request" + resourceName "${serviceUniqueName}/fail" + spanType "rpc" + errored false + tags { + "$Tags.RPC_SERVICE" serviceUniqueName + "rpc.system" "sofarpc" + "sofarpc.protocol" "bolt" + "component" "sofarpc-client" + "span.kind" "client" + peerServiceFrom(Tags.RPC_SERVICE) + defaultTags() + } + } + } + trace(1) { + span { + operationName "sofarpc.request" + resourceName "${serviceUniqueName}/fail" + spanType "rpc" + errored true + childOf trace(0).get(0) + tags { + "$Tags.RPC_SERVICE" serviceUniqueName + "rpc.system" "sofarpc" + "sofarpc.protocol" "bolt" + "component" "sofarpc-server" + "span.kind" "server" + "error.message" { String } + defaultTags(true) } } } @@ -110,4 +216,17 @@ class SofaRpcTest extends InstrumentationSpecification { return "Hello, ${name}" } } + + interface FaultyService { + // Non-void return type: SOFA RPC Bolt throws SofaRpcException on client side + // when the server returns an error response, which is what we verify in the test. + String fail() + } + + static class FaultyServiceImpl implements FaultyService { + @Override + String fail() { + throw new IllegalStateException("something went wrong") + } + } } From 4c54caf4f6b402b07fecf9bd538edf8f2a9acffb Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Wed, 15 Apr 2026 15:58:24 +0200 Subject: [PATCH 4/8] Split sofarpc into sofarpc-5.0/sofarpc-5.14 modules, fix Triple+gRPC span hierarchy, add REST and Triple tests --- .../sofarpc/sofarpc-5.0/build.gradle | 2 + .../ProviderProxyInvokerInstrumentation.java | 24 ++- .../sofarpc/SofaRpcModule.java | 1 - .../sofarpc/SofaRpcProtocolContext.java | 20 +- .../sofarpc/TripleServerInstrumentation.java | 40 ---- .../sofarpc/SofaRpcRestTest.groovy | 125 +++++++++++++ .../sofarpc/sofarpc-5.14/build.gradle | 47 +++++ .../sofarpc/SofaRpcTripleModule.java | 27 +++ .../TripleGrpcMetadataExtractAdapter.java | 32 ++++ .../sofarpc/TripleServerInstrumentation.java | 61 ++++++ .../SofaRpcTripleNoGrpcForkedTest.groovy | 173 ++++++++++++++++++ .../SofaRpcTripleWithGrpcForkedTest.groovy | 113 ++++++++++++ settings.gradle.kts | 1 + 13 files changed, 614 insertions(+), 52 deletions(-) delete mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcRestTest.groovy create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/build.gradle create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcTripleModule.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleGrpcMetadataExtractAdapter.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleNoGrpcForkedTest.groovy create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleWithGrpcForkedTest.groovy diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle index 12a076d09ff..ee19a2b79da 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle @@ -15,6 +15,8 @@ dependencies { compileOnly group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.6.0" testImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.6.0" + // JAX-RS annotations required for the REST protocol test interface (@Path, @GET, etc.) + testImplementation group: "javax.ws.rs", name: "javax.ws.rs-api", version: "2.1.1" latestDepTestImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "+" } diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java index 252a75c4f88..1f606fbbeed 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java @@ -46,17 +46,21 @@ public static AgentScope enter(@Advice.Argument(0) SofaRequest request) { if (protocol == null) { return null; } - AgentSpan span; - if ("bolt".equals(protocol) || "h2c".equals(protocol)) { - // Bolt propagates Datadog trace headers via SofaRequest.requestProps — extract from there. - AgentSpanContext parentContext = extractContextAndGetSpanContext(request, GETTER); - span = startSpan(SOFA_RPC_SERVER, parentContext); - } else { - // For Triple and other protocols, trace context is propagated at the transport layer - // (e.g. gRPC Metadata). The transport instrumentation already activated the parent span, - // so startSpan without an explicit parent inherits it automatically. - span = startSpan(SOFA_RPC_SERVER); + // Prefer the parent context stored by transport-specific instrumentation (e.g. + // TripleServerInstrumentation reads it from gRPC Metadata). For Bolt and H2C the + // transport instrumentations only set the protocol, so parentContext is null here and + // we fall back to extracting from SofaRequest.requestProps as before. + AgentSpanContext parentContext = SofaRpcProtocolContext.getParentContext(); + if (parentContext == null) { + parentContext = extractContextAndGetSpanContext(request, GETTER); } + // parentContext may be null for Triple+gRPC-enabled: TripleServerInstrumentation skips + // Metadata extraction when a grpc.server span is already active. In that case + // startSpan() without an explicit parent naturally attaches to the active grpc.server span. + AgentSpan span = + parentContext != null + ? startSpan(SOFA_RPC_SERVER, parentContext) + : startSpan(SOFA_RPC_SERVER); DECORATE.afterStart(span); DECORATE.onRequest(span, request); span.setTag("sofarpc.protocol", protocol); diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java index 5714d34465b..a1620b627da 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java @@ -31,7 +31,6 @@ public List typeInstrumentations() { new AbstractClusterInstrumentation(), new BoltServerProcessorInstrumentation(), new H2cServerTaskInstrumentation(), - new TripleServerInstrumentation(), new ProviderProxyInvokerInstrumentation()); } } diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcProtocolContext.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcProtocolContext.java index 09bbafae508..08ba7bfbc6c 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcProtocolContext.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcProtocolContext.java @@ -1,9 +1,18 @@ package datadog.trace.instrumentation.sofarpc; -/** Thread-local carrier for the SOFA RPC transport protocol name. */ +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; + +/** + * Thread-local carrier for the SOFA RPC transport protocol name and the propagated parent span + * context. The parent context is extracted by transport-specific instrumentation (e.g. + * TripleServerInstrumentation reads it from gRPC Metadata) so that ProviderProxyInvokerInstrumentation + * can start the server span with an explicit parent regardless of whether other instrumentation + * (e.g. gRPC) is enabled. + */ public final class SofaRpcProtocolContext { private static final ThreadLocal PROTOCOL = new ThreadLocal<>(); + private static final ThreadLocal PARENT_CONTEXT = new ThreadLocal<>(); private SofaRpcProtocolContext() {} @@ -11,11 +20,20 @@ public static void set(String protocol) { PROTOCOL.set(protocol); } + public static void setParentContext(AgentSpanContext parentContext) { + PARENT_CONTEXT.set(parentContext); + } + public static String get() { return PROTOCOL.get(); } + public static AgentSpanContext getParentContext() { + return PARENT_CONTEXT.get(); + } + public static void clear() { PROTOCOL.remove(); + PARENT_CONTEXT.remove(); } } diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java deleted file mode 100644 index 38abc4fb22b..00000000000 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java +++ /dev/null @@ -1,40 +0,0 @@ -package datadog.trace.instrumentation.sofarpc; - -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import datadog.trace.agent.tooling.Instrumenter; -import net.bytebuddy.asm.Advice; - -public class TripleServerInstrumentation - implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { - - @Override - public String instrumentedType() { - return "com.alipay.sofa.rpc.server.triple.UniqueIdInvoker"; - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - isMethod() - .and(named("invoke")) - .and(takesArguments(1)) - .and(takesArgument(0, named("com.alipay.sofa.rpc.core.request.SofaRequest"))), - getClass().getName() + "$InvokeAdvice"); - } - - public static class InvokeAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void enter() { - SofaRpcProtocolContext.set("tri"); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void exit() { - SofaRpcProtocolContext.clear(); - } - } -} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcRestTest.groovy b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcRestTest.groovy new file mode 100644 index 00000000000..2673e896f88 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcRestTest.groovy @@ -0,0 +1,125 @@ +package datadog.trace.instrumentation.sofarpc + +import com.alipay.sofa.rpc.bootstrap.ProviderBootstrap +import com.alipay.sofa.rpc.config.ApplicationConfig +import com.alipay.sofa.rpc.config.ConsumerConfig +import com.alipay.sofa.rpc.config.ProviderConfig +import com.alipay.sofa.rpc.config.ServerConfig +import datadog.trace.agent.test.InstrumentationSpecification +import datadog.trace.bootstrap.instrumentation.api.Tags +import spock.lang.Shared + +import javax.ws.rs.GET +import javax.ws.rs.Path +import javax.ws.rs.PathParam +import javax.ws.rs.Produces +import javax.ws.rs.core.MediaType + +import static datadog.trace.agent.test.utils.TraceUtils.basicSpan +import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace + +/** + * Tests SOFA RPC REST protocol instrumentation. + * + * REST works out-of-the-box via dd-trace-java's Apache HttpClient and HTTP server + * instrumentation. Our SOFA RPC instrumentation contributes the sofarpc.request[client] + * span (from AbstractClusterInstrumentation). There is no sofarpc.request[server] span + * because no transport-level instrumentation sets SofaRpcProtocolContext for REST — the + * server side is covered by JAX-RS / HTTP server instrumentation instead. + */ +class SofaRpcRestTest extends InstrumentationSpecification { + + @Shared + int port = 12205 + + @Shared + ProviderBootstrap restProviderBootstrap + + @Shared + GreeterService greeterService + + def setupSpec() { + ServerConfig serverConfig = + new ServerConfig() + .setProtocol("rest") + .setHost("127.0.0.1") + .setPort(port) + + ProviderConfig providerConfig = + new ProviderConfig() + .setApplication(new ApplicationConfig().setAppName("test-server")) + .setInterfaceId(GreeterService.name) + .setRef(new GreeterServiceImpl()) + .setServer(serverConfig) + .setRegister(false) + + restProviderBootstrap = providerConfig.export() + + greeterService = + new ConsumerConfig() + .setApplication(new ApplicationConfig().setAppName("test-client")) + .setInterfaceId(GreeterService.name) + .setDirectUrl("rest://127.0.0.1:${port}") + .setProtocol("rest") + .setRegister(false) + .setSubscribe(false) + .refer() + } + + def cleanupSpec() { + restProviderBootstrap?.unExport() + } + + def "client span created for REST call"() { + setup: + String serviceUniqueName = GreeterService.name + ":1.0" + + when: + String reply = runUnderTrace("caller") { greeterService.sayHello("World") } + + then: + reply == "Hello, World" + + and: + // REST is instrumented by Apache HttpClient (client) + HTTP server (server) integrations. + // Our SOFA RPC instrumentation adds sofarpc.request[client] on top. No server-side + // SofaRpcProtocolContext is set for REST, so ProviderProxyInvoker produces no sofarpc span. + // Only the client-side trace is collected here. + assertTraces(1) { + trace(2) { + basicSpan(it, "caller") + span { + operationName "sofarpc.request" + resourceName "${serviceUniqueName}/sayHello" + spanType "rpc" + errored false + childOf span(0) + tags { + "$Tags.RPC_SERVICE" serviceUniqueName + "rpc.system" "sofarpc" + "sofarpc.protocol" "rest" + "component" "sofarpc-client" + "span.kind" "client" + peerServiceFrom(Tags.RPC_SERVICE) + defaultTags() + } + } + } + } + } + + @Path("/greeter") + interface GreeterService { + @GET + @Path("/hello/{name}") + @Produces(MediaType.TEXT_PLAIN) + String sayHello(@PathParam("name") String name) + } + + static class GreeterServiceImpl implements GreeterService { + @Override + String sayHello(String name) { + return "Hello, ${name}" + } + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/build.gradle b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/build.gradle new file mode 100644 index 00000000000..3fc28ef198a --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/build.gradle @@ -0,0 +1,47 @@ +muzzle { + pass { + group = "com.alipay.sofa" + module = "sofa-rpc-all" + versions = "[5.14.2,)" + assertInverse = true + } +} + +apply from: "$rootDir/gradle/java.gradle" + +configurations.testRuntimeClasspath { + // Force the JRE variant of Guava so that java.time-based APIs (e.g. + // CacheBuilder.expireAfterWrite(Duration)) are available at test runtime. + // Without this, dependency resolution picks the android variant which lacks + // those overloads, causing NoSuchMethodError during SOFA RPC initialisation. + resolutionStrategy.force "com.google.guava:guava:32.1.3-jre" +} + +addTestSuiteForDir('latestDepTest', 'test') + +dependencies { + compileOnly project(':dd-java-agent:instrumentation:sofarpc:sofarpc-5.0') + compileOnly group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.14.2" + // grpc-api provides io.grpc.Metadata used by TripleGrpcMetadataExtractAdapter. + compileOnly group: "io.grpc", name: "grpc-api", version: "1.53.0" + + testImplementation project(':dd-java-agent:instrumentation:sofarpc:sofarpc-5.0') + // Required so that GrpcServerModule / GrpcClientModule are discovered via ServiceLoader + // in SofaRpcTripleWithGrpcForkedTest (the test framework loads InstrumenterModules from + // the test classpath, not from a shadow jar). + testImplementation project(':dd-java-agent:instrumentation:grpc-1.5') + testImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.14.2" + // sofa-rpc-all:5.14.2 was compiled against gRPC 1.53.0 and does not bundle its + // transitive dependencies — they must be declared explicitly at the matching version. + testImplementation group: "io.grpc", name: "grpc-netty", version: "1.53.0" + testImplementation group: "io.grpc", name: "grpc-core", version: "1.53.0" + testImplementation group: "io.grpc", name: "grpc-stub", version: "1.53.0" + testImplementation group: "com.google.protobuf", name: "protobuf-java", version: "3.25.3" + testImplementation group: "com.alibaba", name: "fastjson", version: "1.2.83" + // sofa-common-tools (transitive via sofa-rpc-all:5.14.2) uses Guava 28+ API + // (CacheBuilder.expireAfterWrite(Duration)); force the JRE variant — the android + // variant omits java.time-based overloads that are required at runtime. + testImplementation group: "com.google.guava", name: "guava", version: "32.1.3-jre" + + latestDepTestImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "+" +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcTripleModule.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcTripleModule.java new file mode 100644 index 00000000000..5bedb274e4e --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcTripleModule.java @@ -0,0 +1,27 @@ +package datadog.trace.instrumentation.sofarpc; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import java.util.Collections; +import java.util.List; + +@AutoService(InstrumenterModule.class) +public class SofaRpcTripleModule extends InstrumenterModule.Tracing { + + public SofaRpcTripleModule() { + super("sofarpc"); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".TripleGrpcMetadataExtractAdapter", + }; + } + + @Override + public List typeInstrumentations() { + return Collections.singletonList(new TripleServerInstrumentation()); + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleGrpcMetadataExtractAdapter.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleGrpcMetadataExtractAdapter.java new file mode 100644 index 00000000000..f22403623ba --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleGrpcMetadataExtractAdapter.java @@ -0,0 +1,32 @@ +package datadog.trace.instrumentation.sofarpc; + +import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; +import io.grpc.Metadata; + +/** + * Extracts Datadog propagation headers from gRPC {@link Metadata}. + * + *

Used by {@link TripleServerInstrumentation} to read the trace context that was injected by + * {@link AbstractClusterInstrumentation} into {@code SofaRequest.requestProps} on the client side + * and then serialised into gRPC Metadata by SOFA RPC's {@code TripleTracerAdapter.beforeSend()}. + * + *

Identical in structure to {@code GrpcExtractAdapter} in the grpc-1.5 instrumentation. + */ +public final class TripleGrpcMetadataExtractAdapter + implements AgentPropagation.ContextVisitor { + + public static final TripleGrpcMetadataExtractAdapter GETTER = + new TripleGrpcMetadataExtractAdapter(); + + @Override + public void forEachKey(Metadata carrier, AgentPropagation.KeyClassifier classifier) { + for (String key : carrier.keys()) { + if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { + if (!classifier.accept( + key, carrier.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)))) { + return; + } + } + } + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java new file mode 100644 index 00000000000..d125c817f66 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java @@ -0,0 +1,61 @@ +package datadog.trace.instrumentation.sofarpc; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; +import static datadog.trace.instrumentation.sofarpc.TripleGrpcMetadataExtractAdapter.GETTER; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.alipay.sofa.rpc.tracer.sofatracer.TracingContextKey; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import io.grpc.Metadata; +import net.bytebuddy.asm.Advice; + +public class TripleServerInstrumentation + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + @Override + public String instrumentedType() { + return "com.alipay.sofa.rpc.server.triple.UniqueIdInvoker"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod() + .and(named("invoke")) + .and(takesArguments(1)) + .and(takesArgument(0, named("com.alipay.sofa.rpc.core.request.SofaRequest"))), + getClass().getName() + "$InvokeAdvice"); + } + + public static class InvokeAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void enter() { + SofaRpcProtocolContext.set("tri"); + // Only extract trace context from gRPC Metadata when gRPC instrumentation is disabled. + // When gRPC instrumentation is active, TracingServerInterceptor has already activated a + // grpc.server span — ProviderProxyInvokerInstrumentation will call startSpan() without an + // explicit parent and naturally become a child of that grpc.server span. + // When gRPC instrumentation is disabled, no span is active here, so we must propagate the + // client trace context ourselves: read the raw Metadata stored by SOFA RPC's + // ServerReqHeaderInterceptor (Datadog headers are not reconstructed into requestProps on + // the server side, so we must read Metadata directly). + if (activeSpan() == null) { + Metadata metadata = TracingContextKey.getKeyMetadata().get(); + if (metadata != null) { + AgentSpanContext parentContext = extractContextAndGetSpanContext(metadata, GETTER); + SofaRpcProtocolContext.setParentContext(parentContext); + } + } + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void exit() { + SofaRpcProtocolContext.clear(); + } + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleNoGrpcForkedTest.groovy b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleNoGrpcForkedTest.groovy new file mode 100644 index 00000000000..8a5221810bf --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleNoGrpcForkedTest.groovy @@ -0,0 +1,173 @@ +package datadog.trace.instrumentation.sofarpc + +import com.alipay.sofa.rpc.bootstrap.ProviderBootstrap +import com.alipay.sofa.rpc.config.ApplicationConfig +import com.alipay.sofa.rpc.config.ConsumerConfig +import com.alipay.sofa.rpc.config.ProviderConfig +import com.alipay.sofa.rpc.config.ServerConfig +import datadog.trace.agent.test.InstrumentationSpecification +import datadog.trace.bootstrap.instrumentation.api.Tags +import spock.lang.Shared + +import static datadog.trace.agent.test.utils.TraceUtils.basicSpan +import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace + +/** + * Forked test: runs in an isolated JVM with gRPC instrumentation disabled + * (trace.grpc.enabled=false set before agent initialisation in configurePreAgent). + * + * PURPOSE: demonstrate the bug in ProviderProxyInvokerInstrumentation. + * + * Root cause: for Triple protocol the server-side span is started with + * startSpan(SOFA_RPC_SERVER) // no explicit parentContext + * relying on the gRPC TracingServerInterceptor having already activated a grpc.server + * span on the current thread. When gRPC instrumentation is absent, no such span + * is active and the sofarpc.request[server] span becomes a disconnected root — the + * distributed trace is broken. + * + * This test asserts the CORRECT, expected behaviour (server span connected to the + * client span). It currently FAILS, proving the bug exists. + * + * Fix: in ProviderProxyInvokerInstrumentation, for Triple, extract propagation context + * from the raw gRPC Metadata (accessible via TracingContextKey.getKeyMetadata() on the + * gRPC Context) instead of depending on an active span from the gRPC instrumentation. + */ +class SofaRpcTripleNoGrpcForkedTest extends InstrumentationSpecification { + + @Override + protected void configurePreAgent() { + super.configurePreAgent() + // Prevent GrpcServerBuilderInstrumentation from installing TracingServerInterceptor. + // With this flag false the interceptor is never registered, so no grpc.server span + // will be active on the thread when ProviderProxyInvoker.invoke() runs. + injectSysConfig("trace.grpc.enabled", "false") + } + + @Shared + int triplePort = 12203 + + @Shared + ProviderBootstrap tripleProviderBootstrap + + @Shared + GreeterService greeterService + + def setupSpec() { + ServerConfig serverConfig = + new ServerConfig() + .setProtocol("tri") + .setHost("127.0.0.1") + .setPort(triplePort) + + ProviderConfig providerConfig = + new ProviderConfig() + .setApplication(new ApplicationConfig().setAppName("test-server")) + .setInterfaceId(GreeterService.name) + .setRef(new GreeterServiceImpl()) + .setServer(serverConfig) + .setRegister(false) + + tripleProviderBootstrap = providerConfig.export() + + greeterService = + new ConsumerConfig() + .setApplication(new ApplicationConfig().setAppName("test-client")) + .setInterfaceId(GreeterService.name) + .setDirectUrl("tri://127.0.0.1:${triplePort}") + .setProtocol("tri") + .setRegister(false) + .setSubscribe(false) + .refer() + } + + def cleanupSpec() { + tripleProviderBootstrap?.unExport() + } + + /** + * EXPECTED behaviour (currently fails — see class-level Javadoc). + * + * With gRPC disabled the only instrumentation active is the sofarpc one. + * AbstractClusterInstrumentation injects the client span context into + * SofaRequest.requestProps; SOFA RPC Triple serialises requestProps into + * gRPC Metadata on the wire. On the server side, however, SOFA RPC only + * reconstructs the 8 framework-specific keys from gRPC Metadata back into + * SofaRequest.requestProps — Datadog trace headers are NOT among them. + * ProviderProxyInvokerInstrumentation therefore cannot extract the parent + * context and falls back to creating a root span. + * + * Once fixed, this test should pass with assertTraces(2): + * trace(0): caller -> sofarpc.request[client] + * trace(1): sofarpc.request[server], childOf trace(0).get(1) + */ + def "Triple: server span is linked to client trace when gRPC instrumentation is disabled"() { + setup: + // On the client side, SOFA RPC includes the default uniqueId (":1.0") in the service name. + // On the server side (Triple/gRPC), the reconstructed SofaRequest does not carry the version, + // so the service name is just the interface name. + String clientServiceName = GreeterService.name + ":1.0" + String serverServiceName = GreeterService.name + + when: + String reply = runUnderTrace("caller") { greeterService.sayHello("World") } + + then: + reply == "Hello, World" + + and: + // BUG: actual result is assertTraces(2) where trace(1) has NO parent (root span), + // so the childOf assertion below fails — the distributed trace is broken. + assertTraces(2) { + trace(2) { + basicSpan(it, "caller") + span { + operationName "sofarpc.request" + resourceName "${clientServiceName}/sayHello" + spanType "rpc" + errored false + childOf span(0) + tags { + "$Tags.RPC_SERVICE" clientServiceName + "rpc.system" "sofarpc" + "sofarpc.protocol" "tri" + "component" "sofarpc-client" + "span.kind" "client" + peerServiceFrom(Tags.RPC_SERVICE) + defaultTags() + } + } + } + trace(1) { + span { + operationName "sofarpc.request" + resourceName "${serverServiceName}/sayHello" + spanType "rpc" + errored false + // This childOf assertion currently fails: the span has no parent because + // ProviderProxyInvokerInstrumentation called startSpan(SOFA_RPC_SERVER) + // with no parentContext (gRPC span absent) and no requestProps extraction. + childOf trace(0).get(1) + tags { + "$Tags.RPC_SERVICE" serverServiceName + "rpc.system" "sofarpc" + "sofarpc.protocol" "tri" + "component" "sofarpc-server" + "span.kind" "server" + defaultTags(true) + } + } + } + } + } + + interface GreeterService { + String sayHello(String name) + } + + static class GreeterServiceImpl implements GreeterService { + @Override + String sayHello(String name) { + return "Hello, ${name}" + } + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleWithGrpcForkedTest.groovy b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleWithGrpcForkedTest.groovy new file mode 100644 index 00000000000..4007b65370f --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleWithGrpcForkedTest.groovy @@ -0,0 +1,113 @@ +package datadog.trace.instrumentation.sofarpc + +import com.alipay.sofa.rpc.bootstrap.ProviderBootstrap +import com.alipay.sofa.rpc.config.ApplicationConfig +import com.alipay.sofa.rpc.config.ConsumerConfig +import com.alipay.sofa.rpc.config.ProviderConfig +import com.alipay.sofa.rpc.config.ServerConfig +import datadog.trace.agent.test.InstrumentationSpecification +import spock.lang.Shared + +import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace + +/** + * Forked test: runs in an isolated JVM with gRPC instrumentation ENABLED (the default). + * + * When gRPC instrumentation is active, SOFA RPC Triple calls produce both gRPC spans + * (grpc.client / grpc.server) and sofarpc spans. All spans run in-process and share + * one trace. Expected hierarchy: + * + * caller + * └─ sofarpc.request [client] + * └─ grpc.client + * └─ grpc.server + * └─ sofarpc.request [server] ← asserted below + * + * The key assertion is that sofarpc.request[server] is a direct child of grpc.server, + * NOT of grpc.client. Before the fix, TripleServerInstrumentation would extract the + * parent context from gRPC Metadata regardless of whether a grpc.server span was active, + * which caused sofarpc.request[server] to become a sibling of grpc.server instead. + */ +class SofaRpcTripleWithGrpcForkedTest extends InstrumentationSpecification { + // No configurePreAgent() override — gRPC instrumentation is enabled by default. + + @Shared + int triplePort = 12204 + + @Shared + ProviderBootstrap tripleProviderBootstrap + + @Shared + GreeterService greeterService + + def setupSpec() { + ServerConfig serverConfig = + new ServerConfig() + .setProtocol("tri") + .setHost("127.0.0.1") + .setPort(triplePort) + + ProviderConfig providerConfig = + new ProviderConfig() + .setApplication(new ApplicationConfig().setAppName("test-server")) + .setInterfaceId(GreeterService.name) + .setRef(new GreeterServiceImpl()) + .setServer(serverConfig) + .setRegister(false) + + tripleProviderBootstrap = providerConfig.export() + + greeterService = + new ConsumerConfig() + .setApplication(new ApplicationConfig().setAppName("test-client")) + .setInterfaceId(GreeterService.name) + .setDirectUrl("tri://127.0.0.1:${triplePort}") + .setProtocol("tri") + .setRegister(false) + .setSubscribe(false) + .refer() + } + + def cleanupSpec() { + tripleProviderBootstrap?.unExport() + } + + def "Triple: server span is nested under grpc.server when gRPC instrumentation is enabled"() { + when: + String reply = runUnderTrace("caller") { greeterService.sayHello("World") } + + then: + reply == "Hello, World" + + and: + // Client spans (caller, sofarpc[client], grpc.client) are flushed when the client-side + // root span finishes. Server spans (grpc.server, sofarpc[server], grpc.message) are + // flushed when grpc.server — the server-side local root — finishes on its own thread. + // That gives two separate ListWriter entries even though both share the same trace_id. + TEST_WRITER.waitForTraces(2) + def allSpans = TEST_WRITER.flatten() + + def serverSofaSpan = allSpans.find { + it.operationName.toString() == "sofarpc.request" && it.getTag("span.kind") == "server" + } + def grpcServerSpan = allSpans.find { + it.operationName.toString() == "grpc.server" + } + + assert serverSofaSpan != null : "Expected sofarpc[server]. Spans found: ${allSpans.collect { it.operationName.toString() + '[' + it.getTag('span.kind') + ']' }}" + assert grpcServerSpan != null : "Expected grpc.server. Spans found: ${allSpans.collect { it.operationName.toString() + '[' + it.getTag('span.kind') + ']' }}" + // sofarpc.request[server] must be a direct child of grpc.server, not grpc.client + serverSofaSpan.parentId == grpcServerSpan.spanId + } + + interface GreeterService { + String sayHello(String name) + } + + static class GreeterServiceImpl implements GreeterService { + @Override + String sayHello(String name) { + return "Hello, ${name}" + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 0a1f22be364..dd5efea01e9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -562,6 +562,7 @@ include( ":dd-java-agent:instrumentation:snakeyaml-1.33", ":dd-java-agent:instrumentation:sofarpc", ":dd-java-agent:instrumentation:sofarpc:sofarpc-5.0", + ":dd-java-agent:instrumentation:sofarpc:sofarpc-5.14", ":dd-java-agent:instrumentation:spark:spark-common", ":dd-java-agent:instrumentation:spark:spark_2.12", ":dd-java-agent:instrumentation:spark:spark_2.13", From 90c9aef529099cb1ea24db24875a7488662cd7d4 Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Wed, 15 Apr 2026 23:47:25 +0200 Subject: [PATCH 5/8] Simplify sofarpc instrumentation: merge modules, add REST/Triple server spans, rpc.method tag --- .../sofarpc/sofarpc-5.0/build.gradle | 15 +- .../sofarpc/H2cServerTaskInstrumentation.java | 3 +- .../ProviderProxyInvokerInstrumentation.java | 16 +- .../RestServerHandlerInstrumentation.java | 36 ++++ .../sofarpc/SofaRpcClientDecorator.java | 1 + .../sofarpc/SofaRpcModule.java | 2 + .../sofarpc/SofaRpcProtocolContext.java | 20 +- .../sofarpc/SofaRpcServerDecorator.java | 1 + .../sofarpc/TripleServerInstrumentation.java | 40 ++++ .../sofarpc/SofaRpcRestTest.groovy | 71 ++++--- .../sofarpc/SofaRpcTest.groovy | 4 + .../SofaRpcTripleWithGrpcForkedTest.groovy | 30 +-- .../sofarpc/sofarpc-5.14/build.gradle | 47 ----- .../sofarpc/SofaRpcTripleModule.java | 27 --- .../TripleGrpcMetadataExtractAdapter.java | 32 ---- .../sofarpc/TripleServerInstrumentation.java | 61 ------ .../SofaRpcTripleNoGrpcForkedTest.groovy | 173 ------------------ settings.gradle.kts | 1 - 18 files changed, 165 insertions(+), 415 deletions(-) create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/RestServerHandlerInstrumentation.java create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java rename dd-java-agent/instrumentation/sofarpc/{sofarpc-5.14 => sofarpc-5.0}/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleWithGrpcForkedTest.groovy (85%) delete mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/build.gradle delete mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcTripleModule.java delete mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleGrpcMetadataExtractAdapter.java delete mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleNoGrpcForkedTest.groovy diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle index ee19a2b79da..bb063fa43be 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle @@ -11,12 +11,25 @@ apply from: "$rootDir/gradle/java.gradle" addTestSuiteForDir('latestDepTest', 'test') +configurations.testRuntimeClasspath { + resolutionStrategy.force "com.google.guava:guava:32.1.3-jre" +} + dependencies { compileOnly group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.6.0" - testImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.6.0" + testImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.14.2" // JAX-RS annotations required for the REST protocol test interface (@Path, @GET, etc.) testImplementation group: "javax.ws.rs", name: "javax.ws.rs-api", version: "2.1.1" + // Required so that GrpcServerModule / GrpcClientModule are discovered via ServiceLoader + // in SofaRpcTripleWithGrpcForkedTest. + testImplementation project(':dd-java-agent:instrumentation:grpc-1.5') + testImplementation group: "io.grpc", name: "grpc-netty", version: "1.53.0" + testImplementation group: "io.grpc", name: "grpc-core", version: "1.53.0" + testImplementation group: "io.grpc", name: "grpc-stub", version: "1.53.0" + testImplementation group: "com.google.protobuf", name: "protobuf-java", version: "3.25.3" + testImplementation group: "com.alibaba", name: "fastjson", version: "1.2.83" + testImplementation group: "com.google.guava", name: "guava", version: "32.1.3-jre" latestDepTestImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "+" } diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/H2cServerTaskInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/H2cServerTaskInstrumentation.java index caf84fb6219..8657125dac3 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/H2cServerTaskInstrumentation.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/H2cServerTaskInstrumentation.java @@ -18,8 +18,7 @@ public String instrumentedType() { @Override public void methodAdvice(MethodTransformer transformer) { transformer.applyAdvice( - isMethod().and(named("run")).and(takesNoArguments()), - getClass().getName() + "$RunAdvice"); + isMethod().and(named("run")).and(takesNoArguments()), getClass().getName() + "$RunAdvice"); } public static class RunAdvice { diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java index 1f606fbbeed..ca8baee6f8b 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java @@ -46,17 +46,11 @@ public static AgentScope enter(@Advice.Argument(0) SofaRequest request) { if (protocol == null) { return null; } - // Prefer the parent context stored by transport-specific instrumentation (e.g. - // TripleServerInstrumentation reads it from gRPC Metadata). For Bolt and H2C the - // transport instrumentations only set the protocol, so parentContext is null here and - // we fall back to extracting from SofaRequest.requestProps as before. - AgentSpanContext parentContext = SofaRpcProtocolContext.getParentContext(); - if (parentContext == null) { - parentContext = extractContextAndGetSpanContext(request, GETTER); - } - // parentContext may be null for Triple+gRPC-enabled: TripleServerInstrumentation skips - // Metadata extraction when a grpc.server span is already active. In that case - // startSpan() without an explicit parent naturally attaches to the active grpc.server span. + // For Bolt and H2C the client injects trace context into SofaRequest.requestProps; + // extract it here. For Triple, parentContext will be null and startSpan() without an + // explicit parent naturally attaches to the active grpc.server span. For REST, + // parentContext will also be null and the active netty.request span becomes the parent. + AgentSpanContext parentContext = extractContextAndGetSpanContext(request, GETTER); AgentSpan span = parentContext != null ? startSpan(SOFA_RPC_SERVER, parentContext) diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/RestServerHandlerInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/RestServerHandlerInstrumentation.java new file mode 100644 index 00000000000..7ac377311a2 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/RestServerHandlerInstrumentation.java @@ -0,0 +1,36 @@ +package datadog.trace.instrumentation.sofarpc; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import net.bytebuddy.asm.Advice; + +public class RestServerHandlerInstrumentation + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + @Override + public String instrumentedType() { + return "com.alipay.sofa.rpc.server.rest.SofaRestRequestHandler"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod().and(named("channelRead0")).and(takesArguments(2)), + getClass().getName() + "$ChannelRead0Advice"); + } + + public static class ChannelRead0Advice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void enter() { + SofaRpcProtocolContext.set("rest"); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void exit() { + SofaRpcProtocolContext.clear(); + } + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcClientDecorator.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcClientDecorator.java index c53ff49eafe..4e9cc7e29f9 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcClientDecorator.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcClientDecorator.java @@ -47,6 +47,7 @@ public AgentSpan onRequest(AgentSpan span, SofaRequest request) { String serviceName = request.getTargetServiceUniqueName(); String methodName = request.getMethodName(); span.setTag(Tags.RPC_SERVICE, serviceName); + span.setTag("rpc.method", methodName); // peer.service is derived automatically by PeerServiceCalculator from rpc.service. if (serviceName != null && methodName != null) { span.setResourceName(serviceName + "/" + methodName); diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java index a1620b627da..ca895c910ef 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java @@ -31,6 +31,8 @@ public List typeInstrumentations() { new AbstractClusterInstrumentation(), new BoltServerProcessorInstrumentation(), new H2cServerTaskInstrumentation(), + new RestServerHandlerInstrumentation(), + new TripleServerInstrumentation(), new ProviderProxyInvokerInstrumentation()); } } diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcProtocolContext.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcProtocolContext.java index 08ba7bfbc6c..09bbafae508 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcProtocolContext.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcProtocolContext.java @@ -1,18 +1,9 @@ package datadog.trace.instrumentation.sofarpc; -import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; - -/** - * Thread-local carrier for the SOFA RPC transport protocol name and the propagated parent span - * context. The parent context is extracted by transport-specific instrumentation (e.g. - * TripleServerInstrumentation reads it from gRPC Metadata) so that ProviderProxyInvokerInstrumentation - * can start the server span with an explicit parent regardless of whether other instrumentation - * (e.g. gRPC) is enabled. - */ +/** Thread-local carrier for the SOFA RPC transport protocol name. */ public final class SofaRpcProtocolContext { private static final ThreadLocal PROTOCOL = new ThreadLocal<>(); - private static final ThreadLocal PARENT_CONTEXT = new ThreadLocal<>(); private SofaRpcProtocolContext() {} @@ -20,20 +11,11 @@ public static void set(String protocol) { PROTOCOL.set(protocol); } - public static void setParentContext(AgentSpanContext parentContext) { - PARENT_CONTEXT.set(parentContext); - } - public static String get() { return PROTOCOL.get(); } - public static AgentSpanContext getParentContext() { - return PARENT_CONTEXT.get(); - } - public static void clear() { PROTOCOL.remove(); - PARENT_CONTEXT.remove(); } } diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcServerDecorator.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcServerDecorator.java index c5dc40af715..7fbbe329934 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcServerDecorator.java +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcServerDecorator.java @@ -48,6 +48,7 @@ public AgentSpan onRequest(AgentSpan span, SofaRequest request) { String serviceName = request.getTargetServiceUniqueName(); String methodName = request.getMethodName(); span.setTag(Tags.RPC_SERVICE, serviceName); + span.setTag("rpc.method", methodName); if (serviceName != null && methodName != null) { span.setResourceName(serviceName + "/" + methodName); } else if (methodName != null) { diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java new file mode 100644 index 00000000000..38abc4fb22b --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java @@ -0,0 +1,40 @@ +package datadog.trace.instrumentation.sofarpc; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import net.bytebuddy.asm.Advice; + +public class TripleServerInstrumentation + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + @Override + public String instrumentedType() { + return "com.alipay.sofa.rpc.server.triple.UniqueIdInvoker"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod() + .and(named("invoke")) + .and(takesArguments(1)) + .and(takesArgument(0, named("com.alipay.sofa.rpc.core.request.SofaRequest"))), + getClass().getName() + "$InvokeAdvice"); + } + + public static class InvokeAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void enter() { + SofaRpcProtocolContext.set("tri"); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void exit() { + SofaRpcProtocolContext.clear(); + } + } +} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcRestTest.groovy b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcRestTest.groovy index 2673e896f88..fb2a919ffa1 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcRestTest.groovy +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcRestTest.groovy @@ -21,11 +21,11 @@ import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace /** * Tests SOFA RPC REST protocol instrumentation. * - * REST works out-of-the-box via dd-trace-java's Apache HttpClient and HTTP server - * instrumentation. Our SOFA RPC instrumentation contributes the sofarpc.request[client] - * span (from AbstractClusterInstrumentation). There is no sofarpc.request[server] span - * because no transport-level instrumentation sets SofaRpcProtocolContext for REST — the - * server side is covered by JAX-RS / HTTP server instrumentation instead. + * Our instrumentation contributes sofarpc.request[client] (AbstractClusterInstrumentation) + * and sofarpc.request[server] (RestServerHandlerInstrumentation + ProviderProxyInvokerInstrumentation). + * Distributed trace propagation is delegated to dd-trace-java's HTTP instrumentation + * (Apache HttpClient on the client side, Netty on the server side), which is not active in + * this unit test — so the server span appears as a separate trace root here. */ class SofaRpcRestTest extends InstrumentationSpecification { @@ -41,36 +41,36 @@ class SofaRpcRestTest extends InstrumentationSpecification { def setupSpec() { ServerConfig serverConfig = new ServerConfig() - .setProtocol("rest") - .setHost("127.0.0.1") - .setPort(port) + .setProtocol("rest") + .setHost("127.0.0.1") + .setPort(port) ProviderConfig providerConfig = new ProviderConfig() - .setApplication(new ApplicationConfig().setAppName("test-server")) - .setInterfaceId(GreeterService.name) - .setRef(new GreeterServiceImpl()) - .setServer(serverConfig) - .setRegister(false) + .setApplication(new ApplicationConfig().setAppName("test-server")) + .setInterfaceId(GreeterService.name) + .setRef(new GreeterServiceImpl()) + .setServer(serverConfig) + .setRegister(false) restProviderBootstrap = providerConfig.export() greeterService = new ConsumerConfig() - .setApplication(new ApplicationConfig().setAppName("test-client")) - .setInterfaceId(GreeterService.name) - .setDirectUrl("rest://127.0.0.1:${port}") - .setProtocol("rest") - .setRegister(false) - .setSubscribe(false) - .refer() + .setApplication(new ApplicationConfig().setAppName("test-client")) + .setInterfaceId(GreeterService.name) + .setDirectUrl("rest://127.0.0.1:${port}") + .setProtocol("rest") + .setRegister(false) + .setSubscribe(false) + .refer() } def cleanupSpec() { restProviderBootstrap?.unExport() } - def "client span created for REST call"() { + def "client and server spans created for REST call"() { setup: String serviceUniqueName = GreeterService.name + ":1.0" @@ -81,11 +81,8 @@ class SofaRpcRestTest extends InstrumentationSpecification { reply == "Hello, World" and: - // REST is instrumented by Apache HttpClient (client) + HTTP server (server) integrations. - // Our SOFA RPC instrumentation adds sofarpc.request[client] on top. No server-side - // SofaRpcProtocolContext is set for REST, so ProviderProxyInvoker produces no sofarpc span. - // Only the client-side trace is collected here. - assertTraces(1) { + assertTraces(2) { + // trace(0): client side — caller + sofarpc.request[client] trace(2) { basicSpan(it, "caller") span { @@ -96,6 +93,7 @@ class SofaRpcRestTest extends InstrumentationSpecification { childOf span(0) tags { "$Tags.RPC_SERVICE" serviceUniqueName + "rpc.method" "sayHello" "rpc.system" "sofarpc" "sofarpc.protocol" "rest" "component" "sofarpc-client" @@ -105,6 +103,27 @@ class SofaRpcRestTest extends InstrumentationSpecification { } } } + // trace(1): server side — sofarpc.request[server]. + // SofaRequest.getTargetServiceUniqueName() is null on the server side for REST + // (not propagated through the JAX-RS layer), so resourceName is the method name only + // and rpc.service tag is absent. Parent link to the client trace is provided by + // HTTP instrumentation (not active in this test), so this span is a trace root here. + trace(1) { + span { + operationName "sofarpc.request" + resourceName "sayHello" + spanType "rpc" + errored false + tags { + "rpc.method" "sayHello" + "rpc.system" "sofarpc" + "sofarpc.protocol" "rest" + "component" "sofarpc-server" + "span.kind" "server" + defaultTags(true) + } + } + } } } diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy index c572b499bc9..60a02cd0233 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy @@ -120,6 +120,7 @@ class SofaRpcTest extends InstrumentationSpecification { childOf span(0) tags { "$Tags.RPC_SERVICE" serviceUniqueName + "rpc.method" "sayHello" "rpc.system" "sofarpc" "sofarpc.protocol" "bolt" "component" "sofarpc-client" @@ -139,6 +140,7 @@ class SofaRpcTest extends InstrumentationSpecification { childOf trace(0).get(1) tags { "$Tags.RPC_SERVICE" serviceUniqueName + "rpc.method" "sayHello" "rpc.system" "sofarpc" "sofarpc.protocol" "bolt" "component" "sofarpc-server" @@ -176,6 +178,7 @@ class SofaRpcTest extends InstrumentationSpecification { errored false tags { "$Tags.RPC_SERVICE" serviceUniqueName + "rpc.method" "fail" "rpc.system" "sofarpc" "sofarpc.protocol" "bolt" "component" "sofarpc-client" @@ -194,6 +197,7 @@ class SofaRpcTest extends InstrumentationSpecification { childOf trace(0).get(0) tags { "$Tags.RPC_SERVICE" serviceUniqueName + "rpc.method" "fail" "rpc.system" "sofarpc" "sofarpc.protocol" "bolt" "component" "sofarpc-server" diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleWithGrpcForkedTest.groovy b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleWithGrpcForkedTest.groovy similarity index 85% rename from dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleWithGrpcForkedTest.groovy rename to dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleWithGrpcForkedTest.groovy index 4007b65370f..ceea44eb9a9 100644 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleWithGrpcForkedTest.groovy +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleWithGrpcForkedTest.groovy @@ -43,29 +43,29 @@ class SofaRpcTripleWithGrpcForkedTest extends InstrumentationSpecification { def setupSpec() { ServerConfig serverConfig = new ServerConfig() - .setProtocol("tri") - .setHost("127.0.0.1") - .setPort(triplePort) + .setProtocol("tri") + .setHost("127.0.0.1") + .setPort(triplePort) ProviderConfig providerConfig = new ProviderConfig() - .setApplication(new ApplicationConfig().setAppName("test-server")) - .setInterfaceId(GreeterService.name) - .setRef(new GreeterServiceImpl()) - .setServer(serverConfig) - .setRegister(false) + .setApplication(new ApplicationConfig().setAppName("test-server")) + .setInterfaceId(GreeterService.name) + .setRef(new GreeterServiceImpl()) + .setServer(serverConfig) + .setRegister(false) tripleProviderBootstrap = providerConfig.export() greeterService = new ConsumerConfig() - .setApplication(new ApplicationConfig().setAppName("test-client")) - .setInterfaceId(GreeterService.name) - .setDirectUrl("tri://127.0.0.1:${triplePort}") - .setProtocol("tri") - .setRegister(false) - .setSubscribe(false) - .refer() + .setApplication(new ApplicationConfig().setAppName("test-client")) + .setInterfaceId(GreeterService.name) + .setDirectUrl("tri://127.0.0.1:${triplePort}") + .setProtocol("tri") + .setRegister(false) + .setSubscribe(false) + .refer() } def cleanupSpec() { diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/build.gradle b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/build.gradle deleted file mode 100644 index 3fc28ef198a..00000000000 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/build.gradle +++ /dev/null @@ -1,47 +0,0 @@ -muzzle { - pass { - group = "com.alipay.sofa" - module = "sofa-rpc-all" - versions = "[5.14.2,)" - assertInverse = true - } -} - -apply from: "$rootDir/gradle/java.gradle" - -configurations.testRuntimeClasspath { - // Force the JRE variant of Guava so that java.time-based APIs (e.g. - // CacheBuilder.expireAfterWrite(Duration)) are available at test runtime. - // Without this, dependency resolution picks the android variant which lacks - // those overloads, causing NoSuchMethodError during SOFA RPC initialisation. - resolutionStrategy.force "com.google.guava:guava:32.1.3-jre" -} - -addTestSuiteForDir('latestDepTest', 'test') - -dependencies { - compileOnly project(':dd-java-agent:instrumentation:sofarpc:sofarpc-5.0') - compileOnly group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.14.2" - // grpc-api provides io.grpc.Metadata used by TripleGrpcMetadataExtractAdapter. - compileOnly group: "io.grpc", name: "grpc-api", version: "1.53.0" - - testImplementation project(':dd-java-agent:instrumentation:sofarpc:sofarpc-5.0') - // Required so that GrpcServerModule / GrpcClientModule are discovered via ServiceLoader - // in SofaRpcTripleWithGrpcForkedTest (the test framework loads InstrumenterModules from - // the test classpath, not from a shadow jar). - testImplementation project(':dd-java-agent:instrumentation:grpc-1.5') - testImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.14.2" - // sofa-rpc-all:5.14.2 was compiled against gRPC 1.53.0 and does not bundle its - // transitive dependencies — they must be declared explicitly at the matching version. - testImplementation group: "io.grpc", name: "grpc-netty", version: "1.53.0" - testImplementation group: "io.grpc", name: "grpc-core", version: "1.53.0" - testImplementation group: "io.grpc", name: "grpc-stub", version: "1.53.0" - testImplementation group: "com.google.protobuf", name: "protobuf-java", version: "3.25.3" - testImplementation group: "com.alibaba", name: "fastjson", version: "1.2.83" - // sofa-common-tools (transitive via sofa-rpc-all:5.14.2) uses Guava 28+ API - // (CacheBuilder.expireAfterWrite(Duration)); force the JRE variant — the android - // variant omits java.time-based overloads that are required at runtime. - testImplementation group: "com.google.guava", name: "guava", version: "32.1.3-jre" - - latestDepTestImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "+" -} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcTripleModule.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcTripleModule.java deleted file mode 100644 index 5bedb274e4e..00000000000 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcTripleModule.java +++ /dev/null @@ -1,27 +0,0 @@ -package datadog.trace.instrumentation.sofarpc; - -import com.google.auto.service.AutoService; -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.agent.tooling.InstrumenterModule; -import java.util.Collections; -import java.util.List; - -@AutoService(InstrumenterModule.class) -public class SofaRpcTripleModule extends InstrumenterModule.Tracing { - - public SofaRpcTripleModule() { - super("sofarpc"); - } - - @Override - public String[] helperClassNames() { - return new String[] { - packageName + ".TripleGrpcMetadataExtractAdapter", - }; - } - - @Override - public List typeInstrumentations() { - return Collections.singletonList(new TripleServerInstrumentation()); - } -} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleGrpcMetadataExtractAdapter.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleGrpcMetadataExtractAdapter.java deleted file mode 100644 index f22403623ba..00000000000 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleGrpcMetadataExtractAdapter.java +++ /dev/null @@ -1,32 +0,0 @@ -package datadog.trace.instrumentation.sofarpc; - -import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; -import io.grpc.Metadata; - -/** - * Extracts Datadog propagation headers from gRPC {@link Metadata}. - * - *

Used by {@link TripleServerInstrumentation} to read the trace context that was injected by - * {@link AbstractClusterInstrumentation} into {@code SofaRequest.requestProps} on the client side - * and then serialised into gRPC Metadata by SOFA RPC's {@code TripleTracerAdapter.beforeSend()}. - * - *

Identical in structure to {@code GrpcExtractAdapter} in the grpc-1.5 instrumentation. - */ -public final class TripleGrpcMetadataExtractAdapter - implements AgentPropagation.ContextVisitor { - - public static final TripleGrpcMetadataExtractAdapter GETTER = - new TripleGrpcMetadataExtractAdapter(); - - @Override - public void forEachKey(Metadata carrier, AgentPropagation.KeyClassifier classifier) { - for (String key : carrier.keys()) { - if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { - if (!classifier.accept( - key, carrier.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)))) { - return; - } - } - } - } -} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java deleted file mode 100644 index d125c817f66..00000000000 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java +++ /dev/null @@ -1,61 +0,0 @@ -package datadog.trace.instrumentation.sofarpc; - -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; -import static datadog.trace.instrumentation.sofarpc.TripleGrpcMetadataExtractAdapter.GETTER; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import com.alipay.sofa.rpc.tracer.sofatracer.TracingContextKey; -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; -import io.grpc.Metadata; -import net.bytebuddy.asm.Advice; - -public class TripleServerInstrumentation - implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { - - @Override - public String instrumentedType() { - return "com.alipay.sofa.rpc.server.triple.UniqueIdInvoker"; - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - isMethod() - .and(named("invoke")) - .and(takesArguments(1)) - .and(takesArgument(0, named("com.alipay.sofa.rpc.core.request.SofaRequest"))), - getClass().getName() + "$InvokeAdvice"); - } - - public static class InvokeAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void enter() { - SofaRpcProtocolContext.set("tri"); - // Only extract trace context from gRPC Metadata when gRPC instrumentation is disabled. - // When gRPC instrumentation is active, TracingServerInterceptor has already activated a - // grpc.server span — ProviderProxyInvokerInstrumentation will call startSpan() without an - // explicit parent and naturally become a child of that grpc.server span. - // When gRPC instrumentation is disabled, no span is active here, so we must propagate the - // client trace context ourselves: read the raw Metadata stored by SOFA RPC's - // ServerReqHeaderInterceptor (Datadog headers are not reconstructed into requestProps on - // the server side, so we must read Metadata directly). - if (activeSpan() == null) { - Metadata metadata = TracingContextKey.getKeyMetadata().get(); - if (metadata != null) { - AgentSpanContext parentContext = extractContextAndGetSpanContext(metadata, GETTER); - SofaRpcProtocolContext.setParentContext(parentContext); - } - } - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void exit() { - SofaRpcProtocolContext.clear(); - } - } -} diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleNoGrpcForkedTest.groovy b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleNoGrpcForkedTest.groovy deleted file mode 100644 index 8a5221810bf..00000000000 --- a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.14/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTripleNoGrpcForkedTest.groovy +++ /dev/null @@ -1,173 +0,0 @@ -package datadog.trace.instrumentation.sofarpc - -import com.alipay.sofa.rpc.bootstrap.ProviderBootstrap -import com.alipay.sofa.rpc.config.ApplicationConfig -import com.alipay.sofa.rpc.config.ConsumerConfig -import com.alipay.sofa.rpc.config.ProviderConfig -import com.alipay.sofa.rpc.config.ServerConfig -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.bootstrap.instrumentation.api.Tags -import spock.lang.Shared - -import static datadog.trace.agent.test.utils.TraceUtils.basicSpan -import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace - -/** - * Forked test: runs in an isolated JVM with gRPC instrumentation disabled - * (trace.grpc.enabled=false set before agent initialisation in configurePreAgent). - * - * PURPOSE: demonstrate the bug in ProviderProxyInvokerInstrumentation. - * - * Root cause: for Triple protocol the server-side span is started with - * startSpan(SOFA_RPC_SERVER) // no explicit parentContext - * relying on the gRPC TracingServerInterceptor having already activated a grpc.server - * span on the current thread. When gRPC instrumentation is absent, no such span - * is active and the sofarpc.request[server] span becomes a disconnected root — the - * distributed trace is broken. - * - * This test asserts the CORRECT, expected behaviour (server span connected to the - * client span). It currently FAILS, proving the bug exists. - * - * Fix: in ProviderProxyInvokerInstrumentation, for Triple, extract propagation context - * from the raw gRPC Metadata (accessible via TracingContextKey.getKeyMetadata() on the - * gRPC Context) instead of depending on an active span from the gRPC instrumentation. - */ -class SofaRpcTripleNoGrpcForkedTest extends InstrumentationSpecification { - - @Override - protected void configurePreAgent() { - super.configurePreAgent() - // Prevent GrpcServerBuilderInstrumentation from installing TracingServerInterceptor. - // With this flag false the interceptor is never registered, so no grpc.server span - // will be active on the thread when ProviderProxyInvoker.invoke() runs. - injectSysConfig("trace.grpc.enabled", "false") - } - - @Shared - int triplePort = 12203 - - @Shared - ProviderBootstrap tripleProviderBootstrap - - @Shared - GreeterService greeterService - - def setupSpec() { - ServerConfig serverConfig = - new ServerConfig() - .setProtocol("tri") - .setHost("127.0.0.1") - .setPort(triplePort) - - ProviderConfig providerConfig = - new ProviderConfig() - .setApplication(new ApplicationConfig().setAppName("test-server")) - .setInterfaceId(GreeterService.name) - .setRef(new GreeterServiceImpl()) - .setServer(serverConfig) - .setRegister(false) - - tripleProviderBootstrap = providerConfig.export() - - greeterService = - new ConsumerConfig() - .setApplication(new ApplicationConfig().setAppName("test-client")) - .setInterfaceId(GreeterService.name) - .setDirectUrl("tri://127.0.0.1:${triplePort}") - .setProtocol("tri") - .setRegister(false) - .setSubscribe(false) - .refer() - } - - def cleanupSpec() { - tripleProviderBootstrap?.unExport() - } - - /** - * EXPECTED behaviour (currently fails — see class-level Javadoc). - * - * With gRPC disabled the only instrumentation active is the sofarpc one. - * AbstractClusterInstrumentation injects the client span context into - * SofaRequest.requestProps; SOFA RPC Triple serialises requestProps into - * gRPC Metadata on the wire. On the server side, however, SOFA RPC only - * reconstructs the 8 framework-specific keys from gRPC Metadata back into - * SofaRequest.requestProps — Datadog trace headers are NOT among them. - * ProviderProxyInvokerInstrumentation therefore cannot extract the parent - * context and falls back to creating a root span. - * - * Once fixed, this test should pass with assertTraces(2): - * trace(0): caller -> sofarpc.request[client] - * trace(1): sofarpc.request[server], childOf trace(0).get(1) - */ - def "Triple: server span is linked to client trace when gRPC instrumentation is disabled"() { - setup: - // On the client side, SOFA RPC includes the default uniqueId (":1.0") in the service name. - // On the server side (Triple/gRPC), the reconstructed SofaRequest does not carry the version, - // so the service name is just the interface name. - String clientServiceName = GreeterService.name + ":1.0" - String serverServiceName = GreeterService.name - - when: - String reply = runUnderTrace("caller") { greeterService.sayHello("World") } - - then: - reply == "Hello, World" - - and: - // BUG: actual result is assertTraces(2) where trace(1) has NO parent (root span), - // so the childOf assertion below fails — the distributed trace is broken. - assertTraces(2) { - trace(2) { - basicSpan(it, "caller") - span { - operationName "sofarpc.request" - resourceName "${clientServiceName}/sayHello" - spanType "rpc" - errored false - childOf span(0) - tags { - "$Tags.RPC_SERVICE" clientServiceName - "rpc.system" "sofarpc" - "sofarpc.protocol" "tri" - "component" "sofarpc-client" - "span.kind" "client" - peerServiceFrom(Tags.RPC_SERVICE) - defaultTags() - } - } - } - trace(1) { - span { - operationName "sofarpc.request" - resourceName "${serverServiceName}/sayHello" - spanType "rpc" - errored false - // This childOf assertion currently fails: the span has no parent because - // ProviderProxyInvokerInstrumentation called startSpan(SOFA_RPC_SERVER) - // with no parentContext (gRPC span absent) and no requestProps extraction. - childOf trace(0).get(1) - tags { - "$Tags.RPC_SERVICE" serverServiceName - "rpc.system" "sofarpc" - "sofarpc.protocol" "tri" - "component" "sofarpc-server" - "span.kind" "server" - defaultTags(true) - } - } - } - } - } - - interface GreeterService { - String sayHello(String name) - } - - static class GreeterServiceImpl implements GreeterService { - @Override - String sayHello(String name) { - return "Hello, ${name}" - } - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index dd5efea01e9..0a1f22be364 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -562,7 +562,6 @@ include( ":dd-java-agent:instrumentation:snakeyaml-1.33", ":dd-java-agent:instrumentation:sofarpc", ":dd-java-agent:instrumentation:sofarpc:sofarpc-5.0", - ":dd-java-agent:instrumentation:sofarpc:sofarpc-5.14", ":dd-java-agent:instrumentation:spark:spark-common", ":dd-java-agent:instrumentation:spark:spark_2.12", ":dd-java-agent:instrumentation:spark:spark_2.13", From ea98882adaf5c3f96df0149dc083b0d2e240439f Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Thu, 16 Apr 2026 15:52:01 +0200 Subject: [PATCH 6/8] Fix sofarpc-5.0 CI build: add gradle.lockfile --- .../sofarpc/sofarpc-5.0/gradle.lockfile | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/gradle.lockfile diff --git a/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/gradle.lockfile b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/gradle.lockfile new file mode 100644 index 00000000000..9a4fcc80e00 --- /dev/null +++ b/dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/gradle.lockfile @@ -0,0 +1,243 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +cafe.cryptography:curve25519-elisabeth:0.1.0=latestDepTestRuntimeClasspath,testRuntimeClasspath +cafe.cryptography:ed25519-elisabeth:0.1.0=latestDepTestRuntimeClasspath,testRuntimeClasspath +ch.qos.logback:logback-classic:1.2.13=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +ch.qos.logback:logback-core:1.2.13=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.alibaba:fastjson:1.2.83=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.alibaba:transmittable-thread-local:2.12.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.alipay.sofa.common:sofa-common-tools:1.0.18=compileClasspath +com.alipay.sofa.common:sofa-common-tools:1.4.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.alipay.sofa.lookout:lookout-api:1.4.1=compileClasspath +com.alipay.sofa:bolt:1.5.2=compileClasspath +com.alipay.sofa:bolt:1.6.10=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.alipay.sofa:hessian:3.3.6=compileClasspath +com.alipay.sofa:hessian:3.5.5=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.alipay.sofa:sofa-rpc-all:5.14.2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.alipay.sofa:sofa-rpc-all:5.6.0=compileClasspath +com.alipay.sofa:tracer-core:2.1.2=compileClasspath +com.alipay.sofa:tracer-core:3.1.10=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.blogspot.mydailyjava:weak-lock-free:0.17=buildTimeInstrumentationPlugin,compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq.okhttp3:okhttp:3.12.15=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq.okio:okio:1.17.6=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-instrument-java:0.0.3=buildTimeInstrumentationPlugin,compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:java-dogstatsd-client:4.4.3=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.datadoghq:sketches-java:0.8.3=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.14.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.9.8=compileClasspath +com.fasterxml.jackson.core:jackson-core:2.14.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-core:2.9.8=compileClasspath +com.fasterxml.jackson.core:jackson-databind:2.14.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-databind:2.9.8=compileClasspath +com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:2.14.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:2.9.8=compileClasspath +com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.14.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.9.8=compileClasspath +com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.14.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.9.8=compileClasspath +com.fasterxml.jackson:jackson-bom:2.14.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.fge:btf:1.2=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.fge:jackson-coreutils:1.6=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.fge:json-patch:1.9=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.fge:msg-simple:1.1=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.javaparser:javaparser-core:3.25.6=codenarc +com.github.jnr:jffi:1.3.14=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-a64asm:1.0.0=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-constants:0.10.4=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-enxio:0.32.19=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-ffi:2.2.18=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-posix:3.1.21=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-unixsocket:0.38.24=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-x86asm:1.0.2=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.spotbugs:spotbugs-annotations:4.9.8=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath +com.github.spotbugs:spotbugs:4.9.8=spotbugs +com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs +com.google.android:annotations:4.1.1.4=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.google.api.grpc:proto-google-common-protos:2.9.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.auth:google-auth-library-credentials:1.4.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.auth:google-auth-library-oauth2-http:1.4.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,testAnnotationProcessor,testCompileClasspath +com.google.auto.service:auto-service:1.1.1=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.auto.value:auto-value-annotations:1.9=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.auto:auto-common:1.2.1=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,compileClasspath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,spotbugs,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath +com.google.code.gson:gson:2.13.2=spotbugs +com.google.code.gson:gson:2.9.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.18.0=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.21.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.41.0=spotbugs +com.google.guava:failureaccess:1.0.1=annotationProcessor,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath +com.google.guava:guava:16.0.1=compileClasspath +com.google.guava:guava:32.0.1-jre=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.guava:guava:32.1.3-jre=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath +com.google.http-client:google-http-client-gson:1.41.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.http-client:google-http-client:1.41.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.j2objc:j2objc-annotations:1.3=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.google.j2objc:j2objc-annotations:2.8=annotationProcessor,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,testAnnotationProcessor,testCompileClasspath +com.google.protobuf:protobuf-java-util:3.21.7=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.protobuf:protobuf-java:3.25.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.re2j:re2j:1.6=latestDepTestCompileClasspath,testCompileClasspath +com.google.re2j:re2j:1.7=latestDepTestRuntimeClasspath,testRuntimeClasspath +com.squareup.moshi:moshi:1.11.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okhttp3:logging-interceptor:3.12.12=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okhttp3:okhttp:3.12.12=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okio:okio:1.17.5=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.thoughtworks.qdox:qdox:1.12.1=codenarc +commons-codec:commons-codec:1.10=compileClasspath +commons-codec:commons-codec:1.11=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-fileupload:commons-fileupload:1.5=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.15.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.20.0=spotbugs +commons-io:commons-io:2.5=compileClasspath +commons-logging:commons-logging:1.2=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +de.thetaphi:forbiddenapis:3.10=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-all:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-alts:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-api:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-auth:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-context:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-core:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-grpclb:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-netty-shaded:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-netty:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-okhttp:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-protobuf-lite:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-protobuf:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-rls:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-services:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-servlet-jakarta:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-servlet:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-stub:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-testing:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.grpc:grpc-xds:1.53.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.leangen.geantyref:geantyref:1.3.16=latestDepTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-all:4.1.32.Final=compileClasspath +io.netty:netty-all:4.1.44.Final=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-buffer:4.1.79.Final=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec-http2:4.1.79.Final=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec-http:4.1.79.Final=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec-socks:4.1.79.Final=latestDepTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec:4.1.79.Final=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-common:4.1.79.Final=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-handler-proxy:4.1.79.Final=latestDepTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-handler:4.1.79.Final=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-resolver:4.1.79.Final=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-native-unix-common:4.1.79.Final=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport:4.1.79.Final=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opencensus:opencensus-api:0.28.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opencensus:opencensus-contrib-http-util:0.28.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opencensus:opencensus-proto:0.2.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentracing:opentracing-api:0.22.0=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentracing:opentracing-mock:0.22.0=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentracing:opentracing-noop:0.22.0=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentracing:opentracing-util:0.22.0=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.perfmark:perfmark-api:0.25.0=latestDepTestRuntimeClasspath,testRuntimeClasspath +io.sqreen:libsqreen:17.3.0=latestDepTestRuntimeClasspath,testRuntimeClasspath +io.swagger:swagger-annotations:1.6.9=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.swagger:swagger-core:1.6.9=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.swagger:swagger-models:1.6.9=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.activation:jakarta.activation-api:1.2.2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +javax.activation:activation:1.1.1=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +javax.servlet:javax.servlet-api:3.1.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +javax.validation:validation-api:2.0.1.Final=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +javax.ws.rs:javax.ws.rs-api:2.1.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +jaxen:jaxen:2.0.0=spotbugs +junit:junit:4.13.2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.18.3=buildTimeInstrumentationPlugin,compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.18.3=buildTimeInstrumentationPlugin,compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.java.dev.jna:jna-platform:5.8.0=latestDepTestRuntimeClasspath,testRuntimeClasspath +net.java.dev.jna:jna:5.8.0=latestDepTestRuntimeClasspath,testRuntimeClasspath +net.jcip:jcip-annotations:1.0=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.sf.saxon:Saxon-HE:12.9=spotbugs +org.apache.ant:ant-antlr:1.10.14=codenarc +org.apache.ant:ant-junit:1.10.14=codenarc +org.apache.bcel:bcel:6.11.0=spotbugs +org.apache.commons:commons-compress:1.26.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.commons:commons-lang3:3.14.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.commons:commons-lang3:3.19.0=spotbugs +org.apache.commons:commons-text:1.14.0=spotbugs +org.apache.httpcomponents:httpclient:4.5.13=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents:httpclient:4.5.4=compileClasspath +org.apache.httpcomponents:httpcore:4.4.15=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents:httpcore:4.4.7=compileClasspath +org.apache.httpcomponents:httpmime:4.5.13=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.logging.log4j:log4j-api:2.25.2=spotbugs +org.apache.logging.log4j:log4j-core:2.25.2=spotbugs +org.apiguardian:apiguardian-api:1.1.2=latestDepTestCompileClasspath,testCompileClasspath +org.checkerframework:checker-qual:3.33.0=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +org.checkerframework:checker-qual:3.37.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.codehaus.groovy:groovy-ant:3.0.23=codenarc +org.codehaus.groovy:groovy-docgenerator:3.0.23=codenarc +org.codehaus.groovy:groovy-groovydoc:3.0.23=codenarc +org.codehaus.groovy:groovy-json:3.0.23=codenarc +org.codehaus.groovy:groovy-json:3.0.25=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.codehaus.groovy:groovy-templates:3.0.23=codenarc +org.codehaus.groovy:groovy-xml:3.0.23=codenarc +org.codehaus.groovy:groovy:3.0.23=codenarc +org.codehaus.groovy:groovy:3.0.25=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.21=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.codenarc:CodeNarc:3.7.0=codenarc +org.conscrypt:conscrypt-openjdk-uber:2.5.2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.dom4j:dom4j:2.2.0=spotbugs +org.furyio:fury-core:0.4.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.gmetrics:GMetrics:2.1.0=codenarc +org.hamcrest:hamcrest-core:1.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hamcrest:hamcrest:3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.javassist:javassist:3.20.0-GA=compileClasspath +org.javassist:javassist:3.29.2-GA=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jboss.logging:jboss-logging:3.3.2.Final=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jboss.resteasy:resteasy-client:3.6.3.Final=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jboss.resteasy:resteasy-jackson2-provider:3.6.3.Final=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jboss.resteasy:resteasy-jaxrs:3.6.3.Final=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jboss.resteasy:resteasy-netty4:3.6.3.Final=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jboss.spec.javax.annotation:jboss-annotations-api_1.3_spec:1.0.1.Final=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.2.Final=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jboss.spec.javax.xml.bind:jboss-jaxb-api_2.3_spec:1.0.1.Final=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jctools:jctools-core-jdk11:4.0.6=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.jctools:jctools-core:4.0.6=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter:5.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-launcher:1.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-runner:1.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-suite-api:1.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-suite-commons:1.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit:junit-bom:5.14.0=spotbugs +org.junit:junit-bom:5.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.mockito:mockito-core:4.4.0=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.objenesis:objenesis:3.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.ow2.asm:asm-analysis:9.7.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.ow2.asm:asm-analysis:9.9=spotbugs +org.ow2.asm:asm-commons:9.9=spotbugs +org.ow2.asm:asm-commons:9.9.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.ow2.asm:asm-tree:9.9=spotbugs +org.ow2.asm:asm-tree:9.9.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.ow2.asm:asm-util:9.7.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.ow2.asm:asm-util:9.9=spotbugs +org.ow2.asm:asm:9.9=spotbugs +org.ow2.asm:asm:9.9.1=buildTimeInstrumentationPlugin,compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.reactivestreams:reactive-streams:1.0.2=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:jcl-over-slf4j:1.7.30=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:jul-to-slf4j:1.7.30=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:log4j-over-slf4j:1.7.30=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:1.7.30=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath +org.slf4j:slf4j-api:1.7.32=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:2.0.17=spotbugs,spotbugsSlf4j +org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j +org.snakeyaml:snakeyaml-engine:2.9=buildTimeInstrumentationPlugin,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.spockframework:spock-bom:2.4-groovy-3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.spockframework:spock-core:2.4-groovy-3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.tabletest:tabletest-junit:1.2.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.tabletest:tabletest-parser:1.2.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.xmlresolver:xmlresolver:5.3.3=spotbugs +org.yaml:snakeyaml:1.33=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +empty=spotbugsPlugins From 1f553d7aa1898b33936b67f1a180e6e4d79c0397 Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Fri, 17 Apr 2026 11:30:05 +0200 Subject: [PATCH 7/8] Raise agent JAR size limit from 32 MB to 33 MB --- dd-java-agent/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/build.gradle b/dd-java-agent/build.gradle index f05fe7ca779..de03c37d4e5 100644 --- a/dd-java-agent/build.gradle +++ b/dd-java-agent/build.gradle @@ -467,7 +467,7 @@ tasks.register('checkAgentJarSize') { doLast { // Arbitrary limit to prevent unintentional increases to the agent jar size // Raise or lower as required - assert tasks.named("shadowJar", ShadowJar).get().archiveFile.get().getAsFile().length() <= 32 * 1024 * 1024 + assert tasks.named("shadowJar", ShadowJar).get().archiveFile.get().getAsFile().length() <= 33 * 1024 * 1024 } dependsOn "shadowJar" From 055530140f43169b3fd88be5859d3d04964cf4a0 Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Fri, 17 Apr 2026 20:58:00 +0200 Subject: [PATCH 8/8] Register sofarpc configs in supported-configurations.json --- metadata/supported-configurations.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index ac7935039e3..af6c4bfc94a 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -9889,6 +9889,30 @@ "aliases": ["DD_TRACE_INTEGRATION_SOCKET_ENABLED", "DD_INTEGRATION_SOCKET_ENABLED"] } ], + "DD_TRACE_SOFARPC_ANALYTICS_ENABLED": [ + { + "version": "A", + "type": "boolean", + "default": "false", + "aliases": ["DD_SOFARPC_ANALYTICS_ENABLED"] + } + ], + "DD_TRACE_SOFARPC_ANALYTICS_SAMPLE_RATE": [ + { + "version": "A", + "type": "decimal", + "default": "1.0", + "aliases": ["DD_SOFARPC_ANALYTICS_SAMPLE_RATE"] + } + ], + "DD_TRACE_SOFARPC_ENABLED": [ + { + "version": "A", + "type": "boolean", + "default": "true", + "aliases": ["DD_TRACE_INTEGRATION_SOFARPC_ENABLED", "DD_INTEGRATION_SOFARPC_ENABLED"] + } + ], "DD_TRACE_SPAN_ATTRIBUTE_SCHEMA": [ { "version": "B",