Skip to content

feat(sdk-platform-java): Add CompositeTracer and CompositeTracerFactory.#12321

Merged
blakeli0 merged 13 commits intomainfrom
feature/add-composite-tracer
Apr 2, 2026
Merged

feat(sdk-platform-java): Add CompositeTracer and CompositeTracerFactory.#12321
blakeli0 merged 13 commits intomainfrom
feature/add-composite-tracer

Conversation

@blakeli0
Copy link
Copy Markdown
Contributor

@blakeli0 blakeli0 commented Mar 30, 2026

This PR

  • Introduces CompositeTracer and CompositeTracerFactory into the gax-java tracing architecture, enabling the delegation of telemetry, logging, and metrics events to multiple underlying ApiTracer instances.

  • Creates a LoggingTracerFactory if logging is enabled in ClientContext.

@blakeli0 blakeli0 requested a review from a team as a code owner March 30, 2026 21:49
Copy link
Copy Markdown
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 composite tracing to GAX Java, allowing multiple tracers to be used via the new CompositeTracer and CompositeTracerFactory. It also optimizes context creation by adding a needsContext check to the ApiTracerFactory interface. Feedback identifies a potential resource leak and nesting issue in CompositeTracer.inScope() due to the lack of LIFO closing and exception safety.

Comment on lines +53 to +65
public Scope inScope() {
final List<Scope> childScopes = new ArrayList<>(children.size());

for (ApiTracer child : children) {
childScopes.add(child.inScope());
}

return () -> {
for (Scope childScope : childScopes) {
childScope.close();
}
};
}
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.

medium

The inScope() implementation has a few issues:

  1. LIFO Order: Scopes should be closed in reverse order (Last-In, First-Out) to correctly handle nested contexts, similar to how try-with-resources handles multiple resources.
  2. Exception Safety: If child.inScope() throws an exception, previously opened scopes are leaked. Similarly, if one childScope.close() throws, subsequent scopes are not closed.

Consider using a more robust implementation that ensures all scopes are closed even if some fail.

  @Override
  public Scope inScope() {
    final List<Scope> childScopes = new ArrayList<>(children.size());
    try {
      for (ApiTracer child : children) {
        childScopes.add(child.inScope());
      }
    } catch (RuntimeException e) {
      for (Scope scope : childScopes) {
        try {
          scope.close();
        } catch (RuntimeException suppressed) {
          e.addSuppressed(suppressed);
        }
      }
      throw e;
    }

    return () -> {
      Throwable error = null;
      for (int i = childScopes.size() - 1; i >= 0; i--) {
        try {
          childScopes.get(i).close();
        } catch (Throwable t) {
          if (error == null) {
            error = t;
          } else {
            error.addSuppressed(t);
          }
        }
      }
      if (error != null) {
        if (error instanceof RuntimeException) throw (RuntimeException) error;
        if (error instanceof Error) throw (Error) error;
        throw new RuntimeException(error);
      }
    };
  }

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.

I agree with #1 here, logging needs to close before tracing.

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.

Exception safety also looks valid to me. User code may be swallowing exceptions while leaving open tracers in scope.

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.

I agree with both, updated. That being said, this is mostly for backward compatibility, it is not used by any of the tracers at this moment. It was originally designed to mimic OpenCensus Scope, but OpenTelemetry significant reduced the complexity of it and we don't have to manage the lifecycle of it mostly of time.

Copy link
Copy Markdown
Contributor

@westarle westarle left a comment

Choose a reason for hiding this comment

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

I left a few thoughts here, you might have considered them already!

Comment on lines +53 to +65
public Scope inScope() {
final List<Scope> childScopes = new ArrayList<>(children.size());

for (ApiTracer child : children) {
childScopes.add(child.inScope());
}

return () -> {
for (Scope childScope : childScopes) {
childScope.close();
}
};
}
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.

I agree with #1 here, logging needs to close before tracing.

Copy link
Copy Markdown
Contributor

@diegomarquezp diegomarquezp left a comment

Choose a reason for hiding this comment

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

LGTM overall, just some minor comments.

Comment on lines +53 to +65
public Scope inScope() {
final List<Scope> childScopes = new ArrayList<>(children.size());

for (ApiTracer child : children) {
childScopes.add(child.inScope());
}

return () -> {
for (Scope childScope : childScopes) {
childScope.close();
}
};
}
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.

Exception safety also looks valid to me. User code may be swallowing exceptions while leaving open tracers in scope.

*/
default ApiTracerContext getApiTracerContext() {
return ApiTracerContext.empty();
default boolean needsContext() {
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.

I see this is only used in ClientContext. Is this to avoid an unnecessary flow on legacy tracer factories? If so, I think we should also use this function in GrcpCallableFactory and its HttpJson counterpart.

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.

It is mostly used to avoid calling withContext() if needsContext is false, to prevent potential test cases in customers' repos. I don't think we need it in GrpcCallableFactory.

Copy link
Copy Markdown
Contributor

@diegomarquezp diegomarquezp left a comment

Choose a reason for hiding this comment

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

LGTM, but checks are failing

@blakeli0 blakeli0 requested a review from diegomarquezp April 1, 2026 20:47
@blakeli0 blakeli0 merged commit 4b5e8af into main Apr 2, 2026
124 of 127 checks passed
@blakeli0 blakeli0 deleted the feature/add-composite-tracer branch April 2, 2026 02:53
diegomarquezp pushed a commit that referenced this pull request Apr 8, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>1.83.0</summary>

##
[1.83.0](v1.82.0...v1.83.0)
(2026-04-07)


### Features

* [aiplatform] [Memorystore for Redis Cluster] Add support for
([0bd7666](0bd7666))
* [aiplatform] Add container_spec to Reasoning Engine public protos
([0bd7666](0bd7666))
* [aiplatform] Add container_spec to Reasoning Engine public protos
([0bd7666](0bd7666))
* [aiplatform] Add container_spec to Reasoning Engine public protos
([3ba3854](3ba3854))
* [aiplatform] Add container_spec to Reasoning Engine public protos
([3ba3854](3ba3854))
* [aiplatform] add evaluation metrics and autorater configuration to
([0bd7666](0bd7666))
* [backupdr] Adding new workload specific fields for AlloyDB
([6344cb0](6344cb0))
* [ces] update public libraries for CES v1
([6344cb0](6344cb0))
* [ces] update public libraries for CES v1beta
([0bd7666](0bd7666))
* [ces] update public libraries for CES v1beta
([0bd7666](0bd7666))
* [chat] Addition of Section and SectionItem APIs
([0bd7666](0bd7666))
* [chat] Support app authentication with admin-consent scopes for
([0bd7666](0bd7666))
* [databasecenter] A new value `SUB_RESOURCE_TYPE_READ_POOL` is
([6344cb0](6344cb0))
* [dataflow] Add Pausing/Yaml capabilities to public protos
([3ba3854](3ba3854))
* [dataflow] add sha256 field to Package proto
([0bd7666](0bd7666))
* [dataflow] add sha256 field to Package proto
([3ba3854](3ba3854))
* [dataform] add folders and teamFolders related changes to v1
([6344cb0](6344cb0))
* [datalineage] add configmanagement v1 module
([#12355](#12355))
([2def625](2def625))
* [datamanager] add INVALID_MERCHANT_ID to the ErrorReason enum for
([6344cb0](6344cb0))
* [dialogflow-cx] updated v3 dialogflow client libraries with
([6344cb0](6344cb0))
* [dialogflow] updated v2 dialogflow client libraries
([6344cb0](6344cb0))
* [dialogflow] updated v2beta1 dialogflow client libraries
([6344cb0](6344cb0))
* [dlp] added support for detecting key-value pairs in client
([e5e22ed](e5e22ed))
* [document-ai] Added a fields for image and table annotation output
([0bd7666](0bd7666))
* [geocode] new module for geocode
([#12343](#12343))
([474efb1](474efb1))
* [netapp] Add ONTAP passthrough APIs
([6344cb0](6344cb0))
* [network-security] Publish proto definitions for AuthzPolicy,
([6344cb0](6344cb0))
* [redis-cluster] [Memorystore for Redis Cluster] Add support for
([0bd7666](0bd7666))
* [redis-cluster] [Memorystore for Redis Cluster] Add support for
([3ba3854](3ba3854))
* [redis-cluster] [Memorystore for Redis Cluster] Add support for
([3ba3854](3ba3854))
* [securesourcemanager] Add CustomHostConfig to configure custom
([6344cb0](6344cb0))
* [storage] populate the `persisted_data_checksums` field with
([e5e22ed](e5e22ed))
* [texttospeech] Support safety settings for Gemini voices and
([0bd7666](0bd7666))
* [texttospeech] Support safety settings for Gemini voices and
([0bd7666](0bd7666))
* [texttospeech] Support safety settings for Gemini voices and
([0bd7666](0bd7666))
* [texttospeech] Support safety settings for Gemini voices and
([0bd7666](0bd7666))
* [translate] A new field `mime_type` is added to message
([e5e22ed](e5e22ed))
* [valkey] [Memorystore for Valkey] Add support for Flexible CA
([0bd7666](0bd7666))
* [valkey] [Memorystore for Valkey] Add support for Flexible CA
([0bd7666](0bd7666))
* [valkey] [Memorystore for Valkey] Add support for Flexible CA
([3ba3854](3ba3854))
* Add getProjectId getter for ComputeEngineCredentials
([#1833](#1833))
([0a7895a](0a7895a))
* **bigguery:** add url.domain to span tracing
([#12208](#12208))
([6f79c2d](6f79c2d))
* **bigquery observability:** add version attribute to span tracing
([#12132](#12132))
([95c3eb8](95c3eb8))
* **bigquery:** add gcp.resource.destination.id for span tracing
([#12134](#12134))
([5f31ded](5f31ded))
* **bigquery:** add opentelemetry W3C Trace Context to headers
([#12203](#12203))
([965761a](965761a))
* **bigquery:** add resend attribute to span tracing + integration tests
([#12313](#12313))
([167722d](167722d))
* **bigquery:** add url.full attribute to span tracing
([#12176](#12176))
([7fdf9ff](7fdf9ff))
* **bigquery:** add url.template to span tracing
([#12181](#12181))
([30f8afb](30f8afb))
* **bigquery:** added error attributes to span tracing
([#12115](#12115))
([863d23b](863d23b))
* Extract resource name from unary requests for tracing
([#4159](#4159))
([23b16b7](23b16b7))
* **gapic-generator-java:** Extract resource name heuristicly
([#12207](#12207))
([f46480a](f46480a))
* **gax:** Actionable Errors Logging API Tracer
([#12202](#12202))
([8d23279](8d23279))
* **gax:** Add error attributes to golden signal metrics.
([#12564](#12564))
([063dfe5](063dfe5))
* **gax:** add utility for logging actionable errors
([#4144](#4144))
([54fb8a5](54fb8a5))
* **gax:** Implement trace context extraction and injection with
integration test
([#12625](#12625))
([6675310](6675310))
* **observability:** Implement gcp.client.service attribute
([#12315](#12315))
([e99812f](e99812f))
* **observability:** implement url.domain attribute
([#12316](#12316))
([0a865bf](0a865bf))
* **sdk-platform-java:** Add CompositeTracer and CompositeTracerFactory.
([#12321](#12321))
([4b5e8af](4b5e8af))
* Switch Eef metrics to using built in open telemetry
([#4385](#4385))
([759bb22](759bb22))


### Bug Fixes

* Add error attributes to logging
([#12685](#12685))
([a9198ee](a9198ee))
* **bq jdbc:** allow & ignore unknown parameters
([#12352](#12352))
([2332ff1](2332ff1))
* **bq jdbc:** ensure getMoreResults() always moves the cursor
([#12353](#12353))
([ac1f0f4](ac1f0f4))
* **ci:** consolidate duplicate yaml keys in github actions workflows
([#12306](#12306))
([f644a19](f644a19))
* Clean up attributes for traces and metrics
([#12677](#12677))
([914f97f](914f97f))
* fix getLong on NUMERIC
([#2420](#2420))
([75ec5c2](75ec5c2))
* **gax:** Implement lazy resource name evaluation in ApiTracerContext
([#12618](#12618))
([5e47749](5e47749))
* Handle null server address
([#12184](#12184))
([435dd8c](435dd8c))
* **hermetic-build:** do not add release please comments on vertexai
poms
([#12559](#12559))
([5e161a7](5e161a7))
* **o11y:** create noop tracer when artifact ID is not set
([#12307](#12307))
([630d83d](630d83d))
* **o11y:** do not record error.type in successful runs
([#12620](#12620))
([28eebf0](28eebf0))
* **o11y:** remove `gpc.client.language` attribute
([#12621](#12621))
([40d2e43](40d2e43))
* **oauth2:** mask sensitive tokens in HTTP logs
([#1900](#1900))
([3e4ccb7](3e4ccb7))
* **release:** add Version.java as extra files in release-please
([#12617](#12617))
([f5101d9](f5101d9))
* **spanner:** enforce READY-only location aware routing and add
endpoint lifecycle management
([ecb86fd](ecb86fd))
* **spanner:** enforce READY-only location aware routing and add
endpoint lifecycle management
([#12678](#12678))
([ca9edb9](ca9edb9))
* **spanner:** improve grpc-gcp affinity cleanup and location-aware
retries
([a157c2f](a157c2f))
* **spanner:** improve grpc-gcp affinity cleanup and location-aware
retries
([#12682](#12682))
([aca0428](aca0428))
* use dynamic tracer name instead of hardcoded gax-java
([#12190](#12190))
([dea24db](dea24db))


### Dependencies

* bump jackson version to 2.18.3
([#12351](#12351))
([50304c1](50304c1))
* update dependencies.txt for grpc-gcp to 1.9.2
([#4164](#4164))
([f336fdc](f336fdc))
* update dependency com.google.apis:google-api-services-storage to
v1-rev20260204-2.0.0
([#1750](#1750))
([340ecbe](340ecbe))
* update dependency com.google.apis:google-api-services-storage to
v1-rev20260204-2.0.0
([#3519](#3519))
([1531107](1531107))
* update dependency com.google.cloud:google-cloud-storage to v2.64.1
([#1752](#1752))
([8fb6935](8fb6935))
* update dependency com.google.cloud:sdk-platform-java-config to v3.58.0
([#1751](#1751))
([9cc3e22](9cc3e22))
* update dependency com.google.cloud:sdk-platform-java-config to v3.58.0
([#3523](#3523))
([26d772a](26d772a))
* update dependency node to v24
([#3509](#3509))
([f308477](f308477))
* update gcr.io/cloud-devrel-public-resources/storage-testbench docker
tag to v0.62.0
([#3526](#3526))
([ca29d5e](ca29d5e))
* update googleapis/sdk-platform-java action to v2.68.0
([#3522](#3522))
([abae1ac](abae1ac))


### Reverts

* ci: only run default list of graalvm tests if too many modules are
touched
([#12292](#12292))
([92bcdf4](92bcdf4))


### Documentation

* [dataplex] Change Dataplex library from `ALPHA` to `GA`
([6344cb0](6344cb0))
* [run] An existing repeated string field custom_audiences is marked
([015d9a1](015d9a1))
* **hermetic-build:** improve usability of development guide
([#12362](#12362))
([5944127](5944127))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: chingor13 <chingor@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants