Skip to content

feat(observability): introduce minimal tracing implementation#4105

Open
diegomarquezp wants to merge 16 commits intomainfrom
observability/initial-tracing-impl
Open

feat(observability): introduce minimal tracing implementation#4105
diegomarquezp wants to merge 16 commits intomainfrom
observability/initial-tracing-impl

Conversation

@diegomarquezp
Copy link
Contributor

@diegomarquezp diegomarquezp commented Feb 5, 2026

Summary

This PR introduces a new tracing mechanism in GAX that allows recording traces using OpenTelemetry. It provides a way of recording spans and attributes, following the existing ApiTracer class pattern with a few tracing-specific additions. The implementation is meant to be extensible to support other implementations.

New Classes

  • TracingRecorder: An interface for recording spans and attributes; can be implemented by observability frameworks.
  • OpenTelemetryTracingRecorder: An implementation of TracingRecorder that uses the OpenTelemetry API.
  • TracingTracer: An ApiTracer implementation that delegates span management to a TracingRecorder.
  • TracingTracerFactory: A factory for creating TracingTracer instances.
  • ApiTracerContext: A context object that carries information (like EndpointContext) used to infer common attributes for all tracers.
  • SpanHandle: A handle returned by TracingRecorder to manage the lifecycle of a specific span (ending it, recording errors, or setting attributes).

Approach

Connecting Tracer with Recorder

The implementation aims to decouple TracingTracer from TracingRecorder. When a tracer starts an operation or an attempt, it requests a SpanHandle from the recorder. This handle allows the tracer to update the span (e.g., adding attributes or recording errors) to keep TracingTracer separated from specific recorder implementations (like OpenTelemetry's Span object).

Attribute Inference via ApiTracerContext

To provide a source of Span Attributes that are common to all operations, we introduced ApiTracerContext. This context is passed to the ApiTracerFactory and contains information like the EndpointContext. It is operated by ClientContext.
Initially, only EndpointContext is contained in this class and it's meant to obtain the server.address attribute.

Integration Tests

A new integration test, ITOtelTracing, was added to the java-showcase module:

  • It validates that the expected spans (operation and attempt spans) are recorded with the correct names, parent-child relationships, and attributes (including server.address and gcp.client.language).
  • It verifies that the current OTel span is properly promoted to current in the thread context of Gax.

Note on coverage

Coverage of new code by java-showcase is below 80 due to:

  • EndpointContext non-public code (parseServerAddress). This is tested in unit tests.
  • TracingTracer failed attempt/operation methods. These will be tested when introducing error attributes.

Note on java-bigtable downstream check

Since SkipTrailersTest mocks the tracer factory, the EndpointContext call to apiTracerFactory.withContext() returns a null factory, causing a null pointer exception when building the client context.
We expect the test to be adjusted with this change.

Confirmation in Cloud Trace

image

@product-auto-label product-auto-label bot added the size: xl Pull request size is extra large. label Feb 5, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @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 significantly enhances the observability capabilities of GAX by integrating a robust tracing system based on OpenTelemetry. The design emphasizes extensibility and clear separation of concerns, allowing GAX to record detailed RPC lifecycle events as spans and attributes. It introduces a mechanism to automatically enrich these traces with contextual information, providing deeper insights into client-server interactions.

Highlights

  • OpenTelemetry Integration: Introduces a new tracing mechanism in GAX that records traces using OpenTelemetry, providing an extensible way to monitor RPC lifecycles.
  • New Tracing Abstractions: Defines core interfaces ('TracingRecorder', 'SpanHandle') and implementations ('OpenTelemetryTracingRecorder', 'TracingTracer', 'TracingTracerFactory') to decouple tracing logic from specific observability frameworks.
  • Context-Based Attribute Inference: Implements 'ApiTracerContext' to carry information (like 'EndpointContext') for inferring common span attributes, such as 'server.address' and 'gcp.client.language'.
  • Comprehensive Testing: Adds new unit tests for the tracing components and integration tests ('ITOtelTracing') in 'java-showcase' to validate span recording, relationships, and attribute propagation across gRPC and HTTP/JSON clients.
Changelog
  • gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java
    • Integrated 'ApiTracerContext' to pass endpoint information to the 'ApiTracerFactory' for attribute inference.
  • gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java
    • Added 'resolvedServerAddress()' method and logic to extract the server address from the resolved endpoint string.
  • gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java
    • New class: Introduced 'ApiTracerContext' to hold common attributes for 'ApiTracer' instances, initially containing 'EndpointContext'.
  • gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java
    • Added a default 'withContext' method to allow 'ApiTracerFactory' implementations to incorporate 'ApiTracerContext' for attribute generation.
  • gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java
    • New class: Implemented 'TracingRecorder' using the OpenTelemetry API for creating, managing, and ending spans, including setting attributes and recording errors.
  • gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java
    • New interface: Defined 'TracingRecorder' and 'SpanHandle' to abstract span recording operations and lifecycle management.
  • gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java
    • New class: Implemented 'ApiTracer' to use 'TracingRecorder' for span management, handling operation and attempt spans, and applying default and inferred attributes.
  • gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java
    • New class: Implemented 'ApiTracerFactory' to construct 'TracingTracer' instances, propagating 'TracingRecorder' and handling context-based attribute injection.
  • gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java
    • Added a test to verify the correct parsing and resolution of server addresses from various endpoint formats.
  • gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java
    • New unit tests for 'OpenTelemetryTracingRecorder' covering span creation, kind assignment, attribute setting, error recording, and OpenTelemetry context management.
  • gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerFactoryTest.java
    • New unit tests for 'TracingTracerFactory' validating tracer instantiation, attribute propagation, and 'ApiTracerContext' integration.
  • gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerTest.java
    • New unit tests for 'TracingTracer' ensuring correct span lifecycle management for operations and attempts, and attribute handling.
  • java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java
    • New integration tests ('ITOtelTracing') to verify end-to-end OpenTelemetry tracing for gRPC and HTTP/JSON clients, including span naming, hierarchy, and attribute correctness.
Activity
  • This pull request introduces a new feature and includes several new files and tests. There is no specific human activity (comments, reviews, or progress updates) recorded in the provided context.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new OpenTelemetry-based tracing system into GAX. Key changes include adding ApiTracerContext to provide endpoint information to tracers, enhancing EndpointContext to extract and store server addresses, and implementing TracingRecorder, TracingTracer, and TracingTracerFactory to integrate OpenTelemetry for operation and attempt spans. Review comments highlight the need to address thread-safety in TracingTracer by using ConcurrentHashMap for attributes, improve the robustness of EndpointContext's server address parsing using java.net.URL, remove a potentially problematic default implementation in TracingRecorder to enforce explicit parent span handling, standardize span naming conventions in TracingTracerFactory to align with OpenTelemetry, and correct the copyright year across several new files.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 5, 2026

@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 5, 2026

Quality Gate Failed Quality Gate failed for 'java_showcase_integration_tests'

Failed conditions
69.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@diegomarquezp diegomarquezp marked this pull request as ready for review February 5, 2026 23:54
private final TracingRecorder tracingRecorder;

/** Mapping of client attributes that are set for every TracingTracer at operation level */
private final Map<String, String> operationAttributes;
Copy link
Contributor

Choose a reason for hiding this comment

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

We exposed a map of attributes in MetricsTracerFactory because partner teams want to pass some client level attributes. In this case though, the TracingTracerFactory is supposed to be created by customers, which I don't think we want to allow them to pass additional attributes?

public TracingTracer(TracingRecorder recorder, String operationSpanName, String attemptSpanName) {
this.recorder = recorder;
this.attemptSpanName = attemptSpanName;
this.operationAttributes = new ConcurrentHashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we have to create a ConcurrentHashMap here, each ApiTracer is supposed to be manipulated only by one request.

return () -> {};
}

interface SpanHandle {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have to create a separate interface? Can this be flattened?

Copy link
Contributor

Choose a reason for hiding this comment

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

Discussed offline. We still need a reference to the Span created earlier in the request. Rename this to something like GaxSpan that wraps Span from Opentelemetry.

}

@Override
public SpanHandle startSpan(String name, Map<String, String> attributes) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Are there any attributes specific to the start of Span? If not, I think we may want to make them a field of the tracer, instead of always passing in as a parameter.


void recordError(Throwable error);

void setAttribute(String key, String value);
Copy link
Contributor

Choose a reason for hiding this comment

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

We probably don't need it at this moment. Use a field for client level attributes. Do not need to expose a setter if not needed.

interface SpanHandle {
void end();

void recordError(Throwable error);
Copy link
Contributor

Choose a reason for hiding this comment

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

Move it to a separate PR. Errors are not implemented yet.

if (watchdogProvider != null && watchdogProvider.shouldAutoClose()) {
backgroundResources.add(watchdog);
}
ApiTracerContext apiTracerContext =
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be a client level tracing context. We can initialize all client level attributes here and pass them to the tracer factory.

Comment on lines +89 to +91
public ApiTracer.Scope inScope(SpanHandle handle) {
if (handle instanceof OtelSpanHandle) {
Scope scope = ((OtelSpanHandle) handle).span.makeCurrent();
Copy link
Contributor

Choose a reason for hiding this comment

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

Investigate more to understand what exactly does Scope do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: xl Pull request size is extra large.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants