Skip to content

feat: Add support for Thrift 0.9.1 and later versions#13784

Closed
YaoYingLong wants to merge 20 commits into
open-telemetry:mainfrom
YaoYingLong:feature/thrift-0.9.1
Closed

feat: Add support for Thrift 0.9.1 and later versions#13784
YaoYingLong wants to merge 20 commits into
open-telemetry:mainfrom
YaoYingLong:feature/thrift-0.9.1

Conversation

@YaoYingLong
Copy link
Copy Markdown
Contributor

Add support for Thrift 0.9.1 and later versions

#1573

Special Note:Although 51 files were added with a total of 16,937 lines of code, 12 of them are test-related files containing 14,960 lines of code, while the actual instrumentation adaptation code is only around 1,900 lines. Among the test-related code, approximately 9,000 lines were generated using the Thrift client based on IDL files for testing purposes. Due to the limitations of Thrift client code generation, these generated codes were pre-generated and placed in the project's test directory.

@YaoYingLong YaoYingLong requested a review from a team as a code owner April 28, 2025 11:24
@SylvainJuge
Copy link
Copy Markdown
Contributor

In order to minimize the amount of code, do you think it would be possible to re-generate the generated code on build instead of adding it to git ? The likely challenge here would be being able to maintain those over time, in particular it would really be useful to be able to re-generate and update those every time the thrift version changes, or even generating and testing multiple versions.

