From 0f654264767df17e3c7e43aff1237fc838947d71 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 24 Mar 2026 15:38:53 -0400 Subject: [PATCH 1/4] fix: use dynamic tracer name instead of hardcoded gax-java Refactored SpanTracerFactory to dynamically initialize tracers with metadata containing the artifact name and version when withContext is invoked. Handled the null-tracer fallback strategy by returning a BaseApiTracer when tracer is null. Updated unit and integration tests. --- .../api/gax/tracing/SpanTracerFactory.java | 28 +++++++-- .../gax/tracing/SpanTracerFactoryTest.java | 47 ++++++++++++--- .../showcase/v1beta1/it/ITOtelTracing.java | 60 +++++++------------ 3 files changed, 84 insertions(+), 51 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java index f891795564f9..0c3e5b6e0c9f 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java @@ -32,6 +32,7 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; +import com.google.api.gax.rpc.LibraryMetadata; import com.google.common.annotations.VisibleForTesting; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Tracer; @@ -48,12 +49,12 @@ @InternalApi public class SpanTracerFactory implements ApiTracerFactory { private final Tracer tracer; - + private final OpenTelemetry openTelemetry; private final ApiTracerContext apiTracerContext; /** Creates a SpanTracerFactory */ public SpanTracerFactory(OpenTelemetry openTelemetry) { - this(openTelemetry.getTracer("gax-java"), ApiTracerContext.empty()); + this(openTelemetry, null, ApiTracerContext.empty()); } /** @@ -62,13 +63,18 @@ public SpanTracerFactory(OpenTelemetry openTelemetry) { * internally. */ @VisibleForTesting - SpanTracerFactory(Tracer tracer, ApiTracerContext apiTracerContext) { + SpanTracerFactory(OpenTelemetry openTelemetry, Tracer tracer, ApiTracerContext apiTracerContext) { + this.openTelemetry = openTelemetry; this.tracer = tracer; this.apiTracerContext = apiTracerContext; } @Override public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { + if (tracer == null) { + // Return a no-op tracer if withContext hasn't been called to initialize the tracer properly + return new BaseApiTracer(); + } // TODO(diegomarquezp): this is a placeholder for span names and will be adjusted as the // feature is developed. String attemptSpanName = spanName.getClientName() + "/" + spanName.getMethodName() + "/attempt"; @@ -78,6 +84,10 @@ public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType op @Override public ApiTracer newTracer(ApiTracer parent, ApiTracerContext apiTracerContext) { + if (tracer == null) { + // Return a no-op tracer if withContext hasn't been called to initialize the tracer properly + return new BaseApiTracer(); + } ApiTracerContext mergedContext = this.apiTracerContext.merge(apiTracerContext); return new SpanTracer(tracer, mergedContext); } @@ -87,8 +97,18 @@ public ApiTracerContext getApiTracerContext() { return apiTracerContext; } + /** + * Returns a new SpanTracerFactory with the provided context. The Tracer is re-initialized using + * the artifact name and version from the library metadata. + */ @Override public ApiTracerFactory withContext(ApiTracerContext context) { - return new SpanTracerFactory(tracer, apiTracerContext.merge(context)); + ApiTracerContext mergedContext = this.apiTracerContext.merge(context); + LibraryMetadata metadata = mergedContext.libraryMetadata(); + // Using io.opentelemetry.api.trace.Tracer.getTracer(String instrumentationScopeName, String + // instrumentationScopeVersion) + // This allows us to specify both the artifact name and version for better observability. + Tracer newTracer = openTelemetry.getTracer(metadata.artifactName(), metadata.version()); + return new SpanTracerFactory(openTelemetry, newTracer, mergedContext); } } diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerFactoryTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerFactoryTest.java index 6054c67ca733..e3c815131762 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerFactoryTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerFactoryTest.java @@ -33,6 +33,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -41,6 +42,7 @@ import com.google.api.gax.rpc.LibraryMetadata; import com.google.api.gax.tracing.ApiTracerContext.Transport; import com.google.api.gax.tracing.ApiTracerFactory.OperationType; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; @@ -54,15 +56,19 @@ import org.mockito.ArgumentCaptor; class SpanTracerFactoryTest { + private OpenTelemetry openTelemetry; private Tracer tracer; private SpanBuilder spanBuilder; private Span span; @BeforeEach void setUp() { + openTelemetry = mock(OpenTelemetry.class); tracer = mock(Tracer.class); spanBuilder = mock(SpanBuilder.class); span = mock(Span.class); + when(openTelemetry.getTracer(nullable(String.class), nullable(String.class))) + .thenReturn(tracer); when(tracer.spanBuilder(anyString())).thenReturn(spanBuilder); when(spanBuilder.setSpanKind(any())).thenReturn(spanBuilder); when(spanBuilder.setAllAttributes(any(Attributes.class))).thenReturn(spanBuilder); @@ -72,7 +78,8 @@ void setUp() { @ParameterizedTest @ValueSource(booleans = {false, true}) void testNewTracer_createsSpanTracer(boolean useContext) { - SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + SpanTracerFactory factory = + new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty()); ApiTracer tracerInstance; if (useContext) { ApiTracerContext context = @@ -92,7 +99,8 @@ void testNewTracer_createsSpanTracer(boolean useContext) { @ParameterizedTest @ValueSource(booleans = {false, true}) void testNewTracer_addsAttributes(boolean useContext) { - ApiTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + ApiTracerFactory factory = + new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty()); factory = factory.withContext( ApiTracerContext.newBuilder() @@ -132,7 +140,8 @@ void testWithContext_addsInferredAttributes(boolean useContext) { .setServerAddress("example.com") .build(); - SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + SpanTracerFactory factory = + new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty()); ApiTracerFactory factoryWithContext = factory.withContext(context); ApiTracer tracerInstance; @@ -166,7 +175,8 @@ void testWithContext_addsInferredAttributes(boolean useContext) { void testWithContext_noEndpointContext_doesNotAddServerAddressAttribute(boolean useContext) { ApiTracerContext context = ApiTracerContext.empty(); - SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + SpanTracerFactory factory = + new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty()); ApiTracerFactory factoryWithContext = factory.withContext(context); ApiTracer tracerInstance; @@ -203,7 +213,8 @@ void testNewTracer_withContext_grpc_usesFullMethodName() { .setLibraryMetadata(LibraryMetadata.empty()) .build(); - SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + SpanTracerFactory factory = + new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty()); ApiTracer tracerInstance = factory.newTracer(null, context); tracerInstance.attemptStarted(null, 1); @@ -229,7 +240,8 @@ void testNewTracer_withContext_http_usesHttpMethodAndPathTemplate( .setLibraryMetadata(LibraryMetadata.empty()) .build(); - SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + SpanTracerFactory factory = + new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty()); ApiTracer tracerInstance = factory.newTracer(null, context); tracerInstance.attemptStarted(null, 1); @@ -246,7 +258,8 @@ void testNewTracer_withContext_http_noHttpMethodOrPathTemplate_usesFullMethodNam .setLibraryMetadata(LibraryMetadata.empty()) .build(); - SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + SpanTracerFactory factory = + new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty()); ApiTracer tracerInstance = factory.newTracer(null, context); tracerInstance.attemptStarted(null, 1); @@ -256,7 +269,8 @@ void testNewTracer_withContext_http_noHttpMethodOrPathTemplate_usesFullMethodNam @Test void testNewTracer_withSpanName_usesPlaceholder() { - SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + SpanTracerFactory factory = + new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty()); ApiTracer tracerInstance = factory.newTracer(null, SpanName.of("Service", "Method"), OperationType.Unary); @@ -272,7 +286,7 @@ void testNewTracer_mergesFactoryContext() { .setServerAddress("factory-address") .setLibraryMetadata(LibraryMetadata.empty()) .build(); - SpanTracerFactory factory = new SpanTracerFactory(tracer, apiTracerContext); + SpanTracerFactory factory = new SpanTracerFactory(openTelemetry, tracer, apiTracerContext); ApiTracerContext callContext = ApiTracerContext.newBuilder() @@ -293,4 +307,19 @@ void testNewTracer_mergesFactoryContext() { assertThat(attributes.asMap()) .containsEntry(AttributeKey.stringKey("rpc.method"), "Service/Method"); } + + @Test + void testNoOpWhenTracerNull() { + SpanTracerFactory factory = + new SpanTracerFactory(openTelemetry, null, ApiTracerContext.empty()); + + ApiTracer tracerInstance = + factory.newTracer(null, SpanName.of("Service", "Method"), OperationType.Unary); + + assertThat(tracerInstance).isInstanceOf(BaseApiTracer.class); + + ApiTracer tracerInstance2 = factory.newTracer(null, ApiTracerContext.empty()); + + assertThat(tracerInstance2).isInstanceOf(BaseApiTracer.class); + } } diff --git a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java index ddaff08896b1..f7acca733ac5 100644 --- a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java +++ b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java @@ -45,8 +45,6 @@ import com.google.showcase.v1beta1.EchoClient; import com.google.showcase.v1beta1.EchoRequest; import com.google.showcase.v1beta1.EchoSettings; -import com.google.showcase.v1beta1.GetUserRequest; -import com.google.showcase.v1beta1.IdentityClient; import com.google.showcase.v1beta1.it.util.TestClientInitializer; import com.google.showcase.v1beta1.stub.EchoStub; import com.google.showcase.v1beta1.stub.EchoStubSettings; @@ -94,24 +92,20 @@ void tearDown() { } @Test - void testTracing_successfulIdentityGetUser_grpc() throws Exception { + void testTracing_successfulEcho_grpc() throws Exception { SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk); - try (IdentityClient client = - TestClientInitializer.createGrpcIdentityClientOpentelemetry(tracingFactory)) { + try (EchoClient client = + TestClientInitializer.createGrpcEchoClientOpentelemetry(tracingFactory)) { - try { - client.getUser(GetUserRequest.newBuilder().setName("users/test-user").build()); - } catch (Exception e) { - // Ignored, the showcase server may not have this user, but trace is still generated. - } + client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); List spans = spanExporter.getFinishedSpanItems(); assertThat(spans).isNotEmpty(); SpanData attemptSpan = spans.stream() - .filter(span -> span.getName().equals("google.showcase.v1beta1.Identity/GetUser")) + .filter(span -> span.getName().equals("google.showcase.v1beta1.Echo/Echo")) .findFirst() .orElseThrow(() -> new AssertionError("Incorrect span name")); assertThat(attemptSpan.getKind()).isEqualTo(SpanKind.CLIENT); @@ -149,46 +143,38 @@ void testTracing_successfulIdentityGetUser_grpc() throws Exception { attemptSpan .getAttributes() .get(AttributeKey.stringKey(ObservabilityAttributes.GRPC_RPC_METHOD_ATTRIBUTE))) - .isEqualTo("google.showcase.v1beta1.Identity/GetUser"); + .isEqualTo("google.showcase.v1beta1.Echo/Echo"); + assertThat(attemptSpan.getInstrumentationScopeInfo().getName()).isEqualTo(SHOWCASE_ARTIFACT); // {x-version-update-start:gapic-showcase:current} assertThat( attemptSpan .getAttributes() .get(AttributeKey.stringKey(ObservabilityAttributes.VERSION_ATTRIBUTE))) .isEqualTo("0.0.0-SNAPSHOT"); + assertThat(attemptSpan.getInstrumentationScopeInfo().getVersion()) + .isEqualTo("0.0.0-SNAPSHOT"); // {x-version-update-end} - assertThat( - attemptSpan - .getAttributes() - .get( - AttributeKey.stringKey( - ObservabilityAttributes.DESTINATION_RESOURCE_ID_ATTRIBUTE))) - .isEqualTo("users/test-user"); } } @Test - void testTracing_successfulIdentityGetUser_httpjson() throws Exception { + void testTracing_successfulEcho_httpjson() throws Exception { SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk); - try (IdentityClient client = - TestClientInitializer.createHttpJsonIdentityClientOpentelemetry(tracingFactory)) { + try (EchoClient client = + TestClientInitializer.createHttpJsonEchoClientOpentelemetry(tracingFactory)) { - try { - client.getUser(GetUserRequest.newBuilder().setName("users/test-user").build()); - } catch (Exception e) { - // Ignored, the showcase server may not have this user, but trace is still generated. - } + client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); List spans = spanExporter.getFinishedSpanItems(); assertThat(spans).isNotEmpty(); SpanData attemptSpan = spans.stream() - .filter(span -> span.getName().equals("GET v1beta1/{name=users/*}")) + .filter(span -> span.getName().equals("POST v1beta1/echo:echo")) .findFirst() .orElseThrow( - () -> new AssertionError("Attempt span 'GET v1beta1/{name=users/*}' not found")); + () -> new AssertionError("Attempt span 'POST v1beta1/echo:echo' not found")); assertThat(attemptSpan.getKind()).isEqualTo(SpanKind.CLIENT); assertThat( attemptSpan @@ -219,19 +205,17 @@ void testTracing_successfulIdentityGetUser_httpjson() throws Exception { attemptSpan .getAttributes() .get(AttributeKey.stringKey(ObservabilityAttributes.HTTP_METHOD_ATTRIBUTE))) - .isEqualTo("GET"); + .isEqualTo("POST"); assertThat( attemptSpan .getAttributes() .get(AttributeKey.stringKey(ObservabilityAttributes.HTTP_URL_TEMPLATE_ATTRIBUTE))) - .isEqualTo("v1beta1/{name=users/*}"); - assertThat( - attemptSpan - .getAttributes() - .get( - AttributeKey.stringKey( - ObservabilityAttributes.DESTINATION_RESOURCE_ID_ATTRIBUTE))) - .isEqualTo("users/test-user"); + .isEqualTo("v1beta1/echo:echo"); + assertThat(attemptSpan.getInstrumentationScopeInfo().getName()).isEqualTo(SHOWCASE_ARTIFACT); + // {x-version-update-start:gapic-showcase:current} + assertThat(attemptSpan.getInstrumentationScopeInfo().getVersion()) + .isEqualTo("0.0.0-SNAPSHOT"); + // {x-version-update-end} } } From 294a121537d057c8a2ab57a86f8e5dd584e230de Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 24 Mar 2026 16:12:40 -0400 Subject: [PATCH 2/4] Apply suggestion from @diegomarquezp --- .../java/com/google/showcase/v1beta1/it/ITOtelTracing.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java index f7acca733ac5..1e665f3a35a1 100644 --- a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java +++ b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java @@ -212,10 +212,6 @@ void testTracing_successfulEcho_httpjson() throws Exception { .get(AttributeKey.stringKey(ObservabilityAttributes.HTTP_URL_TEMPLATE_ATTRIBUTE))) .isEqualTo("v1beta1/echo:echo"); assertThat(attemptSpan.getInstrumentationScopeInfo().getName()).isEqualTo(SHOWCASE_ARTIFACT); - // {x-version-update-start:gapic-showcase:current} - assertThat(attemptSpan.getInstrumentationScopeInfo().getVersion()) - .isEqualTo("0.0.0-SNAPSHOT"); - // {x-version-update-end} } } From 86a622295c9db735376678fd0184c259e03946a8 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 24 Mar 2026 16:13:22 -0400 Subject: [PATCH 3/4] Apply suggestion from @diegomarquezp --- .../com/google/showcase/v1beta1/it/ITOtelTracing.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java index 1e665f3a35a1..bc11b5b15332 100644 --- a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java +++ b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java @@ -145,14 +145,6 @@ void testTracing_successfulEcho_grpc() throws Exception { .get(AttributeKey.stringKey(ObservabilityAttributes.GRPC_RPC_METHOD_ATTRIBUTE))) .isEqualTo("google.showcase.v1beta1.Echo/Echo"); assertThat(attemptSpan.getInstrumentationScopeInfo().getName()).isEqualTo(SHOWCASE_ARTIFACT); - // {x-version-update-start:gapic-showcase:current} - assertThat( - attemptSpan - .getAttributes() - .get(AttributeKey.stringKey(ObservabilityAttributes.VERSION_ATTRIBUTE))) - .isEqualTo("0.0.0-SNAPSHOT"); - assertThat(attemptSpan.getInstrumentationScopeInfo().getVersion()) - .isEqualTo("0.0.0-SNAPSHOT"); // {x-version-update-end} } } From dc583bc5b89475b99755728ae45bf05b4b664311 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 24 Mar 2026 16:13:47 -0400 Subject: [PATCH 4/4] Apply suggestion from @diegomarquezp --- .../java/com/google/api/gax/tracing/SpanTracerFactory.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java index 0c3e5b6e0c9f..7171484ae405 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java @@ -105,9 +105,6 @@ public ApiTracerContext getApiTracerContext() { public ApiTracerFactory withContext(ApiTracerContext context) { ApiTracerContext mergedContext = this.apiTracerContext.merge(context); LibraryMetadata metadata = mergedContext.libraryMetadata(); - // Using io.opentelemetry.api.trace.Tracer.getTracer(String instrumentationScopeName, String - // instrumentationScopeVersion) - // This allows us to specify both the artifact name and version for better observability. Tracer newTracer = openTelemetry.getTracer(metadata.artifactName(), metadata.version()); return new SpanTracerFactory(openTelemetry, newTracer, mergedContext); }