feat(o11y): Introduce rpc.system.name and rpc.method in gRPC#4121
feat(o11y): Introduce rpc.system.name and rpc.method in gRPC#4121diegomarquezp wants to merge 35 commits intomainfrom
rpc.system.name and rpc.method in gRPC#4121Conversation
Summary of ChangesHello @diegomarquezp, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces significant improvements to the observability framework by enriching tracing contexts with RPC system and method information. It refactors the tracing mechanism to be more flexible and less dependent on specific span naming conventions, allowing for better integration with standardized observability practices. The changes streamline how RPC details are captured and propagated through the tracing system, enhancing the clarity and utility of generated traces. Highlights
Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a significant and beneficial refactoring to the tracing infrastructure. By replacing the explicit SpanName with a more structured ApiTracerContext carried by ApiTracerFactory, the code becomes more maintainable and extensible. The introduction of rpc.system.name and rpc.method attributes aligns with modern observability practices.
However, there are a couple of points that need attention:
-
Missing Tests (Critical): The PR adds new logic (e.g., in
ApiTracerContext) and refactors existing components, but it lacks corresponding unit tests. The repository's contribution guidelines (line 148:Testing: All new logic should be accompanied by tests.) are not being met. Please add tests for the new functionality, especially for the parsing logic inApiTracerContext.fromGrpcMethodNameandApiTracerContext.fromHttpJsonMethodName, and ensure existing tests are updated to reflect the API changes. -
Robustness: There's a potential for a
NullPointerExceptioninSpanTracerFactorywhich can be addressed with a defensive check.
Overall, this is a good direction, but the lack of tests is a critical issue that must be addressed before merging.
gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java
Outdated
Show resolved
Hide resolved
b32f09a to
e5b927a
Compare
gax-java/gax/src/main/java/com/google/api/gax/tracing/TracedUnaryCallable.java
Outdated
Show resolved
Hide resolved
gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java
Outdated
Show resolved
Hide resolved
gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java
Show resolved
Hide resolved
| baseCallable, | ||
| clientContext.getTracerFactory(), | ||
| getSpanName(grpcCallSettings.getMethodDescriptor()), | ||
| getApiTracerContext(grpcCallSettings.getMethodDescriptor()), |
There was a problem hiding this comment.
Batching is technically OOS also, but since this is a very low risk change, I think we can take it.
gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java
Outdated
Show resolved
Hide resolved
| this.innerCallable = innerCallable; | ||
| this.tracerFactory = tracerFactory; | ||
| this.spanName = spanName; | ||
| this.apiTracerContext = null; |
There was a problem hiding this comment.
Can we create an apiTracerContext from the SpanName so we don't have to do null check below?
There was a problem hiding this comment.
We would need something like:
this.apiTracerContext = ApiTracerContext.newBuilder()
.setLibraryMetadata(LibraryMetadata.empty())
.setClientName(spanName.getClientName())
.setMethodName(spanName.getMethodName())
.build();However clientName and methodName are computed from fullMethodName (I"m moving the parsing logic to ApiTracerContext.
I think we can achieve this by having a private methodName property that will either return the set value or compute it as it's being done now. WDYT?
There was a problem hiding this comment.
Let's keep a setter as an override of the default regex parsing logic.
gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracerFactory.java
Show resolved
Hide resolved
gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java
Outdated
Show resolved
Hide resolved
| /** | ||
| * @return the client name part of the span name | ||
| */ | ||
| public String getClientName() { |
There was a problem hiding this comment.
Is clientName just serviceName which we should already have it in StubSettings?
There was a problem hiding this comment.
This is meant for computing span names, which may differ depending on the transport.
gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| /** | ||
| * @return the method name part of the span name |
There was a problem hiding this comment.
method name could be used for more than Span, can we use more generic terms instead of span name in the Java docs?
In addition, can we provide an example for each of the public attributes so its easier to understand?
There was a problem hiding this comment.
Improved the javadocs, which now just "RPC" instead of "span names".
I added examples to the public attributes.
There was a problem hiding this comment.
javadocs kept but I moved the SpanName related logic to SpanName instead of ApiTracerContext.
gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java
Show resolved
Hide resolved
gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java
Outdated
Show resolved
Hide resolved
gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java
Outdated
Show resolved
Hide resolved
| */ | ||
| default ApiTracer newTracer( | ||
| ApiTracer parent, ApiTracerContext tracerContext, OperationType operationType) { | ||
| SpanName spanName = SpanName.of(tracerContext.getClientName(), tracerContext.getMethodName()); |
There was a problem hiding this comment.
Pass full method name and transport; let span name do the parsing (keep the regexes in there).
There was a problem hiding this comment.
Making getters such as fullMethodName() package-private makes GrpcCallableFactory and HttpJsonCallableFactory fail due to lack of access.
Passing the whole context solves the problem.
…-attr/rpc.method-and-rpc.system
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces rpc.system.name and rpc.method span attributes for gRPC tracing, which is a great enhancement for observability. The implementation is well-structured, refactoring tracing context creation into a new ApiTracerContext class and adding new overloads to the Traced*Callable classes and ApiTracerFactory. The test coverage is excellent, with new parameterized tests to handle both old and new code paths, and an integration test to verify the new attributes are correctly added to spans. I have a couple of minor suggestions to improve documentation and code consistency.
gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java
Outdated
Show resolved
Hide resolved
gax-java/gax/src/main/java/com/google/api/gax/tracing/TracedUnaryCallable.java
Outdated
Show resolved
Hide resolved
…ilityAttributes.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…https://github.com/googleapis/sdk-platform-java into observability/tracing-attr/rpc.method-and-rpc.system
| * @return a new {@link SpanName} with the parsed client name and method name | ||
| */ | ||
| public static SpanName of(ApiTracerContext apiTracerContext) { | ||
| Preconditions.checkState(apiTracerContext.fullMethodName() != null, "rpcMethod must be set"); |
There was a problem hiding this comment.
I don't think fullMethodName() is nullable? Also can we only pass a String to this method?
There was a problem hiding this comment.
I don't think fullMethodName() is nullable?
I made it @Nullable considering the client-level vs method-def-level contexts we can have. ClientContext will not populate fullMethodName().
Also can we only pass a String to this method?
The problem with SpanName.of(apiTracerContext.getFullMethodName()) is that the callable factories cannot access the package-private method.
There was a problem hiding this comment.
I made it @nullable considering the client-level vs method-def-level contexts we can have. ClientContext will not populate fullMethodName()
I see, makes sense. This might be another data point that it's time to create a ClientTracerContext that only contains client level tracer info. And we can merge it with method/request level before we create a request level tracer. I feel we have a lot of nullable fields in ApiTracerContext only because we don't have the info initially. But we can do it in another PR.
There was a problem hiding this comment.
Sounds good, I'll follow up with separating the contexts.
| this.innerCallable = innerCallable; | ||
| this.tracerFactory = tracerFactory; | ||
| this.apiTracerContext = apiTracerContext; | ||
| this.spanName = null; |
There was a problem hiding this comment.
We can create a spanName from ApiTracerContext now.
There was a problem hiding this comment.
But we wouldn't use it if we have the context, right?
AFAIU we are trying to provide logic that only handles a context object as the SoT for SpanName. That's why we made this change in ApiTracer:
There was a problem hiding this comment.
Agreed. Even if we don't use it, I think it's safer to populate it instead of making it nullable.
There was a problem hiding this comment.
Sounds good, done.
| private final UnaryCallable<RequestT, ResponseT> innerCallable; | ||
| private final ApiTracerFactory tracerFactory; | ||
| private final SpanName spanName; | ||
| @Nullable private final SpanName spanName; |
There was a problem hiding this comment.
I think we can keep this spanName not nullable.
There was a problem hiding this comment.
I'll do it once we confirm my question in #4121 (comment)
There was a problem hiding this comment.
See answers below. Ideally we probably want to remove SpanName as a field, and only use ApiTracerContext as the source of truth. But it can not be done until we migrated all Callables to use ApiTracerContext.
There was a problem hiding this comment.
until we migrated all Callables to use ApiTracerContext
I thought the problem was with other classes that create the callables, such as GrpcCallableFactory. Are there usages of Traced*Callable in downstream libraries?
| * @param tracerContext the method-definition-specific tracer context | ||
| * @param operationType the type of operation that the tracer will trace | ||
| */ | ||
| default ApiTracer newTracer( |
There was a problem hiding this comment.
The downstream failure in bigtable is legit. One possible solution is to move OperationType to ApiTracerContext
|
|





This PR introduces the
rpc.system.nameandrpc.methodspan attributes in gRPC.To achieve this, all
Traced*Callableclasses were modified with a constructor overload that accepts anApiTracerContextinstance. Such an instance will be preferred overSpanNamewhen deciding which (also newly introduced) overload ofApiTracerFactory.newTracer()will be called.The new method in
ApiTracerFactory,newTracer(ApiTracer parent, ApiTracerContext tracerContext, OperationType operationType)will by default merge the existing factory's context with the new one and simply create a newSpanName.However, the logic in
SpanTracerFactorywill use the context to create the span name directly.The
rpc.system.nameandrpc.methodproperties are obtained fromGrpcCallableFactoryand stored inApiTracerContext, which now holds the regexes for parsing client and method names (used when buildingSpanNames).