pass {
group.set("org.apache.thrift")
module.set("libthrift")
versions.set("[0.9.1,)")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually we also add assertInverse.set(true) to verify that instrumentation does not apply on earlier versions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified

var generateThrift = tasks.register<Exec>("generateThrift") {
group = "build"
description = "Generate Java code from Thrift IDL files"
commandLine(thriftExecutable, "--gen", "java", "-out", thriftOutputDir, thriftInputFile)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binaries shouldn't be really added to the source code repository, there is even a scorecard check for that https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#binary-artifacts Also since the project needs to build on at least windows, linux and mac just having one binary wouldn't work. Currently code generation seems to fail https://scans.gradle.com/s/73advrdsyn2fk/console-log/task/:instrumentation:thrift:thrift-0.9.1:javaagent:generateThrift?anchor=1&page=1 I would have hoped that the compiler binaries would be available for download like for the gRPC but that doesn't seem to be the case.
I think it would be best to go back to adding the generated java files to the repo for now. We can work on automating the generation later when other issues have been sorted. One way we could do the generation would be to have the thrift compiler in a docker image and run that image during the build similarly to how semconv repo runs weaver https://github.com/open-telemetry/semantic-conventions-java/blob/36743b068e1d3bbff129b528a340a63fb544e390/build.gradle.kts#L86 An example of such image can be found from https://github.com/reddit/thrift-compiler/pkgs/container/thrift-compiler If we can't find an existing image for the version we need we can build it ourself. @open-telemetry/java-instrumentation-approvers do you have any suggestions? Would having the thrift compiler in docker image be worth the effort?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I will temporarily add the generated Java files to the repository, or temporarily remove the test code.

* Note that the 8888th field of record is reserved for transporting trace header. Because Thrift
* doesn't support to transport metadata.
*/
public abstract class AbstractProtocolWrapper extends TProtocolDecorator {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified

import org.apache.thrift.protocol.TType;
import org.apache.thrift.transport.TTransport;

@SuppressWarnings("all")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of suppressing warnings we prefer to fix the code so that it wouldn't have the warnings

// TMultiplexedProcessor compatible
Field field = null;
try {
field = TProtocolDecorator.class.getDeclaredField("concreteProtocol");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when we use reflection we always fetch the field/method only once and the reuse it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified

@@ -0,0 +1,10 @@
plugins {
id("otel.library-instrumentation")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By library instrumentation we mean instrumentation that can be applied without the agent. This module does not seem to contain a library instrumentation in that sense. Consider merging it into the javaagent module or if you intend to add instrumentations for more versions that could benefit from shared code you could rename this module from library to javaagent too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the shared code for adding instrumentation for more versions, so I have renamed the module to javaagent.

import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import javax.annotation.Nullable;

final class ThriftAttributesExtractor implements AttributesExtractor<ThriftRequest, Integer> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty class

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted


import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;

public final class ThriftSpanNameExtractor implements SpanNameExtractor<ThriftRequest> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are semantic conventions that define the name for a RPC span, you should follow them. See RpcSpanNameExtractor.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified


@Override
public String getSystem(ThriftRequest request) {
return "thrift";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also create a pr to add thrift to rpc.system values in https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md. See open-telemetry/semantic-conventions@7c1aa90 for inspiration. It is possible that the value should be apache_thrift instead of thrift but I'll leave this for the semantic conventions folks to decide.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified

@fatekingsama
Copy link
Copy Markdown

@YaoYingLong 兄弟你还在维护这个pull req吗

@YaoYingLong
Copy link
Copy Markdown
Contributor Author

@YaoYingLong 兄弟你还在维护这个pull req吗

在的 只是最近没有时间顾的上来修改提的修改建议

@beztebya666
Copy link
Copy Markdown

Any updates?

@beztebya666
Copy link
Copy Markdown

@YaoYingLong ?

@breedx-splk breedx-splk added the needs author feedback Waiting for additional feedback from the author label Feb 24, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 3, 2026

This PR has been labeled as stale due to lack of activity and needing author feedback. It will be automatically closed if there is no further activity over the next 7 days.

@github-actions github-actions Bot added the stale label Mar 3, 2026
@YaoYingLong
Copy link
Copy Markdown
Contributor Author

Any updates?

Optimized according to Laurit's suggestions.

@github-actions github-actions Bot removed needs author feedback Waiting for additional feedback from the author stale labels Mar 10, 2026
@YaoYingLong
Copy link
Copy Markdown
Contributor Author

@beztebya666 @laurit Hello, both of you,

Thank you for your previous suggestions! I have made the necessary changes based on your feedback and have also fixed all the failing test cases. Could you please help review it again? Thank you so much!

@MileaRobertStefan
Copy link
Copy Markdown

Any updates on this?

@beztebya666
Copy link
Copy Markdown

@beztebya666 @laurit Hello, both of you,

Thank you for your previous suggestions! I have made the necessary changes based on your feedback and have also fixed all the failing test cases. Could you please help review it again? Thank you so much!

@laurit need to see this

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds OpenTelemetry Javaagent instrumentation for Apache Thrift (targeting Thrift 0.9.1+) by introducing a shared thrift-common library-instrumentation module and a thrift-0.9.1 javaagent module, and wiring these new projects into the build and FOSSA scanning configuration.

Changes:

  • Introduces instrumentation/thrift/thrift-common helpers (request model, header propagation helpers, RPC + network attribute getters, instrumenter factory).
  • Adds instrumentation/thrift/thrift-0.9.1 ByteBuddy instrumentations and protocol wrappers for client/server span creation, context propagation, and async handling.
  • Registers the new Gradle modules in settings.gradle.kts and adds new FOSSA Gradle targets.

Reviewed changes

Copilot reviewed 36 out of 36 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
settings.gradle.kts Adds Thrift modules to the Gradle build (but currently drops zio-http-3.0 include).
.fossa.yml Adds FOSSA targets for the new Thrift modules (but currently drops zio-http-3.0 target).
instrumentation/thrift/thrift-common/javaagent/build.gradle.kts Declares dependencies for the shared Thrift instrumentation helpers.
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/ThriftRequest.java Defines a request carrier for span naming/attributes and context propagation.
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/ThriftInstrumenterFactory.java Centralizes client/server Instrumenter construction (RPC + network attrs, metrics, propagators).
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/ThriftSpanNameExtractor.java Derives span names from service + method.
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/ThriftSpanStatusExtractor.java Maps Thrift status/error to span status.
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/ThriftRpcAttributesGetter.java Supplies RPC semantic convention attributes (rpc.system/service/method).
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/ThriftHeaderGetter.java Extracts propagation headers from the Thrift request carrier.
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/ThriftHeaderSetter.java Injects propagation headers into the Thrift request carrier.
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/SocketAccessor.java Extracts underlying Socket from various Thrift TTransport implementations.
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/RequestScopeContext.java Stores request + context + optional scope for lifecycle management.
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/client/MethodAccessor.java Reflection-based helpers for method return-type discovery and protocol field access.
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/client/VirtualFields.java Stores async callback associations via VirtualField.
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/client/ThriftClientNetworkAttributesGetter.java Supplies network peer attributes for client spans.
instrumentation/thrift/thrift-common/javaagent/src/main/java/io/opentelemetry/instrumentation/thrift/common/server/ThriftServerNetworkAttributesGetter.java Supplies network peer attributes for server spans.
instrumentation/thrift/thrift-0.9.1/javaagent/build.gradle.kts Declares javaagent plugin usage and muzzle range for Thrift 0.9.1+.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/ThriftSingletons.java Provides singleton instrumenters for client/server.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/AbstractProtocolWrapper.java Base protocol decorator and reserved field constants for trace header transport.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/AsyncMethodCallbackWrapper.java Ends spans on async completion/error.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/client/ThriftClientCommonInstrumentationModule.java Registers client-side type instrumentations.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/client/ThriftClientInstrumentation.java Instruments sync clients: wraps protocols and ends spans on send/receive.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/client/ThriftAsyncClientInstrumentation.java Instruments async clients: wraps protocol factory to inject tracing.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/client/ClientProtocolFactoryWrapper.java Wraps TProtocolFactory to return tracing protocol decorators.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/client/ClientOutProtocolWrapper.java Client protocol wrapper that starts spans and injects trace headers via reserved field.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/client/ThriftAsyncMethodCallInstrumentation.java Captures async callbacks and associates them with method calls via VirtualField.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/client/ThriftAsyncWriteArgsInstrumentation.java Adjusts async oneway behavior and wires request scope to callbacks.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/server/ThriftServerInstrumentationModule.java Registers server-side type instrumentations.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/server/ThriftServerInstrumentation.java Wraps server protocol factory on server construction.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/server/ServerProtocolFactoryWrapper.java Produces server-side tracing protocol wrapper instances.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/server/ServerInProtocolWrapper.java Server protocol wrapper that extracts headers and starts server spans.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/server/ThriftFrameBufferInstrumentation.java Captures transport on non-blocking servers to support socket extraction.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/server/ThriftAsyncProcessInstrumentation.java Wraps async result handlers to end spans on callback completion.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/server/ThriftBaseProcessorInstrumentation.java Ends server spans around sync processor process() execution.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/server/ThriftBaseAsyncProcessorInstrumentation.java Ends server spans for async processor execution paths.
instrumentation/thrift/thrift-0.9.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/thrift/v0_9_1/server/ThriftMutiplexedProcessorInstrumentation.java Handles TMultiplexedProcessor end-of-request behavior.

Comment thread settings.gradle.kts Outdated
Comment on lines +714 to +718
include(":instrumentation:xxl-job:xxl-job-common:javaagent")
include(":instrumentation:xxl-job:xxl-job-common:testing")
include(":instrumentation:zio:zio-2.0:javaagent")
include(":instrumentation:zio:zio-http-3.0:javaagent")
include(":instrumentation:thrift:thrift-0.9.1:javaagent")
include(":instrumentation:thrift:thrift-common:javaagent")
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

settings.gradle.kts no longer includes the :instrumentation:zio:zio-http-3.0:javaagent project, but that module still exists in the repo (e.g., instrumentation/zio/zio-http-3.0/...) and is referenced from docs/instrumentation-list.yaml. This will stop it from being built/published and may break expectations; please re-add the include (and keep the Thrift includes as well).

Copilot uses AI. Check for mistakes.
Comment thread .fossa.yml
path: ./
target: ':instrumentation:zio:zio-http-3.0:javaagent'
- type: gradle
path: ./
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.fossa.yml removed the Gradle target for :instrumentation:zio:zio-http-3.0:javaagent, but the module still exists and is listed in docs/instrumentation-list.yaml. Please restore the removed FOSSA target entry so license scanning continues to cover this instrumentation.

Suggested change
path: ./
path: ./
target: ':instrumentation:zio:zio-http-3.0:javaagent'
- type: gradle
path: ./

Copilot uses AI. Check for mistakes.
return field;
}
Context context = serverInstrumenter().start(parentContext, request);
this.requestScopeContext = RequestScopeContext.create(request, null, context);
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the trace header is found in readFieldBegin(), the server span is started but the returned Context is never made current (scope is stored as null). This means downstream work during request processing won't see the server context. Consider calling context.makeCurrent() here too and storing the resulting Scope in RequestScopeContext (mirroring the readMessageEnd() path).

Suggested change
this.requestScopeContext = RequestScopeContext.create(request, null, context);
Scope scope = context.makeCurrent();
this.requestScopeContext = RequestScopeContext.create(request, scope, context);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an asynchronous non-blocking scenario, the related logic will be executed in a separate thread. During thread switching, context.makeCurrent() will be executed once. If context.makeCurrent() is executed again at this point, it will result in duplicate executions of context.makeCurrent(), which may lead to issues.

Comment on lines +22 to +26
public static Set<String> voidMethodNames(String serviceName) {
Set<String> methodNames = SERVICE_NAME_METHOD_NAMES.getOrDefault(serviceName, new HashSet<>());
if (!methodNames.isEmpty()) {
return methodNames;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

voidMethodNames() re-runs reflection every time for services that have no void-returning methods because it treats an empty cached set as “not cached” (if (!methodNames.isEmpty())). This can add unnecessary overhead on hot paths. Consider caching based on containsKey(serviceName) (or using computeIfAbsent) so an empty result is still memoized; also consider using a concurrent map since this cache is accessed from multiple threads in the agent.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +30
public TProtocol getProtocol(TTransport transport) {
TProtocol protocol = this.delegate.getProtocol(transport);
if (protocol instanceof ClientOutProtocolWrapper) {
if (transport != null) {
((ClientOutProtocolWrapper) protocol).updateTransport(this.transport);
}
((ClientOutProtocolWrapper) protocol).setServiceName(this.serviceName);
return protocol;
}
protocol = new ClientOutProtocolWrapper(protocol, this.serviceName, null);
if (transport != null) {
((ClientOutProtocolWrapper) protocol).updateTransport(this.transport);
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getProtocol(TTransport transport) ignores its transport parameter and instead calls updateTransport(this.transport). If the factory is ever reused with a different transport, the wrapper will retain the wrong transport/socket info. Please update the wrapper using the method parameter (and/or drop the stored transport field if it’s not needed).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The passed-in TTransport might be wrapped multiple times, and only the TTransport provided in the constructor is the original one that contains the socket.

Comment on lines +28 to +33
public final class ThriftMutiplexedProcessorInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.apache.thrift.TMultiplexedProcessor");
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class name ThriftMutiplexedProcessorInstrumentation has a typo (“Mutiplexed” vs “Multiplexed”), while it instruments org.apache.thrift.TMultiplexedProcessor. Renaming to ThriftMultiplexedProcessorInstrumentation would improve discoverability and consistency (and update the module’s typeInstrumentations() list accordingly).

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +23
public final class ThriftClientNetworkAttributesGetter
implements ServerAttributesGetter<ThriftRequest>,
NetworkAttributesGetter<ThriftRequest, Integer> {

@Nullable
@Override
public InetSocketAddress getNetworkPeerInetSocketAddress(
ThriftRequest request, @Nullable Integer integer) {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThriftClientNetworkAttributesGetter implements ServerAttributesGetter, but does not override getServerAddress() / getServerPort(). As a result, ServerAttributesExtractor.create(netClientAttributesGetter) in ThriftInstrumenterFactory.clientInstrumenter() is effectively a no-op. Either implement server address/port extraction (e.g., from the socket) or remove the ServerAttributesExtractor registration to avoid confusion and extra work.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +15
import javax.annotation.Nullable;

public enum ThriftHeaderGetter implements TextMapGetter<ThriftRequest> {
INSTANCE;

@Override
public Iterable<String> keys(ThriftRequest request) {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TextMapGetter.keys() does not handle a null carrier, while get() does. Many TextMapGetter implementations in this repo accept @Nullable and return an empty list when the carrier is null to avoid NPEs during propagation. Consider aligning keys() with that pattern.

Suggested change
import javax.annotation.Nullable;
public enum ThriftHeaderGetter implements TextMapGetter<ThriftRequest> {
INSTANCE;
@Override
public Iterable<String> keys(ThriftRequest request) {
import java.util.Collections;
import javax.annotation.Nullable;
public enum ThriftHeaderGetter implements TextMapGetter<ThriftRequest> {
INSTANCE;
@Override
public Iterable<String> keys(@Nullable ThriftRequest request) {
if (request == null) {
return Collections.emptyList();
}

Copilot uses AI. Check for mistakes.
public final class ThriftSpanNameExtractor implements SpanNameExtractor<ThriftRequest> {
@Override
public String extract(ThriftRequest request) {
if (request.getServiceName() == null || request.getMethodName() == null) {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThriftRequest.getMethodName() is declared non-null, but ThriftSpanNameExtractor.extract() still checks request.getMethodName() == null. This is inconsistent and can hide actual contract violations (AutoValue will throw if methodName is null). Either make methodName nullable end-to-end or remove the null check and rely on the non-null contract.

Suggested change
if (request.getServiceName() == null || request.getMethodName() == null) {
if (request.getServiceName() == null) {

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +44
transformer.applyAdviceToMethod(
isConstructor().and(takesArguments(2)),
ThriftClientInstrumentation.class.getName() + "$ConstructorTowAdvice");

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in advice class name: ConstructorTowAdvice should be ConstructorTwoAdvice (or similar). Since these names are used in stack traces and debugging, correcting the typo will make troubleshooting easier.

Copilot uses AI. Check for mistakes.
@laurit
Copy link
Copy Markdown
Contributor

laurit commented Apr 4, 2026

One issue with this PR is that it doesn't include tests. There original PR did include them but it also included a ton of generated code. Ideally code generation would happen during the build. I'm not familiar with thrift toolchain so I don't know whether it is easy (e.g. for protobuf there is a gradle plugin that runs protoc). If tools need to be installed manually one option we could try would be to test for the presence of the tools and skip the build or tests when the necessary tools are not present and the CI environment variable is not set. In ci workflow these tools could be installed. Anyway having tests in the PR even if the include generated code would be preferable to having no tests.
Secondly at first glance the instrumentation looks complicated. There is an opentracing instrumentation for thrift https://github.com/opentracing-contrib/java-thrift that looks simpler. It is a manual instrumentation library not an agent instrumentation. Idk whether the complexity rises from wiring this up for the agent or something else. Does anybody know what this instrumentation, that originates from skywalking, offers over the opentracing one?

@beztebya666
Copy link
Copy Markdown

this is all turning into a very complex PR... we just wanted thrift support for this java agent..

is there an easier way? @laurit @YaoYingLong

I'll be honest, in 24 days this PR will be exactly a year old

@otelbot-java-instrumentation otelbot-java-instrumentation Bot added the test native This label can be applied to PRs to trigger them to run native tests label Apr 7, 2026
@YaoYingLong
Copy link
Copy Markdown
Contributor Author

One issue with this PR is that it doesn't include tests. There original PR did include them but it also included a ton of generated code. Ideally code generation would happen during the build. I'm not familiar with thrift toolchain so I don't know whether it is easy (e.g. for protobuf there is a gradle plugin that runs protoc). If tools need to be installed manually one option we could try would be to test for the presence of the tools and skip the build or tests when the necessary tools are not present and the CI environment variable is not set. In ci workflow these tools could be installed. Anyway having tests in the PR even if the include generated code would be preferable to having no tests. Secondly at first glance the instrumentation looks complicated. There is an opentracing instrumentation for thrift https://github.com/opentracing-contrib/java-thrift that looks simpler. It is a manual instrumentation library not an agent instrumentation. Idk whether the complexity rises from wiring this up for the agent or something else. Does anybody know what this instrumentation, that originates from skywalking, offers over the opentracing one?

@laurit @beztebya666

Regarding the implementation mentioned in https://github.com/opentracing-contrib/java-thrift, I took a closer look. Since Thrift is based on a layer 4 protocol, it inherently supports synchronous blocking, synchronous non-blocking, and asynchronous non-blocking modes. The SpanProcessor used on the server side is an implementation of Thrift's TProcessor, which indicates that it can only support synchronous blocking and synchronous non-blocking scenarios, but not asynchronous non-blocking scenarios (whether on the client or server side). The asynchronous non-blocking mode uses TAsyncProcessor + TNonblockingServer or TThreadedSelectorServer.

The fields exported in https://github.com/opentracing-contrib/java-thrift are actually quite limited and only support Thrift versions 0.13 and above. In this PR, network-related fields have been added for compatibility, supporting versions 0.9.1 and above, including differences in the oneway mechanism between versions 0.9.1, 0.9.2, and 0.9.3. Additionally, to ensure compatibility with asynchronous scenarios, the logic appears relatively complex. If only synchronous scenarios are considered and the interception for asynchronous scenarios is removed, the code would become much simpler. The implementation approach is actually the same as in java-thrift.

Regarding the tests, there are no available Docker images for version 0.9.x, so for now, only version 0.12 images can be used. Additionally, the previously removed test cases have been added back, with some simplifications and optimizations made.

@laurit
Copy link
Copy Markdown
Contributor

laurit commented Apr 7, 2026

@YaoYingLong thanks for the explanation. Just wondering if testing older versions is going to be a pain then perhaps we shouldn't bother with them at all? Would that simplify the instrumentation?

@YaoYingLong
Copy link
Copy Markdown
Contributor Author

@YaoYingLong thanks for the explanation. Just wondering if testing older versions is going to be a pain then perhaps we shouldn't bother with them at all? Would that simplify the instrumentation?

@laurit

Because some of our applications use version 0.9.x, we have implemented compatibility for this version. However, if you think it’s unnecessary, I can remove this part of the compatibility logic and only support relatively higher versions. This would simplify the compatibility logic for ONEWAY messages, but it won’t significantly simplify the code or reduce its complexity because the asynchronous logic is the same in higher versions and version 0.9.1. The primary cause of complexity lies in the asynchronous logic, which is used quite frequently.

If you want me to simplify the compatibility logic for ONEWAY messages, please let me know, and I will make the adjustments.

@laurit
Copy link
Copy Markdown
Contributor

laurit commented Apr 7, 2026

Because some of our applications use version 0.9.x, we have implemented compatibility for this version.

Lets try to keep the support for old versions then.

@trask
Copy link
Copy Markdown
Member

trask commented Apr 30, 2026

Thanks @YaoYingLong! Closing in favor of #18405

@trask trask closed this Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test native This label can be applied to PRs to trigger them to run native tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants