From e01ca0675fd100b29ef44d615b69bc92cfa3790b Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 8 Jan 2026 13:37:43 -0500 Subject: [PATCH 01/17] feat(observability): add no-op implementation of tracing --- .../api/gax/tracing/TracingRecorder.java | 44 +++++++++++ .../google/api/gax/tracing/TracingTracer.java | 47 +++++++++++ .../api/gax/tracing/TracingTracerFactory.java | 55 +++++++++++++ .../google/api/gax/tracing/TracingUtils.java | 59 ++++++++++++++ .../gax/tracing/TracingTracerFactoryTest.java | 79 +++++++++++++++++++ .../api/gax/tracing/TracingTracerTest.java | 46 +++++++++++ .../api/gax/tracing/TracingUtilsTest.java | 56 +++++++++++++ .../showcase/v1beta1/it/ITOtelMetrics.java | 32 +++++++- 8 files changed, 414 insertions(+), 4 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingUtils.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerFactoryTest.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerTest.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingUtilsTest.java diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java new file mode 100644 index 0000000000..2d66129d2d --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java @@ -0,0 +1,44 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.tracing; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; + +/** + * Provides an interface for tracing recording. The implementer is expected to use an observability + * framework, e.g. OpenTelemetry. + */ +@BetaApi +@InternalApi +public interface TracingRecorder { + // Minimal working feature: noop implementation for now. +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java new file mode 100644 index 0000000000..4bd9d4e08a --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.tracing; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; + +/** + * This class computes generic traces that can be observed in the lifecycle of an RPC operation. + */ +@BetaApi +@InternalApi +public class TracingTracer extends BaseApiTracer { + private final TracingRecorder tracingRecorder; + + public TracingTracer(TracingRecorder tracingRecorder) { + this.tracingRecorder = tracingRecorder; + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java new file mode 100644 index 0000000000..85bddf37f6 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.tracing; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; + +/** + * A {@link ApiTracerFactory} to build instances of {@link TracingTracer}. + */ +@BetaApi +@InternalApi +public class TracingTracerFactory implements ApiTracerFactory { + private final TracingRecorder tracingRecorder; + + public TracingTracerFactory(TracingRecorder tracingRecorder) { + this.tracingRecorder = tracingRecorder; + } + + @Override + public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { + if (!TracingUtils.isTracingEnabled()) { + return BaseApiTracer.getInstance(); + } + return new TracingTracer(tracingRecorder); + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingUtils.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingUtils.java new file mode 100644 index 0000000000..161f93f558 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.tracing; + +import com.google.api.core.InternalApi; +import com.google.common.annotations.VisibleForTesting; + +/** Internal utility class to manage tracing configuration. */ +@InternalApi +public class TracingUtils { + static final String GOOGLE_CLOUD_ENABLE_TRACING = "GOOGLE_CLOUD_ENABLE_TRACING"; + + private TracingUtils() {} + + /** + * Returns true if tracing is enabled via the GOOGLE_CLOUD_ENABLE_TRACING environment variable or + * system property. + */ + public static boolean isTracingEnabled() { + String enableTracing = System.getProperty(GOOGLE_CLOUD_ENABLE_TRACING); + if (enableTracing == null) { + enableTracing = System.getenv(GOOGLE_CLOUD_ENABLE_TRACING); + } + return isTracingEnabled(enableTracing); + } + + @VisibleForTesting + static boolean isTracingEnabled(String envValue) { + return "true".equalsIgnoreCase(envValue); + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerFactoryTest.java new file mode 100644 index 0000000000..9169b14abd --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerFactoryTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.tracing; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + +import com.google.api.gax.tracing.ApiTracerFactory.OperationType; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class TracingTracerFactoryTest { + private TracingRecorder tracingRecorder; + private TracingTracerFactory factory; + private ApiTracer parent; + private SpanName spanName; + + @BeforeEach + void setUp() { + tracingRecorder = mock(TracingRecorder.class); + factory = new TracingTracerFactory(tracingRecorder); + parent = mock(ApiTracer.class); + spanName = mock(SpanName.class); + } + + @AfterEach + void tearDown() { + System.clearProperty("GOOGLE_CLOUD_ENABLE_TRACING"); + } + + @Test + void testNewTracer_enabled() { + System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "true"); + ApiTracer tracer = factory.newTracer(parent, spanName, OperationType.Unary); + assertThat(tracer).isInstanceOf(TracingTracer.class); + } + + @Test + void testNewTracer_disabled() { + System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "false"); + ApiTracer tracer = factory.newTracer(parent, spanName, OperationType.Unary); + assertThat(tracer).isSameInstanceAs(BaseApiTracer.getInstance()); + } + + @Test + void testNewTracer_defaultDisabled() { + ApiTracer tracer = factory.newTracer(parent, spanName, OperationType.Unary); + assertThat(tracer).isSameInstanceAs(BaseApiTracer.getInstance()); + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerTest.java new file mode 100644 index 0000000000..f8506d9f06 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.tracing; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class TracingTracerTest { + + @Test + void testConstructor() { + TracingRecorder recorder = Mockito.mock(TracingRecorder.class); + TracingTracer tracer = new TracingTracer(recorder); + assertThat(tracer).isNotNull(); + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingUtilsTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingUtilsTest.java new file mode 100644 index 0000000000..9449fc571f --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingUtilsTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.tracing; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.jupiter.api.Test; + +class TracingUtilsTest { + + @Test + void testIsTracingEnabled_defaultToFalse() { + assertThat(TracingUtils.isTracingEnabled(null)).isFalse(); + } + + @Test + void testIsTracingEnabled_true() { + assertThat(TracingUtils.isTracingEnabled("true")).isTrue(); + assertThat(TracingUtils.isTracingEnabled("TRUE")).isTrue(); + } + + @Test + void testIsTracingEnabled_false() { + assertThat(TracingUtils.isTracingEnabled("false")).isFalse(); + assertThat(TracingUtils.isTracingEnabled("random")).isFalse(); + assertThat(TracingUtils.isTracingEnabled("")).isFalse(); + } +} diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index 06f46deee8..25300f9246 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2026 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -30,6 +30,7 @@ package com.google.showcase.v1beta1.it; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import com.google.api.client.http.javanet.NetHttpTransport; @@ -42,9 +43,7 @@ import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.gax.rpc.UnavailableException; -import com.google.api.gax.tracing.MetricsTracer; -import com.google.api.gax.tracing.MetricsTracerFactory; -import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder; +import com.google.api.gax.tracing.*; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -167,6 +166,8 @@ void setup() throws Exception { @AfterEach void cleanup() throws InterruptedException, IOException { + System.clearProperty("GOOGLE_CLOUD_ENABLE_TRACING"); + inMemoryMetricReader.close(); inMemoryMetricReader.shutdown(); @@ -919,4 +920,27 @@ void grpcOpenTelemetryImplementation_setSampledToLocalTracing_methodFullNameIsRe echoClient.close(); echoClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); } + + @Test + void testTracingFeatureFlag() { + // Test tracing disabled + System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "false"); + TracingTracerFactory factory = new TracingTracerFactory(null); + ApiTracer tracer = + factory.newTracer( + BaseApiTracer.getInstance(), + SpanName.of("EchoClient", "Echo"), + ApiTracerFactory.OperationType.Unary); + assertThat(tracer).isNotInstanceOf(TracingTracer.class); + assertThat(tracer).isSameInstanceAs(BaseApiTracer.getInstance()); + + // Test tracing enabled + System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "true"); + tracer = + factory.newTracer( + BaseApiTracer.getInstance(), + SpanName.of("EchoClient", "Echo"), + ApiTracerFactory.OperationType.Unary); + assertThat(tracer).isInstanceOf(TracingTracer.class); + } } From d3f61d1c254c2a5ed44d6ec8530b16882bce0428 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 8 Jan 2026 13:52:11 -0500 Subject: [PATCH 02/17] chore: add otel tracing recorder --- .../tracing/OpenTelemetryTracingRecorder.java | 50 +++++++++++++++++++ .../showcase/v1beta1/it/ITOtelMetrics.java | 15 ++++-- 2 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java new file mode 100644 index 0000000000..91b403195b --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java @@ -0,0 +1,50 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.tracing; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; +import io.opentelemetry.api.OpenTelemetry; + +/** + * OpenTelemetry implementation of recording traces. + */ +@BetaApi +@InternalApi +public class OpenTelemetryTracingRecorder implements TracingRecorder { + private final OpenTelemetry openTelemetry; + private final String serviceName; + + public OpenTelemetryTracingRecorder(OpenTelemetry openTelemetry, String serviceName) { + this.openTelemetry = openTelemetry; + this.serviceName = serviceName; + } +} diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index 25300f9246..95bbb59575 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -144,13 +144,17 @@ public int getCount() { private OpenTelemetryMetricsRecorder createOtelMetricsRecorder( InMemoryMetricReader inMemoryMetricReader) { SdkMeterProvider sdkMeterProvider = - SdkMeterProvider.builder().registerMetricReader(inMemoryMetricReader).build(); - - OpenTelemetry openTelemetry = - OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build(); + SdkMeterProvider.builder().registerMetricReader(inMemoryMetricReader).build(); + OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build(); return new OpenTelemetryMetricsRecorder(openTelemetry, SERVICE_NAME); } + private OpenTelemetryTracingRecorder createOtelTracingRecorder() { + SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder().build(); + OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build(); + return new OpenTelemetryTracingRecorder(openTelemetry, SERVICE_NAME); + } + @BeforeEach void setup() throws Exception { inMemoryMetricReader = InMemoryMetricReader.create(); @@ -925,7 +929,8 @@ void grpcOpenTelemetryImplementation_setSampledToLocalTracing_methodFullNameIsRe void testTracingFeatureFlag() { // Test tracing disabled System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "false"); - TracingTracerFactory factory = new TracingTracerFactory(null); + TracingRecorder recorder = createOtelTracingRecorder(); + TracingTracerFactory factory = new TracingTracerFactory(recorder); ApiTracer tracer = factory.newTracer( BaseApiTracer.getInstance(), From cf98e86e6588b533e3033a8699c9f2c0cc2c8a1b Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 8 Jan 2026 13:58:29 -0500 Subject: [PATCH 03/17] chore: add javadocs --- .../api/gax/tracing/OpenTelemetryTracingRecorder.java | 9 ++++++++- .../java/com/google/api/gax/tracing/TracingRecorder.java | 4 +++- .../java/com/google/api/gax/tracing/TracingTracer.java | 4 +++- .../com/google/api/gax/tracing/TracingTracerFactory.java | 5 +++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java index 91b403195b..6f32088cb0 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java @@ -35,7 +35,8 @@ import io.opentelemetry.api.OpenTelemetry; /** - * OpenTelemetry implementation of recording traces. + * OpenTelemetry implementation of recording traces. This implementation collections the + * measurements related to the lifecyle of an RPC. */ @BetaApi @InternalApi @@ -43,6 +44,12 @@ public class OpenTelemetryTracingRecorder implements TracingRecorder { private final OpenTelemetry openTelemetry; private final String serviceName; + /** + * Creates a new instance of OpenTelemetryTracingRecorder. + * + * @param openTelemetry OpenTelemetry instance + * @param serviceName Service Name + */ public OpenTelemetryTracingRecorder(OpenTelemetry openTelemetry, String serviceName) { this.openTelemetry = openTelemetry; this.serviceName = serviceName; diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java index 2d66129d2d..afb7ff6078 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java @@ -35,7 +35,9 @@ /** * Provides an interface for tracing recording. The implementer is expected to use an observability - * framework, e.g. OpenTelemetry. + * framework, e.g. OpenTelemetry. There should be only one instance of TracingRecorder per client, + * all the methods in this class are expected to be called from multiple threads, hence the + * implementation must be thread safe. */ @BetaApi @InternalApi diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java index 4bd9d4e08a..c5ea38a9b1 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java @@ -34,7 +34,9 @@ import com.google.api.core.InternalApi; /** - * This class computes generic traces that can be observed in the lifecycle of an RPC operation. + * This class computes generic traces that can be observed in the lifecycle of an RPC operation. The + * responsibility of recording traces should delegate to {@link TracingRecorder}, hence this class + * should not have any knowledge about the observability framework used for tracing recording. */ @BetaApi @InternalApi diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java index 85bddf37f6..8ea6a3e6b6 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java @@ -35,6 +35,11 @@ /** * A {@link ApiTracerFactory} to build instances of {@link TracingTracer}. + * + *

This class wraps the {@link TracingRecorder} and pass it to {@link TracingTracer}. It will be + * used to record traces in {@link TracingTracer}. + * + *

This class is expected to be initialized once during client initialization. */ @BetaApi @InternalApi From 523c3011e8d270064a4961639dcfc73e54349392 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 8 Jan 2026 14:09:07 -0500 Subject: [PATCH 04/17] chore: adjust tests and conform more closely to metrics counterpart. --- .../api/gax/tracing/TracingTracerFactory.java | 17 ++++++++++++++++- .../showcase/v1beta1/it/ITOtelMetrics.java | 8 +++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java index 8ea6a3e6b6..750947b67d 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java @@ -32,6 +32,8 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; +import com.google.common.collect.ImmutableMap; +import java.util.Map; /** * A {@link ApiTracerFactory} to build instances of {@link TracingTracer}. @@ -44,10 +46,23 @@ @BetaApi @InternalApi public class TracingTracerFactory implements ApiTracerFactory { - private final TracingRecorder tracingRecorder; + protected TracingRecorder tracingRecorder; + /** Mapping of client attributes that are set for every TracingTracer */ + private final Map attributes; + + /** Creates a TracingTracerFactory with no additional client level attributes. */ public TracingTracerFactory(TracingRecorder tracingRecorder) { + this(tracingRecorder, ImmutableMap.of()); + } + + /** + * Pass in a Map of client level attributes which will be added to every single TracingTracer + * created from the ApiTracerFactory. + */ + public TracingTracerFactory(TracingRecorder tracingRecorder, Map attributes) { this.tracingRecorder = tracingRecorder; + this.attributes = ImmutableMap.copyOf(attributes); } @Override diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index 95bbb59575..f1fad60dca 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -144,14 +144,16 @@ public int getCount() { private OpenTelemetryMetricsRecorder createOtelMetricsRecorder( InMemoryMetricReader inMemoryMetricReader) { SdkMeterProvider sdkMeterProvider = - SdkMeterProvider.builder().registerMetricReader(inMemoryMetricReader).build(); - OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build(); + SdkMeterProvider.builder().registerMetricReader(inMemoryMetricReader).build(); + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build(); return new OpenTelemetryMetricsRecorder(openTelemetry, SERVICE_NAME); } private OpenTelemetryTracingRecorder createOtelTracingRecorder() { SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder().build(); - OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build(); + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build(); return new OpenTelemetryTracingRecorder(openTelemetry, SERVICE_NAME); } From d9534bd344dcad3730ea7168391cd03a0bf5e85a Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 8 Jan 2026 14:17:43 -0500 Subject: [PATCH 05/17] chore: expand imports in otel it --- .../google/showcase/v1beta1/it/ITOtelMetrics.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index f1fad60dca..ca6b8e2914 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -43,7 +43,17 @@ import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.gax.rpc.UnavailableException; -import com.google.api.gax.tracing.*; +import com.google.api.gax.tracing.ApiTracer; +import com.google.api.gax.tracing.ApiTracerFactory; +import com.google.api.gax.tracing.BaseApiTracer; +import com.google.api.gax.tracing.MetricsTracer; +import com.google.api.gax.tracing.MetricsTracerFactory; +import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder; +import com.google.api.gax.tracing.OpenTelemetryTracingRecorder; +import com.google.api.gax.tracing.SpanName; +import com.google.api.gax.tracing.TracingRecorder; +import com.google.api.gax.tracing.TracingTracer; +import com.google.api.gax.tracing.TracingTracerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; From 196d591390e93f584e2d02ba4f8888e33c5ff5ef Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 8 Jan 2026 14:18:02 -0500 Subject: [PATCH 06/17] Update gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../google/api/gax/tracing/OpenTelemetryTracingRecorder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java index 6f32088cb0..440e3ec676 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java @@ -35,7 +35,7 @@ import io.opentelemetry.api.OpenTelemetry; /** - * OpenTelemetry implementation of recording traces. This implementation collections the + * OpenTelemetry implementation of recording traces. This implementation collects the * measurements related to the lifecyle of an RPC. */ @BetaApi From ea075d35ab910ad7a4bfe537524b429533d8486b Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 8 Jan 2026 14:21:28 -0500 Subject: [PATCH 07/17] test: use tracer provider in ItOtelMetrics --- .../java/com/google/showcase/v1beta1/it/ITOtelMetrics.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index ca6b8e2914..f601163218 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -92,6 +92,8 @@ import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.stream.Collectors; + +import io.opentelemetry.sdk.trace.SdkTracerProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -161,9 +163,9 @@ private OpenTelemetryMetricsRecorder createOtelMetricsRecorder( } private OpenTelemetryTracingRecorder createOtelTracingRecorder() { - SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder().build(); + SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder().build(); OpenTelemetry openTelemetry = - OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build(); + OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).build(); return new OpenTelemetryTracingRecorder(openTelemetry, SERVICE_NAME); } From 2c2697101d21455040ce92130c90b5750c049f22 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 8 Jan 2026 14:33:38 -0500 Subject: [PATCH 08/17] chore: format --- .../google/api/gax/tracing/OpenTelemetryTracingRecorder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java index 440e3ec676..9d3409c3bc 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java @@ -35,8 +35,8 @@ import io.opentelemetry.api.OpenTelemetry; /** - * OpenTelemetry implementation of recording traces. This implementation collects the - * measurements related to the lifecyle of an RPC. + * OpenTelemetry implementation of recording traces. This implementation collects the measurements + * related to the lifecyle of an RPC. */ @BetaApi @InternalApi From 1a76c29247e6b9cfaca1b69bf6e9bcdcc6387a34 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Thu, 8 Jan 2026 19:41:36 +0000 Subject: [PATCH 09/17] chore: generate libraries at Thu Jan 8 19:39:27 UTC 2026 --- .../java/com/google/showcase/v1beta1/it/ITOtelMetrics.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index f601163218..8fb7348851 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -81,6 +81,7 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.data.PointData; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.sdk.trace.SdkTracerProvider; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -92,8 +93,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.stream.Collectors; - -import io.opentelemetry.sdk.trace.SdkTracerProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; From 8a0f855cbaa6f6a43a4db10078834f776f587481 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Fri, 9 Jan 2026 16:10:17 -0500 Subject: [PATCH 10/17] chore: add dummy tracing metric --- .../api/gax/tracing/TracingRecorder.java | 10 ++- .../google/api/gax/tracing/TracingTracer.java | 23 +++++ .../api/gax/tracing/TracingTracerFactory.java | 4 +- .../showcase/v1beta1/it/ITOtelMetrics.java | 32 ------- .../showcase/v1beta1/it/ITOtelTracing.java | 89 +++++++++++++++++++ 5 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java index afb7ff6078..2aa932bb0e 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java @@ -32,6 +32,7 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; +import java.util.Map; /** * Provides an interface for tracing recording. The implementer is expected to use an observability @@ -42,5 +43,12 @@ @BetaApi @InternalApi public interface TracingRecorder { - // Minimal working feature: noop implementation for now. + + /** + * Represents a low-level network span (T4) and serves as a placeholder for further development. + * This represents a single network request/attempt. + * + * @param attributes Map of the attributes to store on the span. + */ + default void recordLowLevelNetworkSpan(Map attributes) {} } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java index c5ea38a9b1..870216a63f 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java @@ -32,6 +32,8 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; +import java.util.HashMap; +import java.util.Map; /** * This class computes generic traces that can be observed in the lifecycle of an RPC operation. The @@ -42,8 +44,29 @@ @InternalApi public class TracingTracer extends BaseApiTracer { private final TracingRecorder tracingRecorder; + private final Map attributes = new HashMap<>(); public TracingTracer(TracingRecorder tracingRecorder) { this.tracingRecorder = tracingRecorder; } + + @Override + public void attemptStarted(Object request, int attemptNumber) { + // temporary dummy trace to enable further development + tracingRecorder.recordLowLevelNetworkSpan(attributes); + } + + /** + * Add attributes that will be attached to all spans. + */ + public void addAttributes(String key, String value) { + attributes.put(key, value); + } + + /** + * Add attributes that will be attached to all spans. + */ + public void addAttributes(Map attributes) { + this.attributes.putAll(attributes); + } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java index 750947b67d..96a562b79b 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java @@ -70,6 +70,8 @@ public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType op if (!TracingUtils.isTracingEnabled()) { return BaseApiTracer.getInstance(); } - return new TracingTracer(tracingRecorder); + TracingTracer tracingTracer = new TracingTracer(tracingRecorder); + attributes.forEach(tracingTracer::addAttributes); + return tracingTracer; } } diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index f601163218..972b9175e0 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -30,7 +30,6 @@ package com.google.showcase.v1beta1.it; -import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import com.google.api.client.http.javanet.NetHttpTransport; @@ -43,17 +42,10 @@ import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.gax.rpc.UnavailableException; -import com.google.api.gax.tracing.ApiTracer; -import com.google.api.gax.tracing.ApiTracerFactory; -import com.google.api.gax.tracing.BaseApiTracer; import com.google.api.gax.tracing.MetricsTracer; import com.google.api.gax.tracing.MetricsTracerFactory; import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder; import com.google.api.gax.tracing.OpenTelemetryTracingRecorder; -import com.google.api.gax.tracing.SpanName; -import com.google.api.gax.tracing.TracingRecorder; -import com.google.api.gax.tracing.TracingTracer; -import com.google.api.gax.tracing.TracingTracerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -938,28 +930,4 @@ void grpcOpenTelemetryImplementation_setSampledToLocalTracing_methodFullNameIsRe echoClient.close(); echoClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); } - - @Test - void testTracingFeatureFlag() { - // Test tracing disabled - System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "false"); - TracingRecorder recorder = createOtelTracingRecorder(); - TracingTracerFactory factory = new TracingTracerFactory(recorder); - ApiTracer tracer = - factory.newTracer( - BaseApiTracer.getInstance(), - SpanName.of("EchoClient", "Echo"), - ApiTracerFactory.OperationType.Unary); - assertThat(tracer).isNotInstanceOf(TracingTracer.class); - assertThat(tracer).isSameInstanceAs(BaseApiTracer.getInstance()); - - // Test tracing enabled - System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "true"); - tracer = - factory.newTracer( - BaseApiTracer.getInstance(), - SpanName.of("EchoClient", "Echo"), - ApiTracerFactory.OperationType.Unary); - assertThat(tracer).isInstanceOf(TracingTracer.class); - } } diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java new file mode 100644 index 0000000000..5c5bb4bed6 --- /dev/null +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java @@ -0,0 +1,89 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.showcase.v1beta1.it; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.gax.tracing.ApiTracer; +import com.google.api.gax.tracing.ApiTracerFactory; +import com.google.api.gax.tracing.BaseApiTracer; +import com.google.api.gax.tracing.SpanName; +import com.google.api.gax.tracing.TracingRecorder; +import com.google.api.gax.tracing.TracingTracer; +import com.google.api.gax.tracing.TracingTracerFactory; +import com.google.showcase.v1beta1.EchoClient; +import com.google.showcase.v1beta1.EchoRequest; +import com.google.showcase.v1beta1.it.util.TestClientInitializer; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +class ITOtelTracing { + + private static class DummyTracingRecorder implements TracingRecorder { + private boolean lowLevelSpanHit = false; + + @Override + public void recordLowLevelNetworkSpan(Map attributes) { + lowLevelSpanHit = true; + } + + public boolean wasLowLevelSpanHit() { + return lowLevelSpanHit; + } + } + + @Test + void testTracingFeatureFlag() { + System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "false"); + TracingTracerFactory factory = new TracingTracerFactory(new DummyTracingRecorder()); + ApiTracer tracer = factory.newTracer(BaseApiTracer.getInstance(), SpanName.of("EchoClient", "Echo"), ApiTracerFactory.OperationType.Unary); + assertThat(tracer).isNotInstanceOf(TracingTracer.class); + + System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "true"); + tracer = factory.newTracer(BaseApiTracer.getInstance(), SpanName.of("EchoClient", "Echo"), ApiTracerFactory.OperationType.Unary); + assertThat(tracer).isInstanceOf(TracingTracer.class); + System.clearProperty("GOOGLE_CLOUD_ENABLE_TRACING"); + } + + @Test + void testTracingTracer_recordsLowLevelNetworkSpan() throws Exception { + System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "true"); + DummyTracingRecorder recorder = new DummyTracingRecorder(); + TracingTracerFactory factory = new TracingTracerFactory(recorder); + try (EchoClient client = TestClientInitializer.createGrpcEchoClientOpentelemetry(factory)) { + client.echo(EchoRequest.newBuilder().setContent("test").build()); + assertThat(recorder.wasLowLevelSpanHit()).isTrue(); + } finally { + System.clearProperty("GOOGLE_CLOUD_ENABLE_TRACING"); + } + } +} \ No newline at end of file From dc3ed42f26b9bb0b5379dc5d876d3ac4d0f07578 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Fri, 9 Jan 2026 16:16:01 -0500 Subject: [PATCH 11/17] chore: refine tracing IT --- .../tracing/OpenTelemetryTracingRecorder.java | 22 ++++++- .../showcase/v1beta1/it/ITOtelMetrics.java | 9 --- .../showcase/v1beta1/it/ITOtelTracing.java | 59 ++++++++++++------- 3 files changed, 59 insertions(+), 31 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java index 9d3409c3bc..c97097d6c5 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java @@ -32,7 +32,11 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; +import com.google.api.gax.core.GaxProperties; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import java.util.Map; /** * OpenTelemetry implementation of recording traces. This implementation collects the measurements @@ -41,7 +45,8 @@ @BetaApi @InternalApi public class OpenTelemetryTracingRecorder implements TracingRecorder { - private final OpenTelemetry openTelemetry; + public static final String GAX_TRACER_NAME = "gax-java"; + private final Tracer tracer; private final String serviceName; /** @@ -51,7 +56,20 @@ public class OpenTelemetryTracingRecorder implements TracingRecorder { * @param serviceName Service Name */ public OpenTelemetryTracingRecorder(OpenTelemetry openTelemetry, String serviceName) { - this.openTelemetry = openTelemetry; + this.tracer = + openTelemetry + .tracerBuilder(GAX_TRACER_NAME) + .setInstrumentationVersion(GaxProperties.getGaxVersion()) + .build(); this.serviceName = serviceName; } + + @Override + public void recordLowLevelNetworkSpan(Map attributes) { + Span span = tracer.spanBuilder(serviceName + "/low-level-network-span").startSpan(); + if (attributes != null) { + attributes.forEach(span::setAttribute); + } + span.end(); + } } diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index 972b9175e0..08ef2afd58 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -45,7 +45,6 @@ import com.google.api.gax.tracing.MetricsTracer; import com.google.api.gax.tracing.MetricsTracerFactory; import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder; -import com.google.api.gax.tracing.OpenTelemetryTracingRecorder; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -85,7 +84,6 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import io.opentelemetry.sdk.trace.SdkTracerProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -154,13 +152,6 @@ private OpenTelemetryMetricsRecorder createOtelMetricsRecorder( return new OpenTelemetryMetricsRecorder(openTelemetry, SERVICE_NAME); } - private OpenTelemetryTracingRecorder createOtelTracingRecorder() { - SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder().build(); - OpenTelemetry openTelemetry = - OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).build(); - return new OpenTelemetryTracingRecorder(openTelemetry, SERVICE_NAME); - } - @BeforeEach void setup() throws Exception { inMemoryMetricReader = InMemoryMetricReader.create(); diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java index 5c5bb4bed6..193ab427c1 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java @@ -35,55 +35,74 @@ import com.google.api.gax.tracing.ApiTracer; import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.gax.tracing.BaseApiTracer; +import com.google.api.gax.tracing.OpenTelemetryTracingRecorder; import com.google.api.gax.tracing.SpanName; -import com.google.api.gax.tracing.TracingRecorder; import com.google.api.gax.tracing.TracingTracer; import com.google.api.gax.tracing.TracingTracerFactory; import com.google.showcase.v1beta1.EchoClient; import com.google.showcase.v1beta1.EchoRequest; import com.google.showcase.v1beta1.it.util.TestClientInitializer; -import java.util.Map; -import java.util.concurrent.TimeUnit; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class ITOtelTracing { + private static final String SERVICE_NAME = "ShowcaseTracingTest"; + private InMemorySpanExporter spanExporter; + private OpenTelemetryTracingRecorder tracingRecorder; - private static class DummyTracingRecorder implements TracingRecorder { - private boolean lowLevelSpanHit = false; - - @Override - public void recordLowLevelNetworkSpan(Map attributes) { - lowLevelSpanHit = true; - } + @BeforeEach + void setup() { + spanExporter = InMemorySpanExporter.create(); + SdkTracerProvider tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) + .build(); + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build(); + tracingRecorder = new OpenTelemetryTracingRecorder(openTelemetry, SERVICE_NAME); + } - public boolean wasLowLevelSpanHit() { - return lowLevelSpanHit; - } + @AfterEach + void tearDown() { + System.clearProperty("GOOGLE_CLOUD_ENABLE_TRACING"); } @Test void testTracingFeatureFlag() { System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "false"); - TracingTracerFactory factory = new TracingTracerFactory(new DummyTracingRecorder()); + TracingTracerFactory factory = new TracingTracerFactory(tracingRecorder); ApiTracer tracer = factory.newTracer(BaseApiTracer.getInstance(), SpanName.of("EchoClient", "Echo"), ApiTracerFactory.OperationType.Unary); assertThat(tracer).isNotInstanceOf(TracingTracer.class); System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "true"); tracer = factory.newTracer(BaseApiTracer.getInstance(), SpanName.of("EchoClient", "Echo"), ApiTracerFactory.OperationType.Unary); assertThat(tracer).isInstanceOf(TracingTracer.class); - System.clearProperty("GOOGLE_CLOUD_ENABLE_TRACING"); } @Test void testTracingTracer_recordsLowLevelNetworkSpan() throws Exception { System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "true"); - DummyTracingRecorder recorder = new DummyTracingRecorder(); - TracingTracerFactory factory = new TracingTracerFactory(recorder); + TracingTracerFactory factory = new TracingTracerFactory(tracingRecorder); try (EchoClient client = TestClientInitializer.createGrpcEchoClientOpentelemetry(factory)) { client.echo(EchoRequest.newBuilder().setContent("test").build()); - assertThat(recorder.wasLowLevelSpanHit()).isTrue(); - } finally { - System.clearProperty("GOOGLE_CLOUD_ENABLE_TRACING"); + + List spans = spanExporter.getFinishedSpanItems(); + assertThat(spans).isNotEmpty(); + + // Verify that the low-level network span was recorded. + // The OpenTelemetryTracingRecorder typically names this span using the service name prefix. + boolean foundLowLevelSpan = + spans.stream() + .anyMatch(span -> span.getName().equals(SERVICE_NAME + "/low-level-network-span")); + assertThat(foundLowLevelSpan).isTrue(); } } } \ No newline at end of file From 5287f82739bac0e0bd4be0b1aaaeba0ac4ef88cb Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Fri, 9 Jan 2026 17:15:04 -0500 Subject: [PATCH 12/17] feat: add composite api tracer --- .../com/google/api/gax/rpc/StubSettings.java | 20 ++- .../api/gax/tracing/CompositeApiTracer.java | 134 ++++++++++++++++++ .../tracing/CompositeApiTracerFactory.java | 61 ++++++++ .../google/api/gax/tracing/TracingTracer.java | 1 - .../api/gax/tracing/TracingTracerFactory.java | 2 +- .../showcase/v1beta1/it/ITOtelMetrics.java | 61 +++++++- .../showcase/v1beta1/it/ITOtelTracing.java | 92 ++++++++++-- 7 files changed, 349 insertions(+), 22 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracer.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracerFactory.java diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java index f97f808ca7..4f31bf4f9c 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java @@ -46,6 +46,11 @@ import com.google.api.gax.core.NoCredentialsProvider; import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.gax.tracing.BaseApiTracerFactory; +import com.google.api.gax.tracing.CompositeApiTracerFactory; +import com.google.api.gax.tracing.OpenTelemetryTracingRecorder; +import com.google.api.gax.tracing.TracingTracerFactory; +import com.google.api.gax.tracing.TracingUtils; +import io.opentelemetry.api.GlobalOpenTelemetry; import com.google.auth.oauth2.QuotaProjectIdProvider; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; @@ -104,13 +109,26 @@ protected StubSettings(Builder builder) { this.quotaProjectId = builder.quotaProjectId; this.streamWatchdogProvider = builder.streamWatchdogProvider; this.streamWatchdogCheckInterval = builder.streamWatchdogCheckInterval; - this.tracerFactory = builder.tracerFactory; + this.tracerFactory = autoConfigureTracerFactory(builder.tracerFactory); this.deprecatedExecutorProviderSet = builder.deprecatedExecutorProviderSet; this.gdchApiAudience = builder.gdchApiAudience; this.endpointContext = buildEndpointContext(builder); this.apiKey = builder.apiKey; } + private ApiTracerFactory autoConfigureTracerFactory(ApiTracerFactory factory) { + if (TracingUtils.isTracingEnabled()) { + ApiTracerFactory tracingFactory = + new TracingTracerFactory( + new OpenTelemetryTracingRecorder(GlobalOpenTelemetry.get(), getServiceName())); + if (factory instanceof BaseApiTracerFactory) { + return tracingFactory; + } + return CompositeApiTracerFactory.of(factory, tracingFactory); + } + return factory; + } + /** * Attempt to build the EndpointContext from the Builder based on all the user configurations * passed in. diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracer.java new file mode 100644 index 0000000000..4bbb08b195 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracer.java @@ -0,0 +1,134 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.tracing; + +import com.google.api.core.InternalApi; +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A composite implementation of {@link ApiTracer} that broadcasts events to multiple tracers. + */ +@InternalApi +public class CompositeApiTracer implements ApiTracer { + private final List tracers; + + public CompositeApiTracer(List tracers) { + this.tracers = ImmutableList.copyOf(tracers); + } + + @Override + public Scope inScope() { + List scopes = tracers.stream().map(ApiTracer::inScope).collect(Collectors.toList()); + return () -> scopes.forEach(Scope::close); + } + + @Override + public void operationSucceeded() { + tracers.forEach(ApiTracer::operationSucceeded); + } + + @Override + public void operationCancelled() { + tracers.forEach(ApiTracer::operationCancelled); + } + + @Override + public void operationFailed(Throwable error) { + tracers.forEach(t -> t.operationFailed(error)); + } + + @Override + public void connectionSelected(String id) { + tracers.forEach(t -> t.connectionSelected(id)); + } + + @Override + public void attemptStarted(int attemptNumber) { + tracers.forEach(t -> t.attemptStarted(attemptNumber)); + } + + @Override + public void attemptStarted(Object request, int attemptNumber) { + tracers.forEach(t -> t.attemptStarted(request, attemptNumber)); + } + + @Override + public void attemptSucceeded() { + tracers.forEach(ApiTracer::attemptSucceeded); + } + + @Override + public void attemptCancelled() { + tracers.forEach(ApiTracer::attemptCancelled); + } + + @Override + public void attemptFailedDuration(Throwable error, java.time.Duration delay) { + tracers.forEach(t -> t.attemptFailedDuration(error, delay)); + } + + @Override + public void attemptFailedRetriesExhausted(Throwable error) { + tracers.forEach(t -> t.attemptFailedRetriesExhausted(error)); + } + + @Override + public void attemptPermanentFailure(Throwable error) { + tracers.forEach(t -> t.attemptPermanentFailure(error)); + } + + @Override + public void lroStartFailed(Throwable error) { + tracers.forEach(t -> t.lroStartFailed(error)); + } + + @Override + public void lroStartSucceeded() { + tracers.forEach(ApiTracer::lroStartSucceeded); + } + + @Override + public void responseReceived() { + tracers.forEach(ApiTracer::responseReceived); + } + + @Override + public void requestSent() { + tracers.forEach(ApiTracer::requestSent); + } + + @Override + public void batchRequestSent(long elementCount, long requestSize) { + tracers.forEach(t -> t.batchRequestSent(elementCount, requestSize)); + } +} \ No newline at end of file diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracerFactory.java new file mode 100644 index 0000000000..ecb154e4ca --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracerFactory.java @@ -0,0 +1,61 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.tracing; + +import com.google.api.core.InternalApi; +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A composite implementation of {@link ApiTracerFactory} that creates {@link CompositeApiTracer}s. + */ +@InternalApi +public class CompositeApiTracerFactory implements ApiTracerFactory { + private final List factories; + + public CompositeApiTracerFactory(List factories) { + this.factories = ImmutableList.copyOf(factories); + } + + public static ApiTracerFactory of(ApiTracerFactory... factories) { + return new CompositeApiTracerFactory(ImmutableList.copyOf(factories)); + } + + @Override + public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { + List tracers = + factories.stream() + .map(f -> f.newTracer(parent, spanName, operationType)) + .collect(Collectors.toList()); + return new CompositeApiTracer(tracers); + } +} \ No newline at end of file diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java index 870216a63f..10d20c871b 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java @@ -52,7 +52,6 @@ public TracingTracer(TracingRecorder tracingRecorder) { @Override public void attemptStarted(Object request, int attemptNumber) { - // temporary dummy trace to enable further development tracingRecorder.recordLowLevelNetworkSpan(attributes); } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java index 96a562b79b..8c65252282 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java @@ -46,7 +46,7 @@ @BetaApi @InternalApi public class TracingTracerFactory implements ApiTracerFactory { - protected TracingRecorder tracingRecorder; + private final TracingRecorder tracingRecorder; /** Mapping of client attributes that are set for every TracingTracer */ private final Map attributes; diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index 08ef2afd58..b169fe86a5 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -30,6 +30,7 @@ package com.google.showcase.v1beta1.it; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import com.google.api.client.http.javanet.NetHttpTransport; @@ -42,9 +43,7 @@ import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.gax.rpc.UnavailableException; -import com.google.api.gax.tracing.MetricsTracer; -import com.google.api.gax.tracing.MetricsTracerFactory; -import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder; +import com.google.api.gax.tracing.*; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -72,6 +71,10 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.data.PointData; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -83,7 +86,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.stream.Collectors; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -921,4 +923,55 @@ void grpcOpenTelemetryImplementation_setSampledToLocalTracing_methodFullNameIsRe echoClient.close(); echoClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); } + + @Test + void testTracingFeatureFlag() throws Exception { + // Test tracing disabled + System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "false"); + TracingTracerFactory factory = new TracingTracerFactory(null); + ApiTracer tracer = + factory.newTracer( + BaseApiTracer.getInstance(), + SpanName.of("EchoClient", "Echo"), + ApiTracerFactory.OperationType.Unary); + assertThat(tracer).isNotInstanceOf(TracingTracer.class); + assertThat(tracer).isSameInstanceAs(BaseApiTracer.getInstance()); + + // Test tracing enabled + System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "true"); + tracer = + factory.newTracer( + BaseApiTracer.getInstance(), + SpanName.of("EchoClient", "Echo"), + ApiTracerFactory.OperationType.Unary); + assertThat(tracer).isInstanceOf(TracingTracer.class); + + // Verify dummy network trace + InMemorySpanExporter spanExporter = InMemorySpanExporter.create(); + SdkTracerProvider tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) + .build(); + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build(); + + OpenTelemetryTracingRecorder tracingRecorder = + new OpenTelemetryTracingRecorder(openTelemetry, SERVICE_NAME); + TracingTracerFactory tracingTracerFactory = new TracingTracerFactory(tracingRecorder); + + EchoClient grpcClient = TestClientInitializer.createGrpcEchoClientOpentelemetry(tracingTracerFactory); + try { + grpcClient.echo(EchoRequest.newBuilder().setContent("test").build()); + + List spans = spanExporter.getFinishedSpanItems(); + assertThat(spans).isNotEmpty(); + Optional t4Span = + spans.stream().filter(s -> s.getName().equals("LowLevelNetworkSpan")).findFirst(); + assertThat(t4Span.isPresent()).isTrue(); + assertThat(t4Span.get().getKind()).isEqualTo(io.opentelemetry.api.trace.SpanKind.CLIENT); + } finally { + grpcClient.close(); + grpcClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); + } + } } diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java index 193ab427c1..4c75238215 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java @@ -35,6 +35,8 @@ import com.google.api.gax.tracing.ApiTracer; import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.gax.tracing.BaseApiTracer; +import com.google.api.gax.tracing.MetricsTracerFactory; +import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder; import com.google.api.gax.tracing.OpenTelemetryTracingRecorder; import com.google.api.gax.tracing.SpanName; import com.google.api.gax.tracing.TracingTracer; @@ -42,12 +44,17 @@ import com.google.showcase.v1beta1.EchoClient; import com.google.showcase.v1beta1.EchoRequest; import com.google.showcase.v1beta1.it.util.TestClientInitializer; +import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import java.util.Collection; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -56,18 +63,31 @@ class ITOtelTracing { private static final String SERVICE_NAME = "ShowcaseTracingTest"; private InMemorySpanExporter spanExporter; + private InMemoryMetricReader metricReader; private OpenTelemetryTracingRecorder tracingRecorder; + private OpenTelemetryMetricsRecorder metricsRecorder; @BeforeEach void setup() { spanExporter = InMemorySpanExporter.create(); + metricReader = InMemoryMetricReader.create(); + SdkTracerProvider tracerProvider = SdkTracerProvider.builder() .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) .build(); + + SdkMeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + OpenTelemetry openTelemetry = - OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build(); + OpenTelemetrySdk.builder() + .setTracerProvider(tracerProvider) + .setMeterProvider(meterProvider) + .build(); + tracingRecorder = new OpenTelemetryTracingRecorder(openTelemetry, SERVICE_NAME); + metricsRecorder = new OpenTelemetryMetricsRecorder(openTelemetry, SERVICE_NAME); } @AfterEach @@ -76,33 +96,75 @@ void tearDown() { } @Test - void testTracingFeatureFlag() { + void testTracingFeatureFlag() throws Exception { + // Test tracing disabled System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "false"); - TracingTracerFactory factory = new TracingTracerFactory(tracingRecorder); - ApiTracer tracer = factory.newTracer(BaseApiTracer.getInstance(), SpanName.of("EchoClient", "Echo"), ApiTracerFactory.OperationType.Unary); - assertThat(tracer).isNotInstanceOf(TracingTracer.class); + try (EchoClient client = TestClientInitializer.createGrpcEchoClient()) { + ApiTracer tracer = + client + .getSettings() + .getStubSettings() + .getTracerFactory() + .newTracer( + BaseApiTracer.getInstance(), + SpanName.of("EchoClient", "Echo"), + ApiTracerFactory.OperationType.Unary); + assertThat(tracer).isNotInstanceOf(TracingTracer.class); + } + // Test tracing enabled System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "true"); - tracer = factory.newTracer(BaseApiTracer.getInstance(), SpanName.of("EchoClient", "Echo"), ApiTracerFactory.OperationType.Unary); - assertThat(tracer).isInstanceOf(TracingTracer.class); + // We need to register a global OpenTelemetry instance for auto-configuration to work + SdkTracerProvider tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(InMemorySpanExporter.create())) + .build(); + OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).buildAndRegisterGlobal(); + + try (EchoClient client = TestClientInitializer.createGrpcEchoClient()) { + ApiTracer tracer = + client + .getSettings() + .getStubSettings() + .getTracerFactory() + .newTracer( + BaseApiTracer.getInstance(), + SpanName.of("EchoClient", "Echo"), + ApiTracerFactory.OperationType.Unary); + assertThat(tracer).isInstanceOf(TracingTracer.class); + } } @Test - void testTracingTracer_recordsLowLevelNetworkSpan() throws Exception { + void testTracingAndMetrics_recordedSimultaneously() throws Exception { System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "true"); - TracingTracerFactory factory = new TracingTracerFactory(tracingRecorder); - try (EchoClient client = TestClientInitializer.createGrpcEchoClientOpentelemetry(factory)) { - client.echo(EchoRequest.newBuilder().setContent("test").build()); + + MetricsTracerFactory metricsFactory = new MetricsTracerFactory(metricsRecorder); + + try (EchoClient client = + TestClientInitializer.createGrpcEchoClient()) { + client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); List spans = spanExporter.getFinishedSpanItems(); assertThat(spans).isNotEmpty(); - - // Verify that the low-level network span was recorded. - // The OpenTelemetryTracingRecorder typically names this span using the service name prefix. boolean foundLowLevelSpan = spans.stream() .anyMatch(span -> span.getName().equals(SERVICE_NAME + "/low-level-network-span")); assertThat(foundLowLevelSpan).isTrue(); } + + spanExporter.reset(); + try (EchoClient client = + TestClientInitializer.createGrpcEchoClientOpentelemetry(metricsFactory)) { + client.echo(EchoRequest.newBuilder().setContent("metrics-test").build()); + + Collection metrics = metricReader.collectAllMetrics(); + List spans = spanExporter.getFinishedSpanItems(); + assertThat(metrics).isNotEmpty(); + assertThat(spans).isNotEmpty(); + boolean foundAttemptCount = + metrics.stream().anyMatch(m -> m.getName().equals(SERVICE_NAME + "/attempt_count")); + assertThat(foundAttemptCount).isTrue(); + } } -} \ No newline at end of file +} From ce2d85bf16c6b8954348fc71f2cefea51bd9d651 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Fri, 9 Jan 2026 17:31:54 -0500 Subject: [PATCH 13/17] test: use global config for tracing --- .../showcase/v1beta1/it/ITOtelTracing.java | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java index 4c75238215..02a35116a8 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java @@ -37,7 +37,6 @@ import com.google.api.gax.tracing.BaseApiTracer; import com.google.api.gax.tracing.MetricsTracerFactory; import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder; -import com.google.api.gax.tracing.OpenTelemetryTracingRecorder; import com.google.api.gax.tracing.SpanName; import com.google.api.gax.tracing.TracingTracer; import com.google.api.gax.tracing.TracingTracerFactory; @@ -45,7 +44,6 @@ import com.google.showcase.v1beta1.EchoRequest; import com.google.showcase.v1beta1.it.util.TestClientInitializer; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.data.MetricData; @@ -64,8 +62,8 @@ class ITOtelTracing { private static final String SERVICE_NAME = "ShowcaseTracingTest"; private InMemorySpanExporter spanExporter; private InMemoryMetricReader metricReader; - private OpenTelemetryTracingRecorder tracingRecorder; private OpenTelemetryMetricsRecorder metricsRecorder; + private OpenTelemetrySdk openTelemetrySdk; @BeforeEach void setup() { @@ -80,19 +78,22 @@ void setup() { SdkMeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(metricReader).build(); - OpenTelemetry openTelemetry = + openTelemetrySdk = OpenTelemetrySdk.builder() .setTracerProvider(tracerProvider) .setMeterProvider(meterProvider) - .build(); + .buildAndRegisterGlobal(); - tracingRecorder = new OpenTelemetryTracingRecorder(openTelemetry, SERVICE_NAME); - metricsRecorder = new OpenTelemetryMetricsRecorder(openTelemetry, SERVICE_NAME); + metricsRecorder = new OpenTelemetryMetricsRecorder(openTelemetrySdk, SERVICE_NAME); } @AfterEach void tearDown() { System.clearProperty("GOOGLE_CLOUD_ENABLE_TRACING"); + if (openTelemetrySdk != null) { + openTelemetrySdk.close(); + } + GlobalOpenTelemetry.resetForTest(); } @Test @@ -114,12 +115,6 @@ void testTracingFeatureFlag() throws Exception { // Test tracing enabled System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "true"); - // We need to register a global OpenTelemetry instance for auto-configuration to work - SdkTracerProvider tracerProvider = - SdkTracerProvider.builder() - .addSpanProcessor(SimpleSpanProcessor.create(InMemorySpanExporter.create())) - .build(); - OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).buildAndRegisterGlobal(); try (EchoClient client = TestClientInitializer.createGrpcEchoClient()) { ApiTracer tracer = @@ -141,15 +136,14 @@ void testTracingAndMetrics_recordedSimultaneously() throws Exception { MetricsTracerFactory metricsFactory = new MetricsTracerFactory(metricsRecorder); - try (EchoClient client = - TestClientInitializer.createGrpcEchoClient()) { + try (EchoClient client = TestClientInitializer.createGrpcEchoClient()) { client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); List spans = spanExporter.getFinishedSpanItems(); assertThat(spans).isNotEmpty(); boolean foundLowLevelSpan = spans.stream() - .anyMatch(span -> span.getName().equals(SERVICE_NAME + "/low-level-network-span")); + .anyMatch(span -> span.getName().equals("/low-level-network-span")); assertThat(foundLowLevelSpan).isTrue(); } From d6341cc7a030f68da2c506f3548eda484ee97b23 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Fri, 9 Jan 2026 17:33:48 -0500 Subject: [PATCH 14/17] chore: restore metrics it --- .../showcase/v1beta1/it/ITOtelMetrics.java | 65 ++----------------- 1 file changed, 5 insertions(+), 60 deletions(-) diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index b169fe86a5..06f46deee8 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -1,5 +1,5 @@ /* - * Copyright 2026 Google LLC + * Copyright 2024 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -30,7 +30,6 @@ package com.google.showcase.v1beta1.it; -import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import com.google.api.client.http.javanet.NetHttpTransport; @@ -43,7 +42,9 @@ import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.gax.rpc.UnavailableException; -import com.google.api.gax.tracing.*; +import com.google.api.gax.tracing.MetricsTracer; +import com.google.api.gax.tracing.MetricsTracerFactory; +import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -71,10 +72,6 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.data.PointData; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -149,6 +146,7 @@ private OpenTelemetryMetricsRecorder createOtelMetricsRecorder( InMemoryMetricReader inMemoryMetricReader) { SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder().registerMetricReader(inMemoryMetricReader).build(); + OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build(); return new OpenTelemetryMetricsRecorder(openTelemetry, SERVICE_NAME); @@ -169,8 +167,6 @@ void setup() throws Exception { @AfterEach void cleanup() throws InterruptedException, IOException { - System.clearProperty("GOOGLE_CLOUD_ENABLE_TRACING"); - inMemoryMetricReader.close(); inMemoryMetricReader.shutdown(); @@ -923,55 +919,4 @@ void grpcOpenTelemetryImplementation_setSampledToLocalTracing_methodFullNameIsRe echoClient.close(); echoClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); } - - @Test - void testTracingFeatureFlag() throws Exception { - // Test tracing disabled - System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "false"); - TracingTracerFactory factory = new TracingTracerFactory(null); - ApiTracer tracer = - factory.newTracer( - BaseApiTracer.getInstance(), - SpanName.of("EchoClient", "Echo"), - ApiTracerFactory.OperationType.Unary); - assertThat(tracer).isNotInstanceOf(TracingTracer.class); - assertThat(tracer).isSameInstanceAs(BaseApiTracer.getInstance()); - - // Test tracing enabled - System.setProperty("GOOGLE_CLOUD_ENABLE_TRACING", "true"); - tracer = - factory.newTracer( - BaseApiTracer.getInstance(), - SpanName.of("EchoClient", "Echo"), - ApiTracerFactory.OperationType.Unary); - assertThat(tracer).isInstanceOf(TracingTracer.class); - - // Verify dummy network trace - InMemorySpanExporter spanExporter = InMemorySpanExporter.create(); - SdkTracerProvider tracerProvider = - SdkTracerProvider.builder() - .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) - .build(); - OpenTelemetry openTelemetry = - OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build(); - - OpenTelemetryTracingRecorder tracingRecorder = - new OpenTelemetryTracingRecorder(openTelemetry, SERVICE_NAME); - TracingTracerFactory tracingTracerFactory = new TracingTracerFactory(tracingRecorder); - - EchoClient grpcClient = TestClientInitializer.createGrpcEchoClientOpentelemetry(tracingTracerFactory); - try { - grpcClient.echo(EchoRequest.newBuilder().setContent("test").build()); - - List spans = spanExporter.getFinishedSpanItems(); - assertThat(spans).isNotEmpty(); - Optional t4Span = - spans.stream().filter(s -> s.getName().equals("LowLevelNetworkSpan")).findFirst(); - assertThat(t4Span.isPresent()).isTrue(); - assertThat(t4Span.get().getKind()).isEqualTo(io.opentelemetry.api.trace.SpanKind.CLIENT); - } finally { - grpcClient.close(); - grpcClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); - } - } } From a9c70ab83091c4d1ca3b5064d873d20c9111b9bb Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Mon, 12 Jan 2026 16:24:58 -0500 Subject: [PATCH 15/17] chore: format --- .../main/java/com/google/api/gax/rpc/StubSettings.java | 2 +- .../com/google/api/gax/tracing/CompositeApiTracer.java | 6 ++---- .../google/api/gax/tracing/CompositeApiTracerFactory.java | 2 +- .../java/com/google/api/gax/tracing/TracingTracer.java | 8 ++------ .../com/google/showcase/v1beta1/it/ITOtelTracing.java | 2 +- 5 files changed, 7 insertions(+), 13 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java index 4f31bf4f9c..35a07a5536 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java @@ -50,10 +50,10 @@ import com.google.api.gax.tracing.OpenTelemetryTracingRecorder; import com.google.api.gax.tracing.TracingTracerFactory; import com.google.api.gax.tracing.TracingUtils; -import io.opentelemetry.api.GlobalOpenTelemetry; import com.google.auth.oauth2.QuotaProjectIdProvider; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; +import io.opentelemetry.api.GlobalOpenTelemetry; import java.io.IOException; import java.util.concurrent.Executor; import javax.annotation.Nonnull; diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracer.java index 4bbb08b195..69e850d522 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracer.java @@ -35,9 +35,7 @@ import java.util.List; import java.util.stream.Collectors; -/** - * A composite implementation of {@link ApiTracer} that broadcasts events to multiple tracers. - */ +/** A composite implementation of {@link ApiTracer} that broadcasts events to multiple tracers. */ @InternalApi public class CompositeApiTracer implements ApiTracer { private final List tracers; @@ -131,4 +129,4 @@ public void requestSent() { public void batchRequestSent(long elementCount, long requestSize) { tracers.forEach(t -> t.batchRequestSent(elementCount, requestSize)); } -} \ No newline at end of file +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracerFactory.java index ecb154e4ca..450f6778fd 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeApiTracerFactory.java @@ -58,4 +58,4 @@ public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType op .collect(Collectors.toList()); return new CompositeApiTracer(tracers); } -} \ No newline at end of file +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java index 10d20c871b..001d8fccc7 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java @@ -55,16 +55,12 @@ public void attemptStarted(Object request, int attemptNumber) { tracingRecorder.recordLowLevelNetworkSpan(attributes); } - /** - * Add attributes that will be attached to all spans. - */ + /** Add attributes that will be attached to all spans. */ public void addAttributes(String key, String value) { attributes.put(key, value); } - /** - * Add attributes that will be attached to all spans. - */ + /** Add attributes that will be attached to all spans. */ public void addAttributes(Map attributes) { this.attributes.putAll(attributes); } diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java index 02a35116a8..14df6dcef6 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java @@ -39,7 +39,6 @@ import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder; import com.google.api.gax.tracing.SpanName; import com.google.api.gax.tracing.TracingTracer; -import com.google.api.gax.tracing.TracingTracerFactory; import com.google.showcase.v1beta1.EchoClient; import com.google.showcase.v1beta1.EchoRequest; import com.google.showcase.v1beta1.it.util.TestClientInitializer; @@ -143,6 +142,7 @@ void testTracingAndMetrics_recordedSimultaneously() throws Exception { assertThat(spans).isNotEmpty(); boolean foundLowLevelSpan = spans.stream() + // .anyMatch(span -> span.getName().equals(SERVICE_NAME + "/low-level-network-span")); .anyMatch(span -> span.getName().equals("/low-level-network-span")); assertThat(foundLowLevelSpan).isTrue(); } From a6716b4b3ea91218f0e84dcfde5905f4e7d16dec Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Mon, 12 Jan 2026 16:25:40 -0500 Subject: [PATCH 16/17] chore: format ii --- .../test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index 0f4a10eddb..06f46deee8 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -72,7 +72,6 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.data.PointData; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; -import io.opentelemetry.sdk.trace.SdkTracerProvider; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; From 99bfd5b730441b7ce3d829d7bc1d9be7052313a0 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 12 Jan 2026 21:32:42 +0000 Subject: [PATCH 17/17] chore: generate libraries at Mon Jan 12 21:30:41 UTC 2026 --- .../test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index 0f4a10eddb..06f46deee8 100644 --- a/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -72,7 +72,6 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.data.PointData; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; -import io.opentelemetry.sdk.trace.SdkTracerProvider; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap;