From 7082247edd5bbd902280292f5ca2766169746daa Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Wed, 4 Feb 2026 14:34:42 -0500 Subject: [PATCH 01/32] feat: introduce minimal implementation of OTel tracing --- .../com/google/api/gax/rpc/ClientContext.java | 2 +- .../google/api/gax/rpc/EndpointContext.java | 20 +- .../com/google/api/gax/rpc/StubSettings.java | 7 + .../api/gax/tracing/ApiTracerFactory.java | 11 ++ .../tracing/OpenTelemetryTracingRecorder.java | 114 ++++++++++++ .../api/gax/tracing/TracingRecorder.java | 70 +++++++ .../google/api/gax/tracing/TracingTracer.java | 154 ++++++++++++++++ .../api/gax/tracing/TracingTracerFactory.java | 99 ++++++++++ .../api/gax/rpc/EndpointContextTest.java | 27 +++ .../OpenTelemetryTracingRecorderTest.java | 116 ++++++++++++ .../gax/tracing/TracingTracerFactoryTest.java | 84 +++++++++ .../api/gax/tracing/TracingTracerTest.java | 103 +++++++++++ .../showcase/v1beta1/it/ITOtelTracing.java | 172 ++++++++++++++++++ 13 files changed, 977 insertions(+), 2 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java 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/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.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 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/rpc/ClientContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index 72d54356b0..d2f11e3b9d 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -284,7 +284,7 @@ public static ClientContext create(StubSettings settings) throws IOException { .setQuotaProjectId(settings.getQuotaProjectId()) .setStreamWatchdog(watchdog) .setStreamWatchdogCheckIntervalDuration(settings.getStreamWatchdogCheckIntervalDuration()) - .setTracerFactory(settings.getTracerFactory()) + .setTracerFactory(settings.getTracerFactory().withAttributesFromSettings(settings)) .setEndpointContext(endpointContext) .build(); } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java index a2e44d8a8b..189111c9b6 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -133,6 +133,8 @@ public static EndpointContext getDefaultInstance() { public abstract String resolvedEndpoint(); + public abstract String resolvedServerAddress(); + public abstract Builder toBuilder(); public static Builder newBuilder() { @@ -228,6 +230,8 @@ public abstract static class Builder { public abstract Builder setResolvedEndpoint(String resolvedEndpoint); + public abstract Builder setResolvedServerAddress(String serverAddress); + public abstract Builder setResolvedUniverseDomain(String resolvedUniverseDomain); abstract Builder setUseS2A(boolean useS2A); @@ -382,6 +386,18 @@ boolean shouldUseS2A() { return mtlsEndpoint().contains(Credentials.GOOGLE_DEFAULT_UNIVERSE); } + private String parseServerAddress(String endpoint) { + int colonPortIndex = endpoint.lastIndexOf(':'); + int doubleSlashIndex = endpoint.lastIndexOf("//"); + if (colonPortIndex == -1) { + return endpoint; + } + if (doubleSlashIndex != -1 && doubleSlashIndex < colonPortIndex) { + return endpoint.substring(doubleSlashIndex + 2, colonPortIndex); + } + return endpoint.substring(0, colonPortIndex); + } + // Default to port 443 for HTTPS. Using HTTP requires explicitly setting the endpoint private String buildEndpointTemplate(String serviceName, String resolvedUniverseDomain) { return serviceName + "." + resolvedUniverseDomain + ":443"; @@ -416,7 +432,9 @@ String mtlsEndpointResolver( public EndpointContext build() throws IOException { // The Universe Domain is used to resolve the Endpoint. It should be resolved first setResolvedUniverseDomain(determineUniverseDomain()); - setResolvedEndpoint(determineEndpoint()); + String endpoint = determineEndpoint(); + setResolvedEndpoint(endpoint); + setResolvedServerAddress(parseServerAddress(endpoint)); setUseS2A(shouldUseS2A()); return autoBuild(); } 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..ee18dcac9b 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 @@ -190,6 +190,13 @@ public String getEndpoint() { return endpointContext.resolvedEndpoint(); } + /** + * @return the fully resolved server address used by the client + */ + public final String getServerAddress() { + return endpointContext.resolvedServerAddress(); + } + /** * @return the newly created EndpointContext */ diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java index bb8345b88c..0a67896b3b 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java @@ -31,6 +31,7 @@ import com.google.api.core.InternalApi; import com.google.api.core.InternalExtensionOnly; +import com.google.api.gax.rpc.StubSettings; /** * A factory to create new instances of {@link ApiTracer}s. @@ -61,4 +62,14 @@ enum OperationType { * @param operationType the type of operation that the tracer will trace */ ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType); + + /** + * Returns a new {@link ApiTracerFactory} that will add the given attributes to all tracers + * created by the factory. + * + * @param settings a {@link StubSettings} object containing information to construct attributes + */ + default ApiTracerFactory withAttributesFromSettings(StubSettings settings) { + return this; + } } 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..28d67aebaf --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java @@ -0,0 +1,114 @@ +/* + * 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; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import java.util.Map; + +/** + * OpenTelemetry implementation of recording traces. This implementation collects the measurements + * related to the lifecyle of an RPC. + */ +@BetaApi +@InternalApi +public class OpenTelemetryTracingRecorder implements TracingRecorder { + private final Tracer tracer; + + public OpenTelemetryTracingRecorder(OpenTelemetry openTelemetry) { + this.tracer = openTelemetry.getTracer("gax-java"); + } + + @Override + public SpanHandle startSpan(String name, Map attributes) { + return startSpan(name, attributes, null); + } + + @Override + public SpanHandle startSpan(String name, Map attributes, SpanHandle parent) { + SpanBuilder spanBuilder = + tracer.spanBuilder(name).setSpanKind(SpanKind.CLIENT); // Mark as a network-facing call + + if (attributes != null) { + attributes.forEach((k, v) -> spanBuilder.setAttribute(k, v)); + } + + if (parent instanceof OtelSpanHandle) { + spanBuilder.setParent(Context.current().with(((OtelSpanHandle) parent).span)); + } + + Span span = spanBuilder.startSpan(); + + return new OtelSpanHandle(span); + } + + @Override + @SuppressWarnings("MustBeClosedChecker") + public ApiTracer.Scope inScope(SpanHandle handle) { + if (handle instanceof OtelSpanHandle) { + Scope scope = ((OtelSpanHandle) handle).span.makeCurrent(); + return scope::close; + } + return () -> {}; + } + + private static class OtelSpanHandle implements SpanHandle { + private final Span span; + + private OtelSpanHandle(Span span) { + this.span = span; + } + + @Override + public void end() { + span.end(); + } + + @Override + public void recordError(Throwable error) { + span.recordException(error); + span.setStatus(StatusCode.ERROR); + } + + @Override + public void setAttribute(String key, String value) { + span.setAttribute(key, value); + } + } +} 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..1566bb8d39 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java @@ -0,0 +1,70 @@ +/* + * 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 java.util.Map; + +/** + * Provides an interface for tracing recording. The implementer is expected to use an observability + * 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 +public interface TracingRecorder { + /** Starts a span and returns a handle to manage its lifecycle. */ + SpanHandle startSpan(String name, Map attributes); + + /** Starts a span with a parent and returns a handle to manage its lifecycle. */ + default SpanHandle startSpan(String name, Map attributes, SpanHandle parent) { + return startSpan(name, attributes); + } + + /** + * Installs the span into the current thread-local context. + * + * @return a scope that must be closed to remove the span from the context. + */ + default ApiTracer.Scope inScope(SpanHandle handle) { + return () -> {}; + } + + interface SpanHandle { + void end(); + + void recordError(Throwable error); + + void setAttribute(String key, String value); + } +} 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..510ae6ca12 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java @@ -0,0 +1,154 @@ +/* + * 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 java.util.HashMap; +import java.util.Map; + +@BetaApi +@InternalApi +public class TracingTracer extends BaseApiTracer { + public static final String SERVER_ADDRESS_ATTRIBUTE = "server.address"; + public static final String LANGUAGE_ATTRIBUTE = "gcp.client.language"; + + public static final String DEFAULT_LANGUAGE = "Java"; + + private final TracingRecorder recorder; + private final Map operationAttributes; + private final Map attemptAttributes; + private final String attemptSpanName; + private final TracingRecorder.SpanHandle operationHandle; + private TracingRecorder.SpanHandle attemptHandle; + + public TracingTracer(TracingRecorder recorder, String operationSpanName, String attemptSpanName) { + this.recorder = recorder; + this.attemptSpanName = attemptSpanName; + this.operationAttributes = new HashMap<>(); + this.attemptAttributes = new HashMap<>(); + this.attemptAttributes.put(LANGUAGE_ATTRIBUTE, DEFAULT_LANGUAGE); + + // Start the long-lived operation span. + this.operationHandle = recorder.startSpan(operationSpanName, operationAttributes); + } + + @Override + public Scope inScope() { + // If an attempt is in progress, make it current so downstream spans are its children. + // Otherwise, make the operation span current. + if (attemptHandle != null) { + return recorder.inScope(attemptHandle); + } + return recorder.inScope(operationHandle); + } + + @Override + public void attemptStarted(Object request, int attemptNumber) { + Map attemptAttributes = new HashMap<>(this.attemptAttributes); + // Start the specific attempt span with the operation span as parent + this.attemptHandle = recorder.startSpan(attemptSpanName, attemptAttributes, operationHandle); + } + + @Override + public void attemptSucceeded() { + endAttempt(); + } + + @Override + public void attemptCancelled() { + endAttempt(); + } + + /** + * Common implementation while no specific behavior is required. + * + * @param error the error to be recorded + */ + private void attemptError(Throwable error) { + if (attemptHandle != null) { + attemptHandle.recordError(error); + } + endAttempt(); + } + + @Override + public void attemptFailedDuration(Throwable error, java.time.Duration delay) { + attemptError(error); + } + + @Override + public void attemptFailedRetriesExhausted(Throwable error) { + attemptError(error); + } + + @Override + public void attemptPermanentFailure(Throwable error) { + attemptError(error); + } + + private void endAttempt() { + if (attemptHandle != null) { + attemptHandle.end(); + attemptHandle = null; + } + } + + @Override + public void operationSucceeded() { + operationHandle.end(); + } + + @Override + public void operationCancelled() { + operationHandle.end(); + } + + @Override + public void operationFailed(Throwable error) { + operationHandle.recordError(error); + operationHandle.end(); + } + + public void addOperationAttributes(Map attributes) { + this.operationAttributes.putAll(attributes); + if (operationHandle != null) { + attributes.forEach((k, v) -> operationHandle.setAttribute(k, v)); + } + } + + public void addAttemptAttributes(Map attributes) { + this.attemptAttributes.putAll(attributes); + if (attemptHandle != null) { + attributes.forEach((k, v) -> attemptHandle.setAttribute(k, v)); + } + } +} 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..081685585a --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java @@ -0,0 +1,99 @@ +/* + * 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 com.google.api.gax.rpc.StubSettings; +import com.google.common.collect.ImmutableMap; +import java.util.HashMap; +import java.util.Map; + +/** + * 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 +public class TracingTracerFactory implements ApiTracerFactory { + private final TracingRecorder tracingRecorder; + + /** Mapping of client attributes that are set for every TracingTracer at operation level */ + private final Map operationAttributes; + + /** Mapping of client attributes that are set for every TracingTracer at attempt level */ + private final Map attemptAttributes; + + /** Creates a TracingTracerFactory with no additional client level attributes. */ + public TracingTracerFactory(TracingRecorder tracingRecorder) { + this(tracingRecorder, ImmutableMap.of(), 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 operationAttributes, + Map attemptAttributes) { + this.tracingRecorder = tracingRecorder; + + this.operationAttributes = ImmutableMap.copyOf(operationAttributes); + this.attemptAttributes = ImmutableMap.copyOf(attemptAttributes); + } + + @Override + public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { + // TODO(diegomarquezp): use span names from design + String operationSpanName = + spanName.getClientName() + "." + spanName.getMethodName() + "/operation"; + String attemptSpanName = spanName.getClientName() + "/" + spanName.getMethodName() + "/attempt"; + + TracingTracer tracingTracer = + new TracingTracer(tracingRecorder, operationSpanName, attemptSpanName); + tracingTracer.addOperationAttributes(operationAttributes); + tracingTracer.addAttemptAttributes(attemptAttributes); + return tracingTracer; + } + + @Override + public ApiTracerFactory withAttributesFromSettings(StubSettings settings) { + Map newAttemptAttributes = new HashMap<>(this.attemptAttributes); + newAttemptAttributes.put(TracingTracer.SERVER_ADDRESS_ATTRIBUTE, settings.getServerAddress()); + newAttemptAttributes.putAll(attemptAttributes); + return new TracingTracerFactory(tracingRecorder, operationAttributes, newAttemptAttributes); + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java index ef64ccd726..733691d789 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -593,4 +593,31 @@ void shouldUseS2A_success() throws IOException { .setUsingGDCH(false); Truth.assertThat(defaultEndpointContextBuilder.shouldUseS2A()).isTrue(); } + + @Test + void endpointContextBuild_resolvesPortAndServerAddress() throws IOException { + String endpoint = "http://localhost:7469"; + EndpointContext endpointContext = + defaultEndpointContextBuilder + .setClientSettingsEndpoint(endpoint) + .setTransportChannelProviderEndpoint(null) + .build(); + Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("localhost"); + + endpoint = "localhost:7469"; + endpointContext = + defaultEndpointContextBuilder + .setClientSettingsEndpoint(endpoint) + .setTransportChannelProviderEndpoint(null) + .build(); + Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("localhost"); + + endpoint = "test.googleapis.com:443"; + endpointContext = + defaultEndpointContextBuilder + .setClientSettingsEndpoint(endpoint) + .setTransportChannelProviderEndpoint(null) + .build(); + Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("test.googleapis.com"); + } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java new file mode 100644 index 0000000000..455e6c4b50 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java @@ -0,0 +1,116 @@ +/* + * 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 org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class OpenTelemetryTracingRecorderTest { + @Mock private OpenTelemetry openTelemetry; + @Mock private Tracer tracer; + @Mock private SpanBuilder spanBuilder; + @Mock private Span span; + @Mock private Scope scope; + + private OpenTelemetryTracingRecorder recorder; + + @BeforeEach + void setUp() { + when(openTelemetry.getTracer(anyString())).thenReturn(tracer); + recorder = new OpenTelemetryTracingRecorder(openTelemetry); + } + + @Test + void testStartSpan_recordsSpan() { + String spanName = "test-span"; + Map attributes = ImmutableMap.of("key1", "value1"); + + when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); + when(spanBuilder.setAttribute("key1", "value1")).thenReturn(spanBuilder); + when(spanBuilder.startSpan()).thenReturn(span); + + TracingRecorder.SpanHandle handle = recorder.startSpan(spanName, attributes); + handle.end(); + + verify(span).end(); + } + + @Test + void testInScope_managesContext() { + String spanName = "test-span"; + when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); + when(spanBuilder.startSpan()).thenReturn(span); + when(span.makeCurrent()).thenReturn(scope); + + TracingRecorder.SpanHandle handle = recorder.startSpan(spanName, null); + try (ApiTracer.Scope ignored = recorder.inScope(handle)) { + // do nothing + } + + verify(span).makeCurrent(); + verify(scope).close(); + } + + @Test + void testRecordError_setsErrorStatus() { + String spanName = "error-span"; + Throwable error = new RuntimeException("test error"); + + when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); + when(spanBuilder.startSpan()).thenReturn(span); + + TracingRecorder.SpanHandle handle = recorder.startSpan(spanName, null); + handle.recordError(error); + + verify(span).recordException(error); + verify(span).setStatus(StatusCode.ERROR); + } +} 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..a64987913d --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerFactoryTest.java @@ -0,0 +1,84 @@ +/* + * 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.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class TracingTracerFactoryTest { + + @Test + void testNewTracer_createsOpenTelemetryTracingTracer() { + TracingRecorder recorder = mock(TracingRecorder.class); + when(recorder.startSpan(anyString(), anyMap())) + .thenReturn(mock(TracingRecorder.SpanHandle.class)); + + TracingTracerFactory factory = new TracingTracerFactory(recorder); + ApiTracer tracer = + factory.newTracer( + null, SpanName.of("service", "method"), ApiTracerFactory.OperationType.Unary); + assertThat(tracer).isInstanceOf(TracingTracer.class); + } + + @Test + void testNewTracer_addsAttributes() { + TracingRecorder recorder = mock(TracingRecorder.class); + TracingRecorder.SpanHandle operationHandle = mock(TracingRecorder.SpanHandle.class); + when(recorder.startSpan(anyString(), anyMap())).thenReturn(operationHandle); + + TracingTracerFactory factory = + new TracingTracerFactory( + recorder, ImmutableMap.of(), ImmutableMap.of("server.port", "443")); + ApiTracer tracer = + factory.newTracer( + null, SpanName.of("service", "method"), ApiTracerFactory.OperationType.Unary); + + tracer.attemptStarted(null, 1); + + ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); + verify(recorder, atLeastOnce()) + .startSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); + + Map attemptAttributes = attributesCaptor.getValue(); + assertThat(attemptAttributes).containsEntry("server.port", "443"); + } +} 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..70ce18b2b4 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerTest.java @@ -0,0 +1,103 @@ +/* + * 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.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class TracingTracerTest { + @Mock private TracingRecorder recorder; + @Mock private TracingRecorder.SpanHandle operationHandle; + @Mock private TracingRecorder.SpanHandle attemptHandle; + private TracingTracer tracer; + private static final String OPERATION_SPAN_NAME = "Service.Method/operation"; + private static final String ATTEMPT_SPAN_NAME = "Service/Method/attempt"; + + @BeforeEach + void setUp() { + when(recorder.startSpan(eq(OPERATION_SPAN_NAME), anyMap())).thenReturn(operationHandle); + tracer = new TracingTracer(recorder, OPERATION_SPAN_NAME, ATTEMPT_SPAN_NAME); + } + + @Test + void testOperationSucceeded_endsSpan() { + tracer.operationSucceeded(); + verify(operationHandle).end(); + } + + @Test + void testOperationFailed_recordsErrorAndEndsSpan() { + Throwable error = new RuntimeException("fail"); + tracer.operationFailed(error); + verify(operationHandle).recordError(error); + verify(operationHandle).end(); + } + + @Test + void testAttemptLifecycle_startsAndEndsAttemptSpan() { + when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) + .thenReturn(attemptHandle); + tracer.attemptStarted(new Object(), 1); + tracer.attemptSucceeded(); + + verify(attemptHandle).end(); + } + + @Test + void testAddAttemptAttributes_passedToAttemptSpan() { + tracer.addAttemptAttributes(ImmutableMap.of("attempt-key", "attempt-value")); + + when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) + .thenReturn(attemptHandle); + tracer.attemptStarted(new Object(), 1); + + ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); + verify(recorder) + .startSpan(eq(ATTEMPT_SPAN_NAME), attributesCaptor.capture(), eq(operationHandle)); + + Map capturedAttributes = attributesCaptor.getValue(); + assertThat(capturedAttributes).containsEntry("attempt-key", "attempt-value"); + assertThat(capturedAttributes) + .containsEntry(TracingTracer.LANGUAGE_ATTRIBUTE, TracingTracer.DEFAULT_LANGUAGE); + } +} 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..329739a4a5 --- /dev/null +++ b/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java @@ -0,0 +1,172 @@ +/* + * 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.OpenTelemetryTracingRecorder; +import com.google.api.gax.tracing.TracingTracer; +import com.google.api.gax.tracing.TracingTracerFactory; +import com.google.common.collect.ImmutableMap; +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.common.AttributeKey; +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 java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ITOtelTracing { + private static final String SHOWCASE_SERVER_ADDRESS = "localhost"; + + private InMemorySpanExporter spanExporter; + private OpenTelemetrySdk openTelemetrySdk; + + @BeforeEach + void setup() { + spanExporter = InMemorySpanExporter.create(); + + SdkTracerProvider tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) + .build(); + + openTelemetrySdk = + OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).buildAndRegisterGlobal(); + } + + @AfterEach + void tearDown() { + if (openTelemetrySdk != null) { + openTelemetrySdk.close(); + } + GlobalOpenTelemetry.resetForTest(); + } + + @Test + void testTracing_successfulEcho_grpc() throws Exception { + TracingTracerFactory tracingFactory = + new TracingTracerFactory(new OpenTelemetryTracingRecorder(openTelemetrySdk)); + + try (EchoClient client = + TestClientInitializer.createGrpcEchoClientOpentelemetry(tracingFactory)) { + + client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); + + List spans = spanExporter.getFinishedSpanItems(); + assertThat(spans).isNotEmpty(); + + SpanData attemptSpan = + spans.stream() + .filter(span -> span.getName().equals("Echo/Echo/attempt")) + .findFirst() + .orElseThrow(() -> new AssertionError("Attempt span 'Echo/Echo/attempt' not found")); + assertThat( + attemptSpan + .getAttributes() + .get(AttributeKey.stringKey(TracingTracer.LANGUAGE_ATTRIBUTE))) + .isEqualTo(TracingTracer.DEFAULT_LANGUAGE); + assertThat( + attemptSpan + .getAttributes() + .get(AttributeKey.stringKey(TracingTracer.SERVER_ADDRESS_ATTRIBUTE))) + .isEqualTo(SHOWCASE_SERVER_ADDRESS); + } + } + + @Test + void testTracing_successfulEcho_httpjson() throws Exception { + TracingTracerFactory tracingFactory = + new TracingTracerFactory(new OpenTelemetryTracingRecorder(openTelemetrySdk)); + + try (EchoClient client = + TestClientInitializer.createHttpJsonEchoClientOpentelemetry(tracingFactory)) { + + client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); + + List spans = spanExporter.getFinishedSpanItems(); + assertThat(spans).isNotEmpty(); + + SpanData attemptSpan = + spans.stream() + .filter(span -> span.getName().equals("google.showcase.v1beta1/Echo/Echo/attempt")) + .findFirst() + .orElseThrow(() -> new AssertionError("Attempt span 'Echo/Echo/attempt' not found")); + assertThat( + attemptSpan + .getAttributes() + .get(AttributeKey.stringKey(TracingTracer.SERVER_ADDRESS_ATTRIBUTE))) + .isEqualTo(SHOWCASE_SERVER_ADDRESS); + } + } + + @Test + void testTracing_withCustomAttributes() throws Exception { + Map opAttributes = ImmutableMap.of("op-key", "op-value"); + Map atAttributes = ImmutableMap.of("at-key", "at-value"); + TracingTracerFactory tracingFactory = + new TracingTracerFactory( + new OpenTelemetryTracingRecorder(openTelemetrySdk), opAttributes, atAttributes); + + try (EchoClient client = + TestClientInitializer.createGrpcEchoClientOpentelemetry(tracingFactory)) { + + client.echo(EchoRequest.newBuilder().setContent("attr-test").build()); + + Thread.sleep(50); + List spans = spanExporter.getFinishedSpanItems(); + + SpanData operationSpan = + spans.stream() + .filter(span -> span.getName().equals("Echo.Echo/operation")) + .findFirst() + .orElseThrow( + () -> new AssertionError("Operation span 'Echo/Echo/operation' not found")); + assertThat(operationSpan.getAttributes().get(AttributeKey.stringKey("op-key"))) + .isEqualTo("op-value"); + SpanData attemptSpan = + spans.stream() + .filter(span -> span.getName().equals("Echo/Echo/attempt")) + .findFirst() + .orElseThrow(() -> new AssertionError("Attempt span 'Echo/Echo/attempt' not found")); + assertThat(attemptSpan.getAttributes().get(AttributeKey.stringKey("at-key"))) + .isEqualTo("at-value"); + } + } +} From bfe6a6d22d5978485aaeb1183cc0ee646bff4733 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 13:38:15 -0500 Subject: [PATCH 02/32] chore: avoid new public methods --- .../main/java/com/google/api/gax/rpc/ClientContext.java | 2 +- .../src/main/java/com/google/api/gax/rpc/StubSettings.java | 7 ------- .../java/com/google/api/gax/tracing/ApiTracerFactory.java | 7 ++++--- .../com/google/api/gax/tracing/TracingTracerFactory.java | 5 +++-- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index d2f11e3b9d..53725e0d38 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -284,7 +284,7 @@ public static ClientContext create(StubSettings settings) throws IOException { .setQuotaProjectId(settings.getQuotaProjectId()) .setStreamWatchdog(watchdog) .setStreamWatchdogCheckIntervalDuration(settings.getStreamWatchdogCheckIntervalDuration()) - .setTracerFactory(settings.getTracerFactory().withAttributesFromSettings(settings)) + .setTracerFactory(settings.getTracerFactory().withInferredAttributes(endpointContext)) .setEndpointContext(endpointContext) .build(); } 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 ee18dcac9b..f97f808ca7 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 @@ -190,13 +190,6 @@ public String getEndpoint() { return endpointContext.resolvedEndpoint(); } - /** - * @return the fully resolved server address used by the client - */ - public final String getServerAddress() { - return endpointContext.resolvedServerAddress(); - } - /** * @return the newly created EndpointContext */ diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java index 0a67896b3b..f4cb386ab4 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java @@ -31,6 +31,7 @@ import com.google.api.core.InternalApi; import com.google.api.core.InternalExtensionOnly; +import com.google.api.gax.rpc.EndpointContext; import com.google.api.gax.rpc.StubSettings; /** @@ -64,12 +65,12 @@ enum OperationType { ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType); /** - * Returns a new {@link ApiTracerFactory} that will add the given attributes to all tracers + * Returns a new {@link ApiTracerFactory} that will infer the attributes for all tracers * created by the factory. * - * @param settings a {@link StubSettings} object containing information to construct attributes + * @param endpointContext an {@link EndpointContext} object containing information to construct attributes */ - default ApiTracerFactory withAttributesFromSettings(StubSettings settings) { + default ApiTracerFactory withInferredAttributes(EndpointContext endpointContext) { return this; } } 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 081685585a..c4b607470a 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,7 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; +import com.google.api.gax.rpc.EndpointContext; import com.google.api.gax.rpc.StubSettings; import com.google.common.collect.ImmutableMap; import java.util.HashMap; @@ -90,9 +91,9 @@ public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType op } @Override - public ApiTracerFactory withAttributesFromSettings(StubSettings settings) { + public ApiTracerFactory withInferredAttributes(EndpointContext endpointContext) { Map newAttemptAttributes = new HashMap<>(this.attemptAttributes); - newAttemptAttributes.put(TracingTracer.SERVER_ADDRESS_ATTRIBUTE, settings.getServerAddress()); + newAttemptAttributes.put(TracingTracer.SERVER_ADDRESS_ATTRIBUTE, endpointContext.resolvedServerAddress()); newAttemptAttributes.putAll(attemptAttributes); return new TracingTracerFactory(tracingRecorder, operationAttributes, newAttemptAttributes); } From ea6cb182a2d6f1cc49986587d33c1642d6e905fc Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 14:12:04 -0500 Subject: [PATCH 03/32] feat: add api tracer context --- .../com/google/api/gax/rpc/ClientContext.java | 6 +- .../api/gax/tracing/ApiTracerContext.java | 61 +++++++++++++++++++ .../api/gax/tracing/ApiTracerFactory.java | 11 ++-- .../api/gax/tracing/TracingTracerFactory.java | 13 ++-- .../gax/tracing/TracingTracerFactoryTest.java | 56 +++++++++++++++++ .../showcase/v1beta1/it/ITOtelTracing.java | 2 +- 6 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index 53725e0d38..c3ff8c2485 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -41,6 +41,7 @@ import com.google.api.gax.core.ExecutorAsBackgroundResource; import com.google.api.gax.core.ExecutorProvider; import com.google.api.gax.rpc.internal.QuotaProjectIdHidingCredentials; +import com.google.api.gax.tracing.ApiTracerContext; import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.gax.tracing.BaseApiTracerFactory; import com.google.auth.ApiKeyCredentials; @@ -269,6 +270,9 @@ public static ClientContext create(StubSettings settings) throws IOException { if (watchdogProvider != null && watchdogProvider.shouldAutoClose()) { backgroundResources.add(watchdog); } + ApiTracerContext apiTracerContext = + ApiTracerContext.newBuilder().setEndpointContext(endpointContext).build(); + ApiTracerFactory apiTracerFactory = settings.getTracerFactory().withContext(apiTracerContext); return newBuilder() .setBackgroundResources(backgroundResources.build()) @@ -284,7 +288,7 @@ public static ClientContext create(StubSettings settings) throws IOException { .setQuotaProjectId(settings.getQuotaProjectId()) .setStreamWatchdog(watchdog) .setStreamWatchdogCheckIntervalDuration(settings.getStreamWatchdogCheckIntervalDuration()) - .setTracerFactory(settings.getTracerFactory().withInferredAttributes(endpointContext)) + .setTracerFactory(apiTracerFactory) .setEndpointContext(endpointContext) .build(); } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java new file mode 100644 index 0000000000..5b6c634bae --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.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.api.gax.rpc.EndpointContext; +import com.google.auto.value.AutoValue; +import javax.annotation.Nullable; + +/** + * A context object that contains information used to infer attributes for {@link ApiTracer}s. The + * information in this class is meant to serve for common attributes to all traces. + * + *

For internal use only. + */ +@InternalApi +@AutoValue +public abstract class ApiTracerContext { + + @Nullable + public abstract EndpointContext getEndpointContext(); + + public static Builder newBuilder() { + return new AutoValue_ApiTracerContext.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setEndpointContext(EndpointContext endpointContext); + + public abstract ApiTracerContext build(); + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java index f4cb386ab4..07a0fcf12d 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java @@ -31,8 +31,6 @@ import com.google.api.core.InternalApi; import com.google.api.core.InternalExtensionOnly; -import com.google.api.gax.rpc.EndpointContext; -import com.google.api.gax.rpc.StubSettings; /** * A factory to create new instances of {@link ApiTracer}s. @@ -65,12 +63,13 @@ enum OperationType { ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType); /** - * Returns a new {@link ApiTracerFactory} that will infer the attributes for all tracers - * created by the factory. + * Returns a new {@link ApiTracerFactory} that will use the provided context to infer attributes + * for all tracers created by the factory. * - * @param endpointContext an {@link EndpointContext} object containing information to construct attributes + * @param context an {@link ApiTracerContext} object containing information to construct + * attributes */ - default ApiTracerFactory withInferredAttributes(EndpointContext endpointContext) { + default ApiTracerFactory withContext(ApiTracerContext context) { return this; } } 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 c4b607470a..0912c2af40 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,8 +32,6 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; -import com.google.api.gax.rpc.EndpointContext; -import com.google.api.gax.rpc.StubSettings; import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.Map; @@ -78,7 +76,7 @@ public TracingTracerFactory( @Override public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { - // TODO(diegomarquezp): use span names from design + // TODO(diegomarquezp): these are placeholders for span names and will be adjusted as the feature is developed. String operationSpanName = spanName.getClientName() + "." + spanName.getMethodName() + "/operation"; String attemptSpanName = spanName.getClientName() + "/" + spanName.getMethodName() + "/attempt"; @@ -91,10 +89,13 @@ public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType op } @Override - public ApiTracerFactory withInferredAttributes(EndpointContext endpointContext) { + public ApiTracerFactory withContext(ApiTracerContext context) { Map newAttemptAttributes = new HashMap<>(this.attemptAttributes); - newAttemptAttributes.put(TracingTracer.SERVER_ADDRESS_ATTRIBUTE, endpointContext.resolvedServerAddress()); - newAttemptAttributes.putAll(attemptAttributes); + if (context.getEndpointContext() != null) { + newAttemptAttributes.put( + TracingTracer.SERVER_ADDRESS_ATTRIBUTE, + context.getEndpointContext().resolvedServerAddress()); + } return new TracingTracerFactory(tracingRecorder, operationAttributes, newAttemptAttributes); } } 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 index a64987913d..e4e1595af3 100644 --- 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 @@ -39,6 +39,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.api.gax.rpc.EndpointContext; import com.google.common.collect.ImmutableMap; import java.util.Map; import org.junit.jupiter.api.Test; @@ -81,4 +82,59 @@ void testNewTracer_addsAttributes() { Map attemptAttributes = attributesCaptor.getValue(); assertThat(attemptAttributes).containsEntry("server.port", "443"); } + + @Test + void testWithContext_addsInferredAttributes() { + TracingRecorder recorder = mock(TracingRecorder.class); + TracingRecorder.SpanHandle operationHandle = mock(TracingRecorder.SpanHandle.class); + when(recorder.startSpan(anyString(), anyMap())).thenReturn(operationHandle); + + EndpointContext endpointContext = mock(EndpointContext.class); + when(endpointContext.resolvedServerAddress()).thenReturn("example.com"); + + ApiTracerContext context = + ApiTracerContext.newBuilder().setEndpointContext(endpointContext).build(); + + TracingTracerFactory factory = new TracingTracerFactory(recorder); + ApiTracerFactory factoryWithContext = factory.withContext(context); + + ApiTracer tracer = + factoryWithContext.newTracer( + null, SpanName.of("service", "method"), ApiTracerFactory.OperationType.Unary); + + tracer.attemptStarted(null, 1); + + ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); + verify(recorder, atLeastOnce()) + .startSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); + + Map attemptAttributes = attributesCaptor.getValue(); + assertThat(attemptAttributes) + .containsEntry(TracingTracer.SERVER_ADDRESS_ATTRIBUTE, "example.com"); + } + + @Test + void testWithContext_noEndpointContext_doesNotAddAttributes() { + TracingRecorder recorder = mock(TracingRecorder.class); + TracingRecorder.SpanHandle operationHandle = mock(TracingRecorder.SpanHandle.class); + when(recorder.startSpan(anyString(), anyMap())).thenReturn(operationHandle); + + ApiTracerContext context = ApiTracerContext.newBuilder().build(); + + TracingTracerFactory factory = new TracingTracerFactory(recorder); + ApiTracerFactory factoryWithContext = factory.withContext(context); + + ApiTracer tracer = + factoryWithContext.newTracer( + null, SpanName.of("service", "method"), ApiTracerFactory.OperationType.Unary); + + tracer.attemptStarted(null, 1); + + ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); + verify(recorder, atLeastOnce()) + .startSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); + + Map attemptAttributes = attributesCaptor.getValue(); + assertThat(attemptAttributes).doesNotContainKey(TracingTracer.SERVER_ADDRESS_ATTRIBUTE); + } } 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 329739a4a5..0b8c3b9008 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 @@ -149,7 +149,7 @@ void testTracing_withCustomAttributes() throws Exception { client.echo(EchoRequest.newBuilder().setContent("attr-test").build()); - Thread.sleep(50); + openTelemetrySdk.getSdkTracerProvider().forceFlush(); List spans = spanExporter.getFinishedSpanItems(); SpanData operationSpan = From 097f7014debab8f25f351d9a76513eabfcd51301 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 14:25:09 -0500 Subject: [PATCH 04/32] chore: concise comment --- .../main/java/com/google/api/gax/tracing/ApiTracerContext.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java index 5b6c634bae..64138a6d14 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java @@ -36,8 +36,7 @@ import javax.annotation.Nullable; /** - * A context object that contains information used to infer attributes for {@link ApiTracer}s. The - * information in this class is meant to serve for common attributes to all traces. + * A context object that contains information used to infer attributes that are common for all {@link ApiTracer}s. * *

For internal use only. */ From f547e5a79d5974335dfbb3bb4e6426b158fbdb67 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 14:44:04 -0500 Subject: [PATCH 05/32] fix: use internal span kind for operations --- .../api/gax/tracing/ApiTracerContext.java | 3 +- .../tracing/OpenTelemetryTracingRecorder.java | 10 +++- .../api/gax/tracing/TracingTracerFactory.java | 3 +- .../OpenTelemetryTracingRecorderTest.java | 33 ++++++++++-- .../showcase/v1beta1/it/ITOtelTracing.java | 50 +++++++++++++++++-- 5 files changed, 88 insertions(+), 11 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java index 64138a6d14..41ea842a3b 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java @@ -36,7 +36,8 @@ import javax.annotation.Nullable; /** - * A context object that contains information used to infer attributes that are common for all {@link ApiTracer}s. + * A context object that contains information used to infer attributes that are common for all + * {@link ApiTracer}s. * *

For internal use only. */ 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 28d67aebaf..1197a42e8a 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 @@ -62,8 +62,14 @@ public SpanHandle startSpan(String name, Map attributes) { @Override public SpanHandle startSpan(String name, Map attributes, SpanHandle parent) { - SpanBuilder spanBuilder = - tracer.spanBuilder(name).setSpanKind(SpanKind.CLIENT); // Mark as a network-facing call + SpanBuilder spanBuilder = tracer.spanBuilder(name); + + // Operation and Attempt spans are INTERNAL and CLIENT respectively. + if (parent == null) { + spanBuilder.setSpanKind(SpanKind.INTERNAL); + } else { + spanBuilder.setSpanKind(SpanKind.CLIENT); + } if (attributes != null) { attributes.forEach((k, v) -> spanBuilder.setAttribute(k, v)); 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 0912c2af40..0be17a3d4b 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 @@ -76,7 +76,8 @@ public TracingTracerFactory( @Override public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { - // TODO(diegomarquezp): these are placeholders for span names and will be adjusted as the feature is developed. + // TODO(diegomarquezp): these are placeholders for span names and will be adjusted as the + // feature is developed. String operationSpanName = spanName.getClientName() + "." + spanName.getMethodName() + "/operation"; String attemptSpanName = spanName.getClientName() + "/" + spanName.getMethodName() + "/attempt"; diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java index 455e6c4b50..93c8dbc1bf 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java @@ -31,6 +31,7 @@ package com.google.api.gax.tracing; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -65,13 +66,39 @@ void setUp() { recorder = new OpenTelemetryTracingRecorder(openTelemetry); } + @Test + void testStartSpan_operation_isInternal() { + String spanName = "operation-span"; + when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); + when(spanBuilder.startSpan()).thenReturn(span); + + recorder.startSpan(spanName, null); + + verify(spanBuilder).setSpanKind(SpanKind.INTERNAL); + } + + @Test + void testStartSpan_attempt_isClient() { + String spanName = "attempt-span"; + TracingRecorder.SpanHandle parent = mock(TracingRecorder.SpanHandle.class); + + when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); + when(spanBuilder.startSpan()).thenReturn(span); + + recorder.startSpan(spanName, null, parent); + + verify(spanBuilder).setSpanKind(SpanKind.CLIENT); + } + @Test void testStartSpan_recordsSpan() { String spanName = "test-span"; Map attributes = ImmutableMap.of("key1", "value1"); when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); - when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); when(spanBuilder.setAttribute("key1", "value1")).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); @@ -85,7 +112,7 @@ void testStartSpan_recordsSpan() { void testInScope_managesContext() { String spanName = "test-span"; when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); - when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); when(span.makeCurrent()).thenReturn(scope); @@ -104,7 +131,7 @@ void testRecordError_setsErrorStatus() { Throwable error = new RuntimeException("test error"); when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); - when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); TracingRecorder.SpanHandle handle = recorder.startSpan(spanName, null); 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 0b8c3b9008..95363d5d16 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 @@ -41,6 +41,7 @@ import com.google.showcase.v1beta1.it.util.TestClientInitializer; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.SdkTracerProvider; @@ -49,6 +50,7 @@ import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -79,6 +81,28 @@ void tearDown() { GlobalOpenTelemetry.resetForTest(); } + /** + * The {@link com.google.api.gax.tracing.TracedUnaryCallable} implementation uses a callback + * approach to report the operation has been completed. That may cause a slight delay between + * client.echo(...) and the availability of the operation span (as opposed to attemptSuceeeded() + * which is reported immediately). This method waits for up to 50ms for the callback to take + * effect. + * + * @param expectedSpans number of flattened spans to be expected + * @return list of spans + */ + private List waitForSpans(int expectedSpans) throws InterruptedException { + for (int i = 0; i < 10; i++) { + List spans = spanExporter.getFinishedSpanItems(); + if (spans.size() == expectedSpans) { + return spans; + } + Thread.sleep(5); + } + Assertions.fail("Timed out waiting for spans"); + return null; + } + @Test void testTracing_successfulEcho_grpc() throws Exception { TracingTracerFactory tracingFactory = @@ -89,14 +113,22 @@ void testTracing_successfulEcho_grpc() throws Exception { client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); - List spans = spanExporter.getFinishedSpanItems(); + List spans = waitForSpans(2); assertThat(spans).isNotEmpty(); + SpanData operationSpan = + spans.stream() + .filter(span -> span.getName().equals("Echo.Echo/operation")) + .findFirst() + .orElseThrow(() -> new AssertionError("Operation span not found")); + assertThat(operationSpan.getKind()).isEqualTo(SpanKind.INTERNAL); + SpanData attemptSpan = spans.stream() .filter(span -> span.getName().equals("Echo/Echo/attempt")) .findFirst() .orElseThrow(() -> new AssertionError("Attempt span 'Echo/Echo/attempt' not found")); + assertThat(attemptSpan.getKind()).isEqualTo(SpanKind.CLIENT); assertThat( attemptSpan .getAttributes() @@ -120,14 +152,22 @@ void testTracing_successfulEcho_httpjson() throws Exception { client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); - List spans = spanExporter.getFinishedSpanItems(); + List spans = waitForSpans(2); assertThat(spans).isNotEmpty(); + SpanData operationSpan = + spans.stream() + .filter(span -> span.getName().equals("google.showcase.v1beta1.Echo/Echo/operation")) + .findFirst() + .orElseThrow(() -> new AssertionError("Operation span not found")); + assertThat(operationSpan.getKind()).isEqualTo(SpanKind.INTERNAL); + SpanData attemptSpan = spans.stream() .filter(span -> span.getName().equals("google.showcase.v1beta1/Echo/Echo/attempt")) .findFirst() .orElseThrow(() -> new AssertionError("Attempt span 'Echo/Echo/attempt' not found")); + assertThat(attemptSpan.getKind()).isEqualTo(SpanKind.CLIENT); assertThat( attemptSpan .getAttributes() @@ -149,8 +189,7 @@ void testTracing_withCustomAttributes() throws Exception { client.echo(EchoRequest.newBuilder().setContent("attr-test").build()); - openTelemetrySdk.getSdkTracerProvider().forceFlush(); - List spans = spanExporter.getFinishedSpanItems(); + List spans = waitForSpans(2); SpanData operationSpan = spans.stream() @@ -158,13 +197,16 @@ void testTracing_withCustomAttributes() throws Exception { .findFirst() .orElseThrow( () -> new AssertionError("Operation span 'Echo/Echo/operation' not found")); + assertThat(operationSpan.getKind()).isEqualTo(SpanKind.INTERNAL); assertThat(operationSpan.getAttributes().get(AttributeKey.stringKey("op-key"))) .isEqualTo("op-value"); + SpanData attemptSpan = spans.stream() .filter(span -> span.getName().equals("Echo/Echo/attempt")) .findFirst() .orElseThrow(() -> new AssertionError("Attempt span 'Echo/Echo/attempt' not found")); + assertThat(attemptSpan.getKind()).isEqualTo(SpanKind.CLIENT); assertThat(attemptSpan.getAttributes().get(AttributeKey.stringKey("at-key"))) .isEqualTo("at-value"); } From b7b9e31d26a659fabd5484c641be1e6f98905efe Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 14:47:09 -0500 Subject: [PATCH 06/32] chore: remove unnecessary inScope implementation --- .../tracing/OpenTelemetryTracingRecorder.java | 11 ----------- .../google/api/gax/tracing/TracingTracer.java | 10 ---------- .../OpenTelemetryTracingRecorderTest.java | 17 ----------------- 3 files changed, 38 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 1197a42e8a..c1a96e9c10 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 @@ -39,7 +39,6 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; import java.util.Map; /** @@ -84,16 +83,6 @@ public SpanHandle startSpan(String name, Map attributes, SpanHan return new OtelSpanHandle(span); } - @Override - @SuppressWarnings("MustBeClosedChecker") - public ApiTracer.Scope inScope(SpanHandle handle) { - if (handle instanceof OtelSpanHandle) { - Scope scope = ((OtelSpanHandle) handle).span.makeCurrent(); - return scope::close; - } - return () -> {}; - } - private static class OtelSpanHandle implements SpanHandle { private final Span span; 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 510ae6ca12..2182e29c9c 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 @@ -61,16 +61,6 @@ public TracingTracer(TracingRecorder recorder, String operationSpanName, String this.operationHandle = recorder.startSpan(operationSpanName, operationAttributes); } - @Override - public Scope inScope() { - // If an attempt is in progress, make it current so downstream spans are its children. - // Otherwise, make the operation span current. - if (attemptHandle != null) { - return recorder.inScope(attemptHandle); - } - return recorder.inScope(operationHandle); - } - @Override public void attemptStarted(Object request, int attemptNumber) { Map attemptAttributes = new HashMap<>(this.attemptAttributes); diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java index 93c8dbc1bf..fea7729038 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java @@ -108,23 +108,6 @@ void testStartSpan_recordsSpan() { verify(span).end(); } - @Test - void testInScope_managesContext() { - String spanName = "test-span"; - when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); - when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); - when(spanBuilder.startSpan()).thenReturn(span); - when(span.makeCurrent()).thenReturn(scope); - - TracingRecorder.SpanHandle handle = recorder.startSpan(spanName, null); - try (ApiTracer.Scope ignored = recorder.inScope(handle)) { - // do nothing - } - - verify(span).makeCurrent(); - verify(scope).close(); - } - @Test void testRecordError_setsErrorStatus() { String spanName = "error-span"; From 556c84c0b250595718a0bac70c9e8e26e18c4d8e Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 14:49:24 -0500 Subject: [PATCH 07/32] Revert "chore: remove unnecessary inScope implementation" This reverts commit b7b9e31d26a659fabd5484c641be1e6f98905efe. --- .../tracing/OpenTelemetryTracingRecorder.java | 11 +++++++++++ .../google/api/gax/tracing/TracingTracer.java | 10 ++++++++++ .../OpenTelemetryTracingRecorderTest.java | 17 +++++++++++++++++ 3 files changed, 38 insertions(+) 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 c1a96e9c10..1197a42e8a 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 @@ -39,6 +39,7 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import java.util.Map; /** @@ -83,6 +84,16 @@ public SpanHandle startSpan(String name, Map attributes, SpanHan return new OtelSpanHandle(span); } + @Override + @SuppressWarnings("MustBeClosedChecker") + public ApiTracer.Scope inScope(SpanHandle handle) { + if (handle instanceof OtelSpanHandle) { + Scope scope = ((OtelSpanHandle) handle).span.makeCurrent(); + return scope::close; + } + return () -> {}; + } + private static class OtelSpanHandle implements SpanHandle { private final Span span; 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 2182e29c9c..510ae6ca12 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 @@ -61,6 +61,16 @@ public TracingTracer(TracingRecorder recorder, String operationSpanName, String this.operationHandle = recorder.startSpan(operationSpanName, operationAttributes); } + @Override + public Scope inScope() { + // If an attempt is in progress, make it current so downstream spans are its children. + // Otherwise, make the operation span current. + if (attemptHandle != null) { + return recorder.inScope(attemptHandle); + } + return recorder.inScope(operationHandle); + } + @Override public void attemptStarted(Object request, int attemptNumber) { Map attemptAttributes = new HashMap<>(this.attemptAttributes); diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java index fea7729038..93c8dbc1bf 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java @@ -108,6 +108,23 @@ void testStartSpan_recordsSpan() { verify(span).end(); } + @Test + void testInScope_managesContext() { + String spanName = "test-span"; + when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); + when(spanBuilder.startSpan()).thenReturn(span); + when(span.makeCurrent()).thenReturn(scope); + + TracingRecorder.SpanHandle handle = recorder.startSpan(spanName, null); + try (ApiTracer.Scope ignored = recorder.inScope(handle)) { + // do nothing + } + + verify(span).makeCurrent(); + verify(scope).close(); + } + @Test void testRecordError_setsErrorStatus() { String spanName = "error-span"; From 0359b7df1dc88d6e7c2ba32bc0e444009cb7074f Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 14:54:52 -0500 Subject: [PATCH 08/32] test: add test for inScope() --- .../showcase/v1beta1/it/ITOtelTracing.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) 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 95363d5d16..191449f67a 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 @@ -32,6 +32,7 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.api.gax.tracing.ApiTracer; import com.google.api.gax.tracing.OpenTelemetryTracingRecorder; import com.google.api.gax.tracing.TracingTracer; import com.google.api.gax.tracing.TracingTracerFactory; @@ -41,6 +42,7 @@ import com.google.showcase.v1beta1.it.util.TestClientInitializer; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; @@ -211,4 +213,28 @@ void testTracing_withCustomAttributes() throws Exception { .isEqualTo("at-value"); } } + + /** + * Confirms that the current span as per Otel context is only valid when the inScope method is + * called. More detailedly, this is to confirm gax thread management, which uses the inScope() + * method, correctly brings the selected span into the context. + */ + @Test + void testInScope_managesOtelContext() { + OpenTelemetryTracingRecorder recorder = new OpenTelemetryTracingRecorder(openTelemetrySdk); + TracingTracer tracer = new TracingTracer(recorder, "operation-span", "attempt-span"); + + // Initially, there should be no current span + assertThat(Span.current().getSpanContext().isValid()).isFalse(); + + try (ApiTracer.Scope ignored = tracer.inScope()) { + // Inside the scope, the current span should be the operation span + assertThat(Span.current().getSpanContext().isValid()).isTrue(); + // We can't easily check the name of the current span in OTel without more complex setup, + // but we can verify it's active. + } + + // After the scope is closed, there should be no current span again + assertThat(Span.current().getSpanContext().isValid()).isFalse(); + } } From 420278bcb4d224c9791510ad3b7118bc0bcf4d1b Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 15:45:33 -0500 Subject: [PATCH 09/32] chore: use suggested server address resolution impl --- .../google/api/gax/rpc/EndpointContext.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java index 189111c9b6..7eb111c69f 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -41,6 +41,8 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -387,15 +389,24 @@ boolean shouldUseS2A() { } private String parseServerAddress(String endpoint) { - int colonPortIndex = endpoint.lastIndexOf(':'); - int doubleSlashIndex = endpoint.lastIndexOf("//"); - if (colonPortIndex == -1) { - return endpoint; - } - if (doubleSlashIndex != -1 && doubleSlashIndex < colonPortIndex) { - return endpoint.substring(doubleSlashIndex + 2, colonPortIndex); + try { + String urlString = endpoint; + if (!urlString.contains("://")) { + urlString = "http://" + urlString; + } + return new URL(urlString).getHost(); + } catch (MalformedURLException e) { + // Fallback for cases URL can't handle. + int colonPortIndex = endpoint.lastIndexOf(':'); + int doubleSlashIndex = endpoint.lastIndexOf("//"); + if (colonPortIndex == -1) { + return endpoint; + } + if (doubleSlashIndex != -1 && doubleSlashIndex < colonPortIndex) { + return endpoint.substring(doubleSlashIndex + 2, colonPortIndex); + } + return endpoint.substring(0, colonPortIndex); } - return endpoint.substring(0, colonPortIndex); } // Default to port 443 for HTTPS. Using HTTP requires explicitly setting the endpoint From 6de9fb6ca7d48e7aee44917a77a98c377c9c0c9d Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 15:50:05 -0500 Subject: [PATCH 10/32] fix: use concurrent hash map --- .../main/java/com/google/api/gax/tracing/TracingTracer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 510ae6ca12..1cf30cc07e 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,6 +34,7 @@ import com.google.api.core.InternalApi; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; @BetaApi @InternalApi @@ -53,8 +54,8 @@ public class TracingTracer extends BaseApiTracer { public TracingTracer(TracingRecorder recorder, String operationSpanName, String attemptSpanName) { this.recorder = recorder; this.attemptSpanName = attemptSpanName; - this.operationAttributes = new HashMap<>(); - this.attemptAttributes = new HashMap<>(); + this.operationAttributes = new ConcurrentHashMap<>(); + this.attemptAttributes = new ConcurrentHashMap<>(); this.attemptAttributes.put(LANGUAGE_ATTRIBUTE, DEFAULT_LANGUAGE); // Start the long-lived operation span. From a61dacf7d045a2f5f3822f740667e3ed3eb329e1 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 15:51:58 -0500 Subject: [PATCH 11/32] chore: remove default impl for startSpan with parent --- .../main/java/com/google/api/gax/tracing/TracingRecorder.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 1566bb8d39..1dfc9264c0 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 @@ -47,9 +47,7 @@ public interface TracingRecorder { SpanHandle startSpan(String name, Map attributes); /** Starts a span with a parent and returns a handle to manage its lifecycle. */ - default SpanHandle startSpan(String name, Map attributes, SpanHandle parent) { - return startSpan(name, attributes); - } + SpanHandle startSpan(String name, Map attributes, SpanHandle parent); /** * Installs the span into the current thread-local context. From b000d5249f6ebdd594bcccaf4fc09900dae43baf Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 15:54:57 -0500 Subject: [PATCH 12/32] chore: add opentelemery-context to tests --- gax-java/dependencies.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/gax-java/dependencies.properties b/gax-java/dependencies.properties index 3b46b82fff..2fb4fb1316 100644 --- a/gax-java/dependencies.properties +++ b/gax-java/dependencies.properties @@ -40,6 +40,7 @@ maven.com_google_api_grpc_grpc_google_common_protos=com.google.api.grpc:grpc-goo maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.42.1 maven.com_google_auth_google_auth_library_credentials=com.google.auth:google-auth-library-credentials:1.42.1 maven.io_opentelemetry_opentelemetry_api=io.opentelemetry:opentelemetry-api:1.47.0 +maven.io_opentelemetry_opentelemetry_context=io.opentelemetry:opentelemetry-context:1.47.0 maven.io_opencensus_opencensus_api=io.opencensus:opencensus-api:0.31.1 maven.io_opencensus_opencensus_contrib_grpc_metrics=io.opencensus:opencensus-contrib-grpc-metrics:0.31.1 maven.io_opencensus_opencensus_contrib_http_util=io.opencensus:opencensus-contrib-http-util:0.31.1 From 9ce0c3429c1c46716f89a9be4462ca3345529c56 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 16:00:27 -0500 Subject: [PATCH 13/32] test: increase coverage for TracingTracerTest --- .../api/gax/tracing/TracingTracerTest.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) 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 index 70ce18b2b4..fe27ba01c2 100644 --- 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 @@ -32,10 +32,13 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.api.gax.tracing.ApiTracer.Scope; import com.google.common.collect.ImmutableMap; +import java.time.Duration; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -65,6 +68,12 @@ void testOperationSucceeded_endsSpan() { verify(operationHandle).end(); } + @Test + void testOperationCancelled_endsSpan() { + tracer.operationCancelled(); + verify(operationHandle).end(); + } + @Test void testOperationFailed_recordsErrorAndEndsSpan() { Throwable error = new RuntimeException("fail"); @@ -83,6 +92,81 @@ void testAttemptLifecycle_startsAndEndsAttemptSpan() { verify(attemptHandle).end(); } + @Test + void testAttemptCancelled_endsSpan() { + when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) + .thenReturn(attemptHandle); + tracer.attemptStarted(new Object(), 1); + tracer.attemptCancelled(); + + verify(attemptHandle).end(); + } + + @Test + void testAttemptFailedDuration_recordsErrorAndEndsSpan() { + when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) + .thenReturn(attemptHandle); + tracer.attemptStarted(new Object(), 1); + + Throwable error = new RuntimeException("fail"); + tracer.attemptFailedDuration(error, Duration.ofMillis(100)); + + verify(attemptHandle).recordError(error); + verify(attemptHandle).end(); + } + + @Test + void testAttemptFailedRetriesExhausted_recordsErrorAndEndsSpan() { + when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) + .thenReturn(attemptHandle); + tracer.attemptStarted(new Object(), 1); + + Throwable error = new RuntimeException("fail"); + tracer.attemptFailedRetriesExhausted(error); + + verify(attemptHandle).recordError(error); + verify(attemptHandle).end(); + } + + @Test + void testAttemptPermanentFailure_recordsErrorAndEndsSpan() { + when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) + .thenReturn(attemptHandle); + tracer.attemptStarted(new Object(), 1); + + Throwable error = new RuntimeException("fail"); + tracer.attemptPermanentFailure(error); + + verify(attemptHandle).recordError(error); + verify(attemptHandle).end(); + } + + @Test + void testInScope_returnsOperationScopeWhenNoAttempt() { + Scope scope = mock(Scope.class); + when(recorder.inScope(operationHandle)).thenReturn(scope); + + assertThat(tracer.inScope()).isEqualTo(scope); + } + + @Test + void testInScope_returnsAttemptScopeWhenAttemptInProgress() { + when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) + .thenReturn(attemptHandle); + tracer.attemptStarted(new Object(), 1); + + Scope scope = mock(Scope.class); + when(recorder.inScope(attemptHandle)).thenReturn(scope); + + assertThat(tracer.inScope()).isEqualTo(scope); + } + + @Test + void testAddOperationAttributes_passedToOperationSpan() { + tracer.addOperationAttributes(ImmutableMap.of("op-key", "op-value")); + verify(operationHandle).setAttribute("op-key", "op-value"); + } + @Test void testAddAttemptAttributes_passedToAttemptSpan() { tracer.addAttemptAttributes(ImmutableMap.of("attempt-key", "attempt-value")); @@ -100,4 +184,14 @@ void testAddAttemptAttributes_passedToAttemptSpan() { assertThat(capturedAttributes) .containsEntry(TracingTracer.LANGUAGE_ATTRIBUTE, TracingTracer.DEFAULT_LANGUAGE); } + + @Test + void testAddAttemptAttributes_updatesActiveAttemptSpan() { + when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) + .thenReturn(attemptHandle); + tracer.attemptStarted(new Object(), 1); + + tracer.addAttemptAttributes(ImmutableMap.of("late-key", "late-value")); + verify(attemptHandle).setAttribute("late-key", "late-value"); + } } From 9c6c73716f00e550597e3656bc5d295fc8f6c690 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Feb 2026 17:01:46 -0500 Subject: [PATCH 14/32] deps: include opentelemetry context in gax --- gax-java/gax/BUILD.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/gax-java/gax/BUILD.bazel b/gax-java/gax/BUILD.bazel index 80b26ad785..15ed36bcbd 100644 --- a/gax-java/gax/BUILD.bazel +++ b/gax-java/gax/BUILD.bazel @@ -19,6 +19,7 @@ _COMPILE_DEPS = [ "@com_google_errorprone_error_prone_annotations//jar", "@com_google_guava_guava//jar", "@io_opentelemetry_opentelemetry_api//jar", + "@io_opentelemetry_opentelemetry_context//jar", "@io_opencensus_opencensus_api//jar", "@io_opencensus_opencensus_contrib_http_util//jar", "@io_grpc_grpc_java//context:context", From e77de58a1bfa7d052ad2bc49756f16dcc68ee0bb Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Mon, 9 Feb 2026 12:00:51 -0500 Subject: [PATCH 15/32] chore: simplify and remove error handling --- .../google/api/gax/tracing/TracingTracer.java | 43 ------------- .../api/gax/tracing/TracingTracerTest.java | 63 ------------------- 2 files changed, 106 deletions(-) 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 1cf30cc07e..fbc04bc223 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 @@ -84,38 +84,6 @@ public void attemptSucceeded() { endAttempt(); } - @Override - public void attemptCancelled() { - endAttempt(); - } - - /** - * Common implementation while no specific behavior is required. - * - * @param error the error to be recorded - */ - private void attemptError(Throwable error) { - if (attemptHandle != null) { - attemptHandle.recordError(error); - } - endAttempt(); - } - - @Override - public void attemptFailedDuration(Throwable error, java.time.Duration delay) { - attemptError(error); - } - - @Override - public void attemptFailedRetriesExhausted(Throwable error) { - attemptError(error); - } - - @Override - public void attemptPermanentFailure(Throwable error) { - attemptError(error); - } - private void endAttempt() { if (attemptHandle != null) { attemptHandle.end(); @@ -128,17 +96,6 @@ public void operationSucceeded() { operationHandle.end(); } - @Override - public void operationCancelled() { - operationHandle.end(); - } - - @Override - public void operationFailed(Throwable error) { - operationHandle.recordError(error); - operationHandle.end(); - } - public void addOperationAttributes(Map attributes) { this.operationAttributes.putAll(attributes); if (operationHandle != null) { 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 index fe27ba01c2..eec3871b1d 100644 --- 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 @@ -68,20 +68,6 @@ void testOperationSucceeded_endsSpan() { verify(operationHandle).end(); } - @Test - void testOperationCancelled_endsSpan() { - tracer.operationCancelled(); - verify(operationHandle).end(); - } - - @Test - void testOperationFailed_recordsErrorAndEndsSpan() { - Throwable error = new RuntimeException("fail"); - tracer.operationFailed(error); - verify(operationHandle).recordError(error); - verify(operationHandle).end(); - } - @Test void testAttemptLifecycle_startsAndEndsAttemptSpan() { when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) @@ -92,55 +78,6 @@ void testAttemptLifecycle_startsAndEndsAttemptSpan() { verify(attemptHandle).end(); } - @Test - void testAttemptCancelled_endsSpan() { - when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) - .thenReturn(attemptHandle); - tracer.attemptStarted(new Object(), 1); - tracer.attemptCancelled(); - - verify(attemptHandle).end(); - } - - @Test - void testAttemptFailedDuration_recordsErrorAndEndsSpan() { - when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) - .thenReturn(attemptHandle); - tracer.attemptStarted(new Object(), 1); - - Throwable error = new RuntimeException("fail"); - tracer.attemptFailedDuration(error, Duration.ofMillis(100)); - - verify(attemptHandle).recordError(error); - verify(attemptHandle).end(); - } - - @Test - void testAttemptFailedRetriesExhausted_recordsErrorAndEndsSpan() { - when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) - .thenReturn(attemptHandle); - tracer.attemptStarted(new Object(), 1); - - Throwable error = new RuntimeException("fail"); - tracer.attemptFailedRetriesExhausted(error); - - verify(attemptHandle).recordError(error); - verify(attemptHandle).end(); - } - - @Test - void testAttemptPermanentFailure_recordsErrorAndEndsSpan() { - when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) - .thenReturn(attemptHandle); - tracer.attemptStarted(new Object(), 1); - - Throwable error = new RuntimeException("fail"); - tracer.attemptPermanentFailure(error); - - verify(attemptHandle).recordError(error); - verify(attemptHandle).end(); - } - @Test void testInScope_returnsOperationScopeWhenNoAttempt() { Scope scope = mock(Scope.class); From 8f07f61f4ff8a85854f3fc9fae0be01c5035500a Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 10 Feb 2026 10:49:39 -0500 Subject: [PATCH 16/32] chore: review refactor - Rename SpanHandle to GaxSpan - Simplify adding op and attempt attributes - Use HashMap instead of ConcurrentHashMap - Remove error handling (for now) --- .../tracing/OpenTelemetryTracingRecorder.java | 32 ++++--------- .../api/gax/tracing/TracingRecorder.java | 12 ++--- .../google/api/gax/tracing/TracingTracer.java | 30 ++++-------- .../api/gax/tracing/TracingTracerFactory.java | 13 +++-- .../OpenTelemetryTracingRecorderTest.java | 23 ++------- .../gax/tracing/TracingTracerFactoryTest.java | 9 ++-- .../api/gax/tracing/TracingTracerTest.java | 47 +++---------------- .../showcase/v1beta1/it/ITOtelTracing.java | 5 +- 8 files changed, 48 insertions(+), 123 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 1197a42e8a..d84ee8f597 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 @@ -36,7 +36,6 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; @@ -56,12 +55,12 @@ public OpenTelemetryTracingRecorder(OpenTelemetry openTelemetry) { } @Override - public SpanHandle startSpan(String name, Map attributes) { + public GaxSpan startSpan(String name, Map attributes) { return startSpan(name, attributes, null); } @Override - public SpanHandle startSpan(String name, Map attributes, SpanHandle parent) { + public GaxSpan startSpan(String name, Map attributes, GaxSpan parent) { SpanBuilder spanBuilder = tracer.spanBuilder(name); // Operation and Attempt spans are INTERNAL and CLIENT respectively. @@ -75,29 +74,29 @@ public SpanHandle startSpan(String name, Map attributes, SpanHan attributes.forEach((k, v) -> spanBuilder.setAttribute(k, v)); } - if (parent instanceof OtelSpanHandle) { - spanBuilder.setParent(Context.current().with(((OtelSpanHandle) parent).span)); + if (parent instanceof OtelGaxSpan) { + spanBuilder.setParent(Context.current().with(((OtelGaxSpan) parent).span)); } Span span = spanBuilder.startSpan(); - return new OtelSpanHandle(span); + return new OtelGaxSpan(span); } @Override @SuppressWarnings("MustBeClosedChecker") - public ApiTracer.Scope inScope(SpanHandle handle) { - if (handle instanceof OtelSpanHandle) { - Scope scope = ((OtelSpanHandle) handle).span.makeCurrent(); + public ApiTracer.Scope inScope(GaxSpan handle) { + if (handle instanceof OtelGaxSpan) { + Scope scope = ((OtelGaxSpan) handle).span.makeCurrent(); return scope::close; } return () -> {}; } - private static class OtelSpanHandle implements SpanHandle { + private static class OtelGaxSpan implements GaxSpan { private final Span span; - private OtelSpanHandle(Span span) { + private OtelGaxSpan(Span span) { this.span = span; } @@ -105,16 +104,5 @@ private OtelSpanHandle(Span span) { public void end() { span.end(); } - - @Override - public void recordError(Throwable error) { - span.recordException(error); - span.setStatus(StatusCode.ERROR); - } - - @Override - public void setAttribute(String key, String value) { - span.setAttribute(key, value); - } } } 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 1dfc9264c0..f6878614d0 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 @@ -44,25 +44,21 @@ @InternalApi public interface TracingRecorder { /** Starts a span and returns a handle to manage its lifecycle. */ - SpanHandle startSpan(String name, Map attributes); + GaxSpan startSpan(String name, Map attributes); /** Starts a span with a parent and returns a handle to manage its lifecycle. */ - SpanHandle startSpan(String name, Map attributes, SpanHandle parent); + GaxSpan startSpan(String name, Map attributes, GaxSpan parent); /** * Installs the span into the current thread-local context. * * @return a scope that must be closed to remove the span from the context. */ - default ApiTracer.Scope inScope(SpanHandle handle) { + default ApiTracer.Scope inScope(GaxSpan handle) { return () -> {}; } - interface SpanHandle { + interface GaxSpan { void end(); - - void recordError(Throwable error); - - void setAttribute(String key, String value); } } 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 fbc04bc223..b5d9c47868 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,6 @@ import com.google.api.core.InternalApi; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; @BetaApi @InternalApi @@ -45,17 +44,20 @@ public class TracingTracer extends BaseApiTracer { public static final String DEFAULT_LANGUAGE = "Java"; private final TracingRecorder recorder; - private final Map operationAttributes; private final Map attemptAttributes; private final String attemptSpanName; - private final TracingRecorder.SpanHandle operationHandle; - private TracingRecorder.SpanHandle attemptHandle; + private final TracingRecorder.GaxSpan operationHandle; + private TracingRecorder.GaxSpan attemptHandle; - public TracingTracer(TracingRecorder recorder, String operationSpanName, String attemptSpanName) { + public TracingTracer( + TracingRecorder recorder, + String operationSpanName, + String attemptSpanName, + Map operationAttributes, + Map attemptAttributes) { this.recorder = recorder; this.attemptSpanName = attemptSpanName; - this.operationAttributes = new ConcurrentHashMap<>(); - this.attemptAttributes = new ConcurrentHashMap<>(); + this.attemptAttributes = attemptAttributes; this.attemptAttributes.put(LANGUAGE_ATTRIBUTE, DEFAULT_LANGUAGE); // Start the long-lived operation span. @@ -95,18 +97,4 @@ private void endAttempt() { public void operationSucceeded() { operationHandle.end(); } - - public void addOperationAttributes(Map attributes) { - this.operationAttributes.putAll(attributes); - if (operationHandle != null) { - attributes.forEach((k, v) -> operationHandle.setAttribute(k, v)); - } - } - - public void addAttemptAttributes(Map attributes) { - this.attemptAttributes.putAll(attributes); - if (attemptHandle != null) { - attributes.forEach((k, v) -> attemptHandle.setAttribute(k, v)); - } - } } 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 0be17a3d4b..f7c4f10266 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,8 +70,8 @@ public TracingTracerFactory( Map attemptAttributes) { this.tracingRecorder = tracingRecorder; - this.operationAttributes = ImmutableMap.copyOf(operationAttributes); - this.attemptAttributes = ImmutableMap.copyOf(attemptAttributes); + this.operationAttributes = new HashMap<>(operationAttributes); + this.attemptAttributes = new HashMap<>(attemptAttributes); } @Override @@ -83,9 +83,12 @@ public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType op String attemptSpanName = spanName.getClientName() + "/" + spanName.getMethodName() + "/attempt"; TracingTracer tracingTracer = - new TracingTracer(tracingRecorder, operationSpanName, attemptSpanName); - tracingTracer.addOperationAttributes(operationAttributes); - tracingTracer.addAttemptAttributes(attemptAttributes); + new TracingTracer( + tracingRecorder, + operationSpanName, + attemptSpanName, + this.operationAttributes, + this.attemptAttributes); return tracingTracer; } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java index 93c8dbc1bf..0c310fd781 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java @@ -40,7 +40,6 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; import java.util.Map; @@ -81,7 +80,7 @@ void testStartSpan_operation_isInternal() { @Test void testStartSpan_attempt_isClient() { String spanName = "attempt-span"; - TracingRecorder.SpanHandle parent = mock(TracingRecorder.SpanHandle.class); + TracingRecorder.GaxSpan parent = mock(TracingRecorder.GaxSpan.class); when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); @@ -102,7 +101,7 @@ void testStartSpan_recordsSpan() { when(spanBuilder.setAttribute("key1", "value1")).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); - TracingRecorder.SpanHandle handle = recorder.startSpan(spanName, attributes); + TracingRecorder.GaxSpan handle = recorder.startSpan(spanName, attributes); handle.end(); verify(span).end(); @@ -116,7 +115,7 @@ void testInScope_managesContext() { when(spanBuilder.startSpan()).thenReturn(span); when(span.makeCurrent()).thenReturn(scope); - TracingRecorder.SpanHandle handle = recorder.startSpan(spanName, null); + TracingRecorder.GaxSpan handle = recorder.startSpan(spanName, null); try (ApiTracer.Scope ignored = recorder.inScope(handle)) { // do nothing } @@ -124,20 +123,4 @@ void testInScope_managesContext() { verify(span).makeCurrent(); verify(scope).close(); } - - @Test - void testRecordError_setsErrorStatus() { - String spanName = "error-span"; - Throwable error = new RuntimeException("test error"); - - when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); - when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); - when(spanBuilder.startSpan()).thenReturn(span); - - TracingRecorder.SpanHandle handle = recorder.startSpan(spanName, null); - handle.recordError(error); - - verify(span).recordException(error); - verify(span).setStatus(StatusCode.ERROR); - } } 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 index e4e1595af3..039d6fff5a 100644 --- 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 @@ -50,8 +50,7 @@ class TracingTracerFactoryTest { @Test void testNewTracer_createsOpenTelemetryTracingTracer() { TracingRecorder recorder = mock(TracingRecorder.class); - when(recorder.startSpan(anyString(), anyMap())) - .thenReturn(mock(TracingRecorder.SpanHandle.class)); + when(recorder.startSpan(anyString(), anyMap())).thenReturn(mock(TracingRecorder.GaxSpan.class)); TracingTracerFactory factory = new TracingTracerFactory(recorder); ApiTracer tracer = @@ -63,7 +62,7 @@ void testNewTracer_createsOpenTelemetryTracingTracer() { @Test void testNewTracer_addsAttributes() { TracingRecorder recorder = mock(TracingRecorder.class); - TracingRecorder.SpanHandle operationHandle = mock(TracingRecorder.SpanHandle.class); + TracingRecorder.GaxSpan operationHandle = mock(TracingRecorder.GaxSpan.class); when(recorder.startSpan(anyString(), anyMap())).thenReturn(operationHandle); TracingTracerFactory factory = @@ -86,7 +85,7 @@ void testNewTracer_addsAttributes() { @Test void testWithContext_addsInferredAttributes() { TracingRecorder recorder = mock(TracingRecorder.class); - TracingRecorder.SpanHandle operationHandle = mock(TracingRecorder.SpanHandle.class); + TracingRecorder.GaxSpan operationHandle = mock(TracingRecorder.GaxSpan.class); when(recorder.startSpan(anyString(), anyMap())).thenReturn(operationHandle); EndpointContext endpointContext = mock(EndpointContext.class); @@ -116,7 +115,7 @@ void testWithContext_addsInferredAttributes() { @Test void testWithContext_noEndpointContext_doesNotAddAttributes() { TracingRecorder recorder = mock(TracingRecorder.class); - TracingRecorder.SpanHandle operationHandle = mock(TracingRecorder.SpanHandle.class); + TracingRecorder.GaxSpan operationHandle = mock(TracingRecorder.GaxSpan.class); when(recorder.startSpan(anyString(), anyMap())).thenReturn(operationHandle); ApiTracerContext context = ApiTracerContext.newBuilder().build(); 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 index eec3871b1d..30b943941e 100644 --- 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 @@ -37,21 +37,18 @@ import static org.mockito.Mockito.when; import com.google.api.gax.tracing.ApiTracer.Scope; -import com.google.common.collect.ImmutableMap; -import java.time.Duration; -import java.util.Map; +import java.util.HashMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) class TracingTracerTest { @Mock private TracingRecorder recorder; - @Mock private TracingRecorder.SpanHandle operationHandle; - @Mock private TracingRecorder.SpanHandle attemptHandle; + @Mock private TracingRecorder.GaxSpan operationHandle; + @Mock private TracingRecorder.GaxSpan attemptHandle; private TracingTracer tracer; private static final String OPERATION_SPAN_NAME = "Service.Method/operation"; private static final String ATTEMPT_SPAN_NAME = "Service/Method/attempt"; @@ -59,7 +56,9 @@ class TracingTracerTest { @BeforeEach void setUp() { when(recorder.startSpan(eq(OPERATION_SPAN_NAME), anyMap())).thenReturn(operationHandle); - tracer = new TracingTracer(recorder, OPERATION_SPAN_NAME, ATTEMPT_SPAN_NAME); + tracer = + new TracingTracer( + recorder, OPERATION_SPAN_NAME, ATTEMPT_SPAN_NAME, new HashMap<>(), new HashMap<>()); } @Test @@ -97,38 +96,4 @@ void testInScope_returnsAttemptScopeWhenAttemptInProgress() { assertThat(tracer.inScope()).isEqualTo(scope); } - - @Test - void testAddOperationAttributes_passedToOperationSpan() { - tracer.addOperationAttributes(ImmutableMap.of("op-key", "op-value")); - verify(operationHandle).setAttribute("op-key", "op-value"); - } - - @Test - void testAddAttemptAttributes_passedToAttemptSpan() { - tracer.addAttemptAttributes(ImmutableMap.of("attempt-key", "attempt-value")); - - when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) - .thenReturn(attemptHandle); - tracer.attemptStarted(new Object(), 1); - - ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); - verify(recorder) - .startSpan(eq(ATTEMPT_SPAN_NAME), attributesCaptor.capture(), eq(operationHandle)); - - Map capturedAttributes = attributesCaptor.getValue(); - assertThat(capturedAttributes).containsEntry("attempt-key", "attempt-value"); - assertThat(capturedAttributes) - .containsEntry(TracingTracer.LANGUAGE_ATTRIBUTE, TracingTracer.DEFAULT_LANGUAGE); - } - - @Test - void testAddAttemptAttributes_updatesActiveAttemptSpan() { - when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) - .thenReturn(attemptHandle); - tracer.attemptStarted(new Object(), 1); - - tracer.addAttemptAttributes(ImmutableMap.of("late-key", "late-value")); - verify(attemptHandle).setAttribute("late-key", "late-value"); - } } 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 191449f67a..caa6f4e615 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 @@ -49,6 +49,7 @@ import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterEach; @@ -222,7 +223,9 @@ void testTracing_withCustomAttributes() throws Exception { @Test void testInScope_managesOtelContext() { OpenTelemetryTracingRecorder recorder = new OpenTelemetryTracingRecorder(openTelemetrySdk); - TracingTracer tracer = new TracingTracer(recorder, "operation-span", "attempt-span"); + TracingTracer tracer = + new TracingTracer( + recorder, "operation-span", "attempt-span", new HashMap<>(), new HashMap<>()); // Initially, there should be no current span assertThat(Span.current().getSpanContext().isValid()).isFalse(); From 2cfcd68203aeba6d77847019c78e9a17c50d6f16 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 10 Feb 2026 10:56:02 -0500 Subject: [PATCH 17/32] chore: make TracingTracerFactory(recorder, opAtts, atAtts) package private --- .../api/gax/tracing/TracingTracerFactory.java | 12 +++--- .../showcase/v1beta1/it/ITOtelTracing.java | 38 ------------------- 2 files changed, 7 insertions(+), 43 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 f7c4f10266..95568e6ed3 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,7 +32,7 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; -import com.google.common.collect.ImmutableMap; +import com.google.common.annotations.VisibleForTesting; import java.util.HashMap; import java.util.Map; @@ -55,16 +55,18 @@ public class TracingTracerFactory implements ApiTracerFactory { /** Mapping of client attributes that are set for every TracingTracer at attempt level */ private final Map attemptAttributes; - /** Creates a TracingTracerFactory with no additional client level attributes. */ + /** Creates a TracingTracerFactory */ public TracingTracerFactory(TracingRecorder tracingRecorder) { - this(tracingRecorder, ImmutableMap.of(), ImmutableMap.of()); + this(tracingRecorder, new HashMap<>(), new HashMap<>()); } /** * Pass in a Map of client level attributes which will be added to every single TracingTracer - * created from the ApiTracerFactory. + * created from the ApiTracerFactory. This is package private since span attributes are determined + * internally. */ - public TracingTracerFactory( + @VisibleForTesting + TracingTracerFactory( TracingRecorder tracingRecorder, Map operationAttributes, Map attemptAttributes) { 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 caa6f4e615..8d4062ca78 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 @@ -36,7 +36,6 @@ import com.google.api.gax.tracing.OpenTelemetryTracingRecorder; import com.google.api.gax.tracing.TracingTracer; import com.google.api.gax.tracing.TracingTracerFactory; -import com.google.common.collect.ImmutableMap; import com.google.showcase.v1beta1.EchoClient; import com.google.showcase.v1beta1.EchoRequest; import com.google.showcase.v1beta1.it.util.TestClientInitializer; @@ -51,7 +50,6 @@ import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import java.util.HashMap; import java.util.List; -import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -179,42 +177,6 @@ void testTracing_successfulEcho_httpjson() throws Exception { } } - @Test - void testTracing_withCustomAttributes() throws Exception { - Map opAttributes = ImmutableMap.of("op-key", "op-value"); - Map atAttributes = ImmutableMap.of("at-key", "at-value"); - TracingTracerFactory tracingFactory = - new TracingTracerFactory( - new OpenTelemetryTracingRecorder(openTelemetrySdk), opAttributes, atAttributes); - - try (EchoClient client = - TestClientInitializer.createGrpcEchoClientOpentelemetry(tracingFactory)) { - - client.echo(EchoRequest.newBuilder().setContent("attr-test").build()); - - List spans = waitForSpans(2); - - SpanData operationSpan = - spans.stream() - .filter(span -> span.getName().equals("Echo.Echo/operation")) - .findFirst() - .orElseThrow( - () -> new AssertionError("Operation span 'Echo/Echo/operation' not found")); - assertThat(operationSpan.getKind()).isEqualTo(SpanKind.INTERNAL); - assertThat(operationSpan.getAttributes().get(AttributeKey.stringKey("op-key"))) - .isEqualTo("op-value"); - - SpanData attemptSpan = - spans.stream() - .filter(span -> span.getName().equals("Echo/Echo/attempt")) - .findFirst() - .orElseThrow(() -> new AssertionError("Attempt span 'Echo/Echo/attempt' not found")); - assertThat(attemptSpan.getKind()).isEqualTo(SpanKind.CLIENT); - assertThat(attemptSpan.getAttributes().get(AttributeKey.stringKey("at-key"))) - .isEqualTo("at-value"); - } - } - /** * Confirms that the current span as per Otel context is only valid when the inScope method is * called. More detailedly, this is to confirm gax thread management, which uses the inScope() From 2415c6b02eaee4324894a7e237868abe9a89a4a1 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 10 Feb 2026 12:20:28 -0500 Subject: [PATCH 18/32] chore: remove unnecessary inScope --- .../tracing/OpenTelemetryTracingRecorder.java | 11 ------- .../api/gax/tracing/TracingRecorder.java | 9 ------ .../google/api/gax/tracing/TracingTracer.java | 10 ------- .../OpenTelemetryTracingRecorderTest.java | 17 ----------- .../api/gax/tracing/TracingTracerTest.java | 23 --------------- .../showcase/v1beta1/it/ITOtelTracing.java | 29 ------------------- 6 files changed, 99 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 d84ee8f597..b111ad305f 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 @@ -38,7 +38,6 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; import java.util.Map; /** @@ -83,16 +82,6 @@ public GaxSpan startSpan(String name, Map attributes, GaxSpan pa return new OtelGaxSpan(span); } - @Override - @SuppressWarnings("MustBeClosedChecker") - public ApiTracer.Scope inScope(GaxSpan handle) { - if (handle instanceof OtelGaxSpan) { - Scope scope = ((OtelGaxSpan) handle).span.makeCurrent(); - return scope::close; - } - return () -> {}; - } - private static class OtelGaxSpan implements GaxSpan { private final Span span; 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 f6878614d0..5a09042334 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 @@ -49,15 +49,6 @@ public interface TracingRecorder { /** Starts a span with a parent and returns a handle to manage its lifecycle. */ GaxSpan startSpan(String name, Map attributes, GaxSpan parent); - /** - * Installs the span into the current thread-local context. - * - * @return a scope that must be closed to remove the span from the context. - */ - default ApiTracer.Scope inScope(GaxSpan handle) { - return () -> {}; - } - interface GaxSpan { void end(); } 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 b5d9c47868..8a4a875abd 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 @@ -64,16 +64,6 @@ public TracingTracer( this.operationHandle = recorder.startSpan(operationSpanName, operationAttributes); } - @Override - public Scope inScope() { - // If an attempt is in progress, make it current so downstream spans are its children. - // Otherwise, make the operation span current. - if (attemptHandle != null) { - return recorder.inScope(attemptHandle); - } - return recorder.inScope(operationHandle); - } - @Override public void attemptStarted(Object request, int attemptNumber) { Map attemptAttributes = new HashMap<>(this.attemptAttributes); diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java index 0c310fd781..c0edf7d644 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java @@ -106,21 +106,4 @@ void testStartSpan_recordsSpan() { verify(span).end(); } - - @Test - void testInScope_managesContext() { - String spanName = "test-span"; - when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); - when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); - when(spanBuilder.startSpan()).thenReturn(span); - when(span.makeCurrent()).thenReturn(scope); - - TracingRecorder.GaxSpan handle = recorder.startSpan(spanName, null); - try (ApiTracer.Scope ignored = recorder.inScope(handle)) { - // do nothing - } - - verify(span).makeCurrent(); - verify(scope).close(); - } } 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 index 30b943941e..556ba1d34d 100644 --- 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 @@ -29,14 +29,11 @@ */ package com.google.api.gax.tracing; -import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.google.api.gax.tracing.ApiTracer.Scope; import java.util.HashMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -76,24 +73,4 @@ void testAttemptLifecycle_startsAndEndsAttemptSpan() { verify(attemptHandle).end(); } - - @Test - void testInScope_returnsOperationScopeWhenNoAttempt() { - Scope scope = mock(Scope.class); - when(recorder.inScope(operationHandle)).thenReturn(scope); - - assertThat(tracer.inScope()).isEqualTo(scope); - } - - @Test - void testInScope_returnsAttemptScopeWhenAttemptInProgress() { - when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) - .thenReturn(attemptHandle); - tracer.attemptStarted(new Object(), 1); - - Scope scope = mock(Scope.class); - when(recorder.inScope(attemptHandle)).thenReturn(scope); - - assertThat(tracer.inScope()).isEqualTo(scope); - } } 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 8d4062ca78..a5ea84dbd6 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 @@ -32,7 +32,6 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.api.gax.tracing.ApiTracer; import com.google.api.gax.tracing.OpenTelemetryTracingRecorder; import com.google.api.gax.tracing.TracingTracer; import com.google.api.gax.tracing.TracingTracerFactory; @@ -41,14 +40,12 @@ import com.google.showcase.v1beta1.it.util.TestClientInitializer; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; 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.HashMap; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -176,30 +173,4 @@ void testTracing_successfulEcho_httpjson() throws Exception { .isEqualTo(SHOWCASE_SERVER_ADDRESS); } } - - /** - * Confirms that the current span as per Otel context is only valid when the inScope method is - * called. More detailedly, this is to confirm gax thread management, which uses the inScope() - * method, correctly brings the selected span into the context. - */ - @Test - void testInScope_managesOtelContext() { - OpenTelemetryTracingRecorder recorder = new OpenTelemetryTracingRecorder(openTelemetrySdk); - TracingTracer tracer = - new TracingTracer( - recorder, "operation-span", "attempt-span", new HashMap<>(), new HashMap<>()); - - // Initially, there should be no current span - assertThat(Span.current().getSpanContext().isValid()).isFalse(); - - try (ApiTracer.Scope ignored = tracer.inScope()) { - // Inside the scope, the current span should be the operation span - assertThat(Span.current().getSpanContext().isValid()).isTrue(); - // We can't easily check the name of the current span in OTel without more complex setup, - // but we can verify it's active. - } - - // After the scope is closed, there should be no current span again - assertThat(Span.current().getSpanContext().isValid()).isFalse(); - } } From 0f5e9b5d03f0f85fb65af421a57d7a4187c6ce54 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 10 Feb 2026 12:39:12 -0500 Subject: [PATCH 19/32] chore: rename startSpan to createSpan --- .../gax/tracing/OpenTelemetryTracingRecorder.java | 6 +++--- .../google/api/gax/tracing/TracingRecorder.java | 4 ++-- .../com/google/api/gax/tracing/TracingTracer.java | 4 ++-- .../tracing/OpenTelemetryTracingRecorderTest.java | 12 ++++++------ .../api/gax/tracing/TracingTracerFactoryTest.java | 15 ++++++++------- .../google/api/gax/tracing/TracingTracerTest.java | 4 ++-- 6 files changed, 23 insertions(+), 22 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 b111ad305f..75bfe491fb 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 @@ -54,12 +54,12 @@ public OpenTelemetryTracingRecorder(OpenTelemetry openTelemetry) { } @Override - public GaxSpan startSpan(String name, Map attributes) { - return startSpan(name, attributes, null); + public GaxSpan createSpan(String name, Map attributes) { + return createSpan(name, attributes, null); } @Override - public GaxSpan startSpan(String name, Map attributes, GaxSpan parent) { + public GaxSpan createSpan(String name, Map attributes, GaxSpan parent) { SpanBuilder spanBuilder = tracer.spanBuilder(name); // Operation and Attempt spans are INTERNAL and CLIENT respectively. 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 5a09042334..51935ce1cd 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 @@ -44,10 +44,10 @@ @InternalApi public interface TracingRecorder { /** Starts a span and returns a handle to manage its lifecycle. */ - GaxSpan startSpan(String name, Map attributes); + GaxSpan createSpan(String name, Map attributes); /** Starts a span with a parent and returns a handle to manage its lifecycle. */ - GaxSpan startSpan(String name, Map attributes, GaxSpan parent); + GaxSpan createSpan(String name, Map attributes, GaxSpan parent); interface GaxSpan { void end(); 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 8a4a875abd..b17bbeb57c 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 @@ -61,14 +61,14 @@ public TracingTracer( this.attemptAttributes.put(LANGUAGE_ATTRIBUTE, DEFAULT_LANGUAGE); // Start the long-lived operation span. - this.operationHandle = recorder.startSpan(operationSpanName, operationAttributes); + this.operationHandle = recorder.createSpan(operationSpanName, operationAttributes); } @Override public void attemptStarted(Object request, int attemptNumber) { Map attemptAttributes = new HashMap<>(this.attemptAttributes); // Start the specific attempt span with the operation span as parent - this.attemptHandle = recorder.startSpan(attemptSpanName, attemptAttributes, operationHandle); + this.attemptHandle = recorder.createSpan(attemptSpanName, attemptAttributes, operationHandle); } @Override diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java index c0edf7d644..bf95e26694 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java @@ -66,19 +66,19 @@ void setUp() { } @Test - void testStartSpan_operation_isInternal() { + void testCreateSpan_operation_isInternal() { String spanName = "operation-span"; when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); - recorder.startSpan(spanName, null); + recorder.createSpan(spanName, null); verify(spanBuilder).setSpanKind(SpanKind.INTERNAL); } @Test - void testStartSpan_attempt_isClient() { + void testCreateSpan_attempt_isClient() { String spanName = "attempt-span"; TracingRecorder.GaxSpan parent = mock(TracingRecorder.GaxSpan.class); @@ -86,13 +86,13 @@ void testStartSpan_attempt_isClient() { when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); - recorder.startSpan(spanName, null, parent); + recorder.createSpan(spanName, null, parent); verify(spanBuilder).setSpanKind(SpanKind.CLIENT); } @Test - void testStartSpan_recordsSpan() { + void testCreateSpan_recordsSpan() { String spanName = "test-span"; Map attributes = ImmutableMap.of("key1", "value1"); @@ -101,7 +101,7 @@ void testStartSpan_recordsSpan() { when(spanBuilder.setAttribute("key1", "value1")).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); - TracingRecorder.GaxSpan handle = recorder.startSpan(spanName, attributes); + TracingRecorder.GaxSpan handle = recorder.createSpan(spanName, attributes); handle.end(); verify(span).end(); 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 index 039d6fff5a..a9ab3ce11a 100644 --- 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 @@ -50,7 +50,8 @@ class TracingTracerFactoryTest { @Test void testNewTracer_createsOpenTelemetryTracingTracer() { TracingRecorder recorder = mock(TracingRecorder.class); - when(recorder.startSpan(anyString(), anyMap())).thenReturn(mock(TracingRecorder.GaxSpan.class)); + when(recorder.createSpan(anyString(), anyMap())) + .thenReturn(mock(TracingRecorder.GaxSpan.class)); TracingTracerFactory factory = new TracingTracerFactory(recorder); ApiTracer tracer = @@ -63,7 +64,7 @@ void testNewTracer_createsOpenTelemetryTracingTracer() { void testNewTracer_addsAttributes() { TracingRecorder recorder = mock(TracingRecorder.class); TracingRecorder.GaxSpan operationHandle = mock(TracingRecorder.GaxSpan.class); - when(recorder.startSpan(anyString(), anyMap())).thenReturn(operationHandle); + when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); TracingTracerFactory factory = new TracingTracerFactory( @@ -76,7 +77,7 @@ void testNewTracer_addsAttributes() { ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); verify(recorder, atLeastOnce()) - .startSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); + .createSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); Map attemptAttributes = attributesCaptor.getValue(); assertThat(attemptAttributes).containsEntry("server.port", "443"); @@ -86,7 +87,7 @@ void testNewTracer_addsAttributes() { void testWithContext_addsInferredAttributes() { TracingRecorder recorder = mock(TracingRecorder.class); TracingRecorder.GaxSpan operationHandle = mock(TracingRecorder.GaxSpan.class); - when(recorder.startSpan(anyString(), anyMap())).thenReturn(operationHandle); + when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); EndpointContext endpointContext = mock(EndpointContext.class); when(endpointContext.resolvedServerAddress()).thenReturn("example.com"); @@ -105,7 +106,7 @@ void testWithContext_addsInferredAttributes() { ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); verify(recorder, atLeastOnce()) - .startSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); + .createSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); Map attemptAttributes = attributesCaptor.getValue(); assertThat(attemptAttributes) @@ -116,7 +117,7 @@ void testWithContext_addsInferredAttributes() { void testWithContext_noEndpointContext_doesNotAddAttributes() { TracingRecorder recorder = mock(TracingRecorder.class); TracingRecorder.GaxSpan operationHandle = mock(TracingRecorder.GaxSpan.class); - when(recorder.startSpan(anyString(), anyMap())).thenReturn(operationHandle); + when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); ApiTracerContext context = ApiTracerContext.newBuilder().build(); @@ -131,7 +132,7 @@ void testWithContext_noEndpointContext_doesNotAddAttributes() { ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); verify(recorder, atLeastOnce()) - .startSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); + .createSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); Map attemptAttributes = attributesCaptor.getValue(); assertThat(attemptAttributes).doesNotContainKey(TracingTracer.SERVER_ADDRESS_ATTRIBUTE); 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 index 556ba1d34d..aede62658c 100644 --- 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 @@ -52,7 +52,7 @@ class TracingTracerTest { @BeforeEach void setUp() { - when(recorder.startSpan(eq(OPERATION_SPAN_NAME), anyMap())).thenReturn(operationHandle); + when(recorder.createSpan(eq(OPERATION_SPAN_NAME), anyMap())).thenReturn(operationHandle); tracer = new TracingTracer( recorder, OPERATION_SPAN_NAME, ATTEMPT_SPAN_NAME, new HashMap<>(), new HashMap<>()); @@ -66,7 +66,7 @@ void testOperationSucceeded_endsSpan() { @Test void testAttemptLifecycle_startsAndEndsAttemptSpan() { - when(recorder.startSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) + when(recorder.createSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) .thenReturn(attemptHandle); tracer.attemptStarted(new Object(), 1); tracer.attemptSucceeded(); From 0c3bd22ec4a2a5ab5c17af955272b7334fe806c2 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 10 Feb 2026 12:51:02 -0500 Subject: [PATCH 20/32] chore: use server address instead of endpoint context --- .../src/main/java/com/google/api/gax/rpc/ClientContext.java | 4 +++- .../java/com/google/api/gax/tracing/ApiTracerContext.java | 5 ++--- .../com/google/api/gax/tracing/TracingTracerFactory.java | 6 ++---- .../google/api/gax/tracing/TracingTracerFactoryTest.java | 6 +----- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index c3ff8c2485..e782fdd926 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -271,7 +271,9 @@ public static ClientContext create(StubSettings settings) throws IOException { backgroundResources.add(watchdog); } ApiTracerContext apiTracerContext = - ApiTracerContext.newBuilder().setEndpointContext(endpointContext).build(); + ApiTracerContext.newBuilder() + .setServerAddress(endpointContext.resolvedServerAddress()) + .build(); ApiTracerFactory apiTracerFactory = settings.getTracerFactory().withContext(apiTracerContext); return newBuilder() diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java index 41ea842a3b..1c92bd8db3 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java @@ -31,7 +31,6 @@ package com.google.api.gax.tracing; import com.google.api.core.InternalApi; -import com.google.api.gax.rpc.EndpointContext; import com.google.auto.value.AutoValue; import javax.annotation.Nullable; @@ -46,7 +45,7 @@ public abstract class ApiTracerContext { @Nullable - public abstract EndpointContext getEndpointContext(); + public abstract String getServerAddress(); public static Builder newBuilder() { return new AutoValue_ApiTracerContext.Builder(); @@ -54,7 +53,7 @@ public static Builder newBuilder() { @AutoValue.Builder public abstract static class Builder { - public abstract Builder setEndpointContext(EndpointContext endpointContext); + public abstract Builder setServerAddress(String serverAddress); public abstract ApiTracerContext build(); } 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 95568e6ed3..12470a8437 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 @@ -97,10 +97,8 @@ public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType op @Override public ApiTracerFactory withContext(ApiTracerContext context) { Map newAttemptAttributes = new HashMap<>(this.attemptAttributes); - if (context.getEndpointContext() != null) { - newAttemptAttributes.put( - TracingTracer.SERVER_ADDRESS_ATTRIBUTE, - context.getEndpointContext().resolvedServerAddress()); + if (context.getServerAddress() != null) { + newAttemptAttributes.put(TracingTracer.SERVER_ADDRESS_ATTRIBUTE, context.getServerAddress()); } return new TracingTracerFactory(tracingRecorder, operationAttributes, newAttemptAttributes); } 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 index a9ab3ce11a..252c347f7c 100644 --- 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 @@ -39,7 +39,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.google.api.gax.rpc.EndpointContext; import com.google.common.collect.ImmutableMap; import java.util.Map; import org.junit.jupiter.api.Test; @@ -89,11 +88,8 @@ void testWithContext_addsInferredAttributes() { TracingRecorder.GaxSpan operationHandle = mock(TracingRecorder.GaxSpan.class); when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); - EndpointContext endpointContext = mock(EndpointContext.class); - when(endpointContext.resolvedServerAddress()).thenReturn("example.com"); - ApiTracerContext context = - ApiTracerContext.newBuilder().setEndpointContext(endpointContext).build(); + ApiTracerContext.newBuilder().setServerAddress("example.com").build(); TracingTracerFactory factory = new TracingTracerFactory(recorder); ApiTracerFactory factoryWithContext = factory.withContext(context); From bdeb291537100dfdd6febb1cea89f1cd020c79fa Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 10 Feb 2026 14:56:47 -0500 Subject: [PATCH 21/32] chore: rename classes --- ...acingTracer.java => AppCentricTracer.java} | 12 +++--- ...tory.java => AppCentricTracerFactory.java} | 41 ++++++++++--------- ...r.java => OpenTelemetryTraceRecorder.java} | 4 +- ...racingRecorder.java => TraceRecorder.java} | 6 +-- ....java => AppCentricTracerFactoryTest.java} | 35 ++++++++-------- ...cerTest.java => AppCentricTracerTest.java} | 12 +++--- ...va => OpenTelemetryTraceRecorderTest.java} | 10 ++--- .../showcase/v1beta1/it/ITOtelTracing.java | 22 +++++----- 8 files changed, 70 insertions(+), 72 deletions(-) rename gax-java/gax/src/main/java/com/google/api/gax/tracing/{TracingTracer.java => AppCentricTracer.java} (92%) rename gax-java/gax/src/main/java/com/google/api/gax/tracing/{TracingTracerFactory.java => AppCentricTracerFactory.java} (73%) rename gax-java/gax/src/main/java/com/google/api/gax/tracing/{OpenTelemetryTracingRecorder.java => OpenTelemetryTraceRecorder.java} (95%) rename gax-java/gax/src/main/java/com/google/api/gax/tracing/{TracingRecorder.java => TraceRecorder.java} (91%) rename gax-java/gax/src/test/java/com/google/api/gax/tracing/{TracingTracerFactoryTest.java => AppCentricTracerFactoryTest.java} (80%) rename gax-java/gax/src/test/java/com/google/api/gax/tracing/{TracingTracerTest.java => AppCentricTracerTest.java} (92%) rename gax-java/gax/src/test/java/com/google/api/gax/tracing/{OpenTelemetryTracingRecorderTest.java => OpenTelemetryTraceRecorderTest.java} (92%) 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/AppCentricTracer.java similarity index 92% rename from gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracer.java rename to gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java index b17bbeb57c..ac337f322c 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/AppCentricTracer.java @@ -37,20 +37,20 @@ @BetaApi @InternalApi -public class TracingTracer extends BaseApiTracer { +public class AppCentricTracer extends BaseApiTracer { public static final String SERVER_ADDRESS_ATTRIBUTE = "server.address"; public static final String LANGUAGE_ATTRIBUTE = "gcp.client.language"; public static final String DEFAULT_LANGUAGE = "Java"; - private final TracingRecorder recorder; + private final TraceRecorder recorder; private final Map attemptAttributes; private final String attemptSpanName; - private final TracingRecorder.GaxSpan operationHandle; - private TracingRecorder.GaxSpan attemptHandle; + private final TraceRecorder.GaxSpan operationHandle; + private TraceRecorder.GaxSpan attemptHandle; - public TracingTracer( - TracingRecorder recorder, + public AppCentricTracer( + TraceRecorder recorder, String operationSpanName, String attemptSpanName, Map operationAttributes, 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/AppCentricTracerFactory.java similarity index 73% rename from gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingTracerFactory.java rename to gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracerFactory.java index 12470a8437..c2f4261383 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/AppCentricTracerFactory.java @@ -37,40 +37,40 @@ import java.util.Map; /** - * A {@link ApiTracerFactory} to build instances of {@link TracingTracer}. + * A {@link ApiTracerFactory} to build instances of {@link AppCentricTracer}. * - *

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

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

This class is expected to be initialized once during client initialization. */ @BetaApi @InternalApi -public class TracingTracerFactory implements ApiTracerFactory { - private final TracingRecorder tracingRecorder; +public class AppCentricTracerFactory implements ApiTracerFactory { + private final TraceRecorder traceRecorder; - /** Mapping of client attributes that are set for every TracingTracer at operation level */ + /** Mapping of client attributes that are set for every AppCentricTracer at operation level */ private final Map operationAttributes; - /** Mapping of client attributes that are set for every TracingTracer at attempt level */ + /** Mapping of client attributes that are set for every AppCentricTracer at attempt level */ private final Map attemptAttributes; - /** Creates a TracingTracerFactory */ - public TracingTracerFactory(TracingRecorder tracingRecorder) { - this(tracingRecorder, new HashMap<>(), new HashMap<>()); + /** Creates a AppCentricTracerFactory */ + public AppCentricTracerFactory(TraceRecorder traceRecorder) { + this(traceRecorder, new HashMap<>(), new HashMap<>()); } /** - * Pass in a Map of client level attributes which will be added to every single TracingTracer + * Pass in a Map of client level attributes which will be added to every single AppCentricTracer * created from the ApiTracerFactory. This is package private since span attributes are determined * internally. */ @VisibleForTesting - TracingTracerFactory( - TracingRecorder tracingRecorder, + AppCentricTracerFactory( + TraceRecorder traceRecorder, Map operationAttributes, Map attemptAttributes) { - this.tracingRecorder = tracingRecorder; + this.traceRecorder = traceRecorder; this.operationAttributes = new HashMap<>(operationAttributes); this.attemptAttributes = new HashMap<>(attemptAttributes); @@ -84,22 +84,23 @@ public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType op spanName.getClientName() + "." + spanName.getMethodName() + "/operation"; String attemptSpanName = spanName.getClientName() + "/" + spanName.getMethodName() + "/attempt"; - TracingTracer tracingTracer = - new TracingTracer( - tracingRecorder, + AppCentricTracer appCentricTracer = + new AppCentricTracer( + traceRecorder, operationSpanName, attemptSpanName, this.operationAttributes, this.attemptAttributes); - return tracingTracer; + return appCentricTracer; } @Override public ApiTracerFactory withContext(ApiTracerContext context) { Map newAttemptAttributes = new HashMap<>(this.attemptAttributes); if (context.getServerAddress() != null) { - newAttemptAttributes.put(TracingTracer.SERVER_ADDRESS_ATTRIBUTE, context.getServerAddress()); + newAttemptAttributes.put( + AppCentricTracer.SERVER_ADDRESS_ATTRIBUTE, context.getServerAddress()); } - return new TracingTracerFactory(tracingRecorder, operationAttributes, newAttemptAttributes); + return new AppCentricTracerFactory(traceRecorder, operationAttributes, newAttemptAttributes); } } 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/OpenTelemetryTraceRecorder.java similarity index 95% rename from gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorder.java rename to gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java index 75bfe491fb..eff2139c57 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/OpenTelemetryTraceRecorder.java @@ -46,10 +46,10 @@ */ @BetaApi @InternalApi -public class OpenTelemetryTracingRecorder implements TracingRecorder { +public class OpenTelemetryTraceRecorder implements TraceRecorder { private final Tracer tracer; - public OpenTelemetryTracingRecorder(OpenTelemetry openTelemetry) { + public OpenTelemetryTraceRecorder(OpenTelemetry openTelemetry) { this.tracer = openTelemetry.getTracer("gax-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/TraceRecorder.java similarity index 91% rename from gax-java/gax/src/main/java/com/google/api/gax/tracing/TracingRecorder.java rename to gax-java/gax/src/main/java/com/google/api/gax/tracing/TraceRecorder.java index 51935ce1cd..0fcc9950e4 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/TraceRecorder.java @@ -36,13 +36,11 @@ /** * Provides an interface for tracing recording. The implementer is expected to use an observability - * 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. + * framework, e.g. OpenTelemetry. There should be only one instance of TraceRecorder per client. */ @BetaApi @InternalApi -public interface TracingRecorder { +public interface TraceRecorder { /** Starts a span and returns a handle to manage its lifecycle. */ GaxSpan createSpan(String name, Map attributes); 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/AppCentricTracerFactoryTest.java similarity index 80% rename from gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerFactoryTest.java rename to gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java index 252c347f7c..8bf2b3e27c 100644 --- 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/AppCentricTracerFactoryTest.java @@ -44,29 +44,28 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -class TracingTracerFactoryTest { +class AppCentricTracerFactoryTest { @Test void testNewTracer_createsOpenTelemetryTracingTracer() { - TracingRecorder recorder = mock(TracingRecorder.class); - when(recorder.createSpan(anyString(), anyMap())) - .thenReturn(mock(TracingRecorder.GaxSpan.class)); + TraceRecorder recorder = mock(TraceRecorder.class); + when(recorder.createSpan(anyString(), anyMap())).thenReturn(mock(TraceRecorder.GaxSpan.class)); - TracingTracerFactory factory = new TracingTracerFactory(recorder); + AppCentricTracerFactory factory = new AppCentricTracerFactory(recorder); ApiTracer tracer = factory.newTracer( null, SpanName.of("service", "method"), ApiTracerFactory.OperationType.Unary); - assertThat(tracer).isInstanceOf(TracingTracer.class); + assertThat(tracer).isInstanceOf(AppCentricTracer.class); } @Test void testNewTracer_addsAttributes() { - TracingRecorder recorder = mock(TracingRecorder.class); - TracingRecorder.GaxSpan operationHandle = mock(TracingRecorder.GaxSpan.class); + TraceRecorder recorder = mock(TraceRecorder.class); + TraceRecorder.GaxSpan operationHandle = mock(TraceRecorder.GaxSpan.class); when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); - TracingTracerFactory factory = - new TracingTracerFactory( + AppCentricTracerFactory factory = + new AppCentricTracerFactory( recorder, ImmutableMap.of(), ImmutableMap.of("server.port", "443")); ApiTracer tracer = factory.newTracer( @@ -84,14 +83,14 @@ void testNewTracer_addsAttributes() { @Test void testWithContext_addsInferredAttributes() { - TracingRecorder recorder = mock(TracingRecorder.class); - TracingRecorder.GaxSpan operationHandle = mock(TracingRecorder.GaxSpan.class); + TraceRecorder recorder = mock(TraceRecorder.class); + TraceRecorder.GaxSpan operationHandle = mock(TraceRecorder.GaxSpan.class); when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); ApiTracerContext context = ApiTracerContext.newBuilder().setServerAddress("example.com").build(); - TracingTracerFactory factory = new TracingTracerFactory(recorder); + AppCentricTracerFactory factory = new AppCentricTracerFactory(recorder); ApiTracerFactory factoryWithContext = factory.withContext(context); ApiTracer tracer = @@ -106,18 +105,18 @@ void testWithContext_addsInferredAttributes() { Map attemptAttributes = attributesCaptor.getValue(); assertThat(attemptAttributes) - .containsEntry(TracingTracer.SERVER_ADDRESS_ATTRIBUTE, "example.com"); + .containsEntry(AppCentricTracer.SERVER_ADDRESS_ATTRIBUTE, "example.com"); } @Test void testWithContext_noEndpointContext_doesNotAddAttributes() { - TracingRecorder recorder = mock(TracingRecorder.class); - TracingRecorder.GaxSpan operationHandle = mock(TracingRecorder.GaxSpan.class); + TraceRecorder recorder = mock(TraceRecorder.class); + TraceRecorder.GaxSpan operationHandle = mock(TraceRecorder.GaxSpan.class); when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); ApiTracerContext context = ApiTracerContext.newBuilder().build(); - TracingTracerFactory factory = new TracingTracerFactory(recorder); + AppCentricTracerFactory factory = new AppCentricTracerFactory(recorder); ApiTracerFactory factoryWithContext = factory.withContext(context); ApiTracer tracer = @@ -131,6 +130,6 @@ void testWithContext_noEndpointContext_doesNotAddAttributes() { .createSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); Map attemptAttributes = attributesCaptor.getValue(); - assertThat(attemptAttributes).doesNotContainKey(TracingTracer.SERVER_ADDRESS_ATTRIBUTE); + assertThat(attemptAttributes).doesNotContainKey(AppCentricTracer.SERVER_ADDRESS_ATTRIBUTE); } } 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/AppCentricTracerTest.java similarity index 92% rename from gax-java/gax/src/test/java/com/google/api/gax/tracing/TracingTracerTest.java rename to gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerTest.java index aede62658c..653ac3e7bb 100644 --- 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/AppCentricTracerTest.java @@ -42,11 +42,11 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class TracingTracerTest { - @Mock private TracingRecorder recorder; - @Mock private TracingRecorder.GaxSpan operationHandle; - @Mock private TracingRecorder.GaxSpan attemptHandle; - private TracingTracer tracer; +class AppCentricTracerTest { + @Mock private TraceRecorder recorder; + @Mock private TraceRecorder.GaxSpan operationHandle; + @Mock private TraceRecorder.GaxSpan attemptHandle; + private AppCentricTracer tracer; private static final String OPERATION_SPAN_NAME = "Service.Method/operation"; private static final String ATTEMPT_SPAN_NAME = "Service/Method/attempt"; @@ -54,7 +54,7 @@ class TracingTracerTest { void setUp() { when(recorder.createSpan(eq(OPERATION_SPAN_NAME), anyMap())).thenReturn(operationHandle); tracer = - new TracingTracer( + new AppCentricTracer( recorder, OPERATION_SPAN_NAME, ATTEMPT_SPAN_NAME, new HashMap<>(), new HashMap<>()); } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java similarity index 92% rename from gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java rename to gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java index bf95e26694..17b67076a4 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java @@ -50,19 +50,19 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class OpenTelemetryTracingRecorderTest { +class OpenTelemetryTraceRecorderTest { @Mock private OpenTelemetry openTelemetry; @Mock private Tracer tracer; @Mock private SpanBuilder spanBuilder; @Mock private Span span; @Mock private Scope scope; - private OpenTelemetryTracingRecorder recorder; + private OpenTelemetryTraceRecorder recorder; @BeforeEach void setUp() { when(openTelemetry.getTracer(anyString())).thenReturn(tracer); - recorder = new OpenTelemetryTracingRecorder(openTelemetry); + recorder = new OpenTelemetryTraceRecorder(openTelemetry); } @Test @@ -80,7 +80,7 @@ void testCreateSpan_operation_isInternal() { @Test void testCreateSpan_attempt_isClient() { String spanName = "attempt-span"; - TracingRecorder.GaxSpan parent = mock(TracingRecorder.GaxSpan.class); + TraceRecorder.GaxSpan parent = mock(TraceRecorder.GaxSpan.class); when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); @@ -101,7 +101,7 @@ void testCreateSpan_recordsSpan() { when(spanBuilder.setAttribute("key1", "value1")).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); - TracingRecorder.GaxSpan handle = recorder.createSpan(spanName, attributes); + TraceRecorder.GaxSpan handle = recorder.createSpan(spanName, attributes); handle.end(); verify(span).end(); 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 a5ea84dbd6..df352e9f81 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 @@ -32,9 +32,9 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.api.gax.tracing.OpenTelemetryTracingRecorder; -import com.google.api.gax.tracing.TracingTracer; -import com.google.api.gax.tracing.TracingTracerFactory; +import com.google.api.gax.tracing.AppCentricTracer; +import com.google.api.gax.tracing.AppCentricTracerFactory; +import com.google.api.gax.tracing.OpenTelemetryTraceRecorder; import com.google.showcase.v1beta1.EchoClient; import com.google.showcase.v1beta1.EchoRequest; import com.google.showcase.v1beta1.it.util.TestClientInitializer; @@ -103,8 +103,8 @@ private List waitForSpans(int expectedSpans) throws InterruptedExcepti @Test void testTracing_successfulEcho_grpc() throws Exception { - TracingTracerFactory tracingFactory = - new TracingTracerFactory(new OpenTelemetryTracingRecorder(openTelemetrySdk)); + AppCentricTracerFactory tracingFactory = + new AppCentricTracerFactory(new OpenTelemetryTraceRecorder(openTelemetrySdk)); try (EchoClient client = TestClientInitializer.createGrpcEchoClientOpentelemetry(tracingFactory)) { @@ -130,20 +130,20 @@ void testTracing_successfulEcho_grpc() throws Exception { assertThat( attemptSpan .getAttributes() - .get(AttributeKey.stringKey(TracingTracer.LANGUAGE_ATTRIBUTE))) - .isEqualTo(TracingTracer.DEFAULT_LANGUAGE); + .get(AttributeKey.stringKey(AppCentricTracer.LANGUAGE_ATTRIBUTE))) + .isEqualTo(AppCentricTracer.DEFAULT_LANGUAGE); assertThat( attemptSpan .getAttributes() - .get(AttributeKey.stringKey(TracingTracer.SERVER_ADDRESS_ATTRIBUTE))) + .get(AttributeKey.stringKey(AppCentricTracer.SERVER_ADDRESS_ATTRIBUTE))) .isEqualTo(SHOWCASE_SERVER_ADDRESS); } } @Test void testTracing_successfulEcho_httpjson() throws Exception { - TracingTracerFactory tracingFactory = - new TracingTracerFactory(new OpenTelemetryTracingRecorder(openTelemetrySdk)); + AppCentricTracerFactory tracingFactory = + new AppCentricTracerFactory(new OpenTelemetryTraceRecorder(openTelemetrySdk)); try (EchoClient client = TestClientInitializer.createHttpJsonEchoClientOpentelemetry(tracingFactory)) { @@ -169,7 +169,7 @@ void testTracing_successfulEcho_httpjson() throws Exception { assertThat( attemptSpan .getAttributes() - .get(AttributeKey.stringKey(TracingTracer.SERVER_ADDRESS_ATTRIBUTE))) + .get(AttributeKey.stringKey(AppCentricTracer.SERVER_ADDRESS_ATTRIBUTE))) .isEqualTo(SHOWCASE_SERVER_ADDRESS); } } From 5c07a3cdc6c796081d232acffef675a75dfe73f0 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 10 Feb 2026 14:58:52 -0500 Subject: [PATCH 22/32] chore: add javadoc for tracer --- .../com/google/api/gax/tracing/AppCentricTracer.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java index ac337f322c..adbb842c09 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java @@ -35,6 +35,9 @@ import java.util.HashMap; import java.util.Map; +/** + * An implementation of {@link ApiTracer} that uses a {@link TraceRecorder} to record traces. + */ @BetaApi @InternalApi public class AppCentricTracer extends BaseApiTracer { @@ -49,6 +52,15 @@ public class AppCentricTracer extends BaseApiTracer { private final TraceRecorder.GaxSpan operationHandle; private TraceRecorder.GaxSpan attemptHandle; + /** + * Creates a new instance of {@code AppCentricTracer}. + * + * @param recorder the {@link TraceRecorder} to use for recording spans + * @param operationSpanName the name of the long-lived operation span + * @param attemptSpanName the name of the individual attempt spans + * @param operationAttributes attributes to be added to the operation span + * @param attemptAttributes attributes to be added to each attempt span + */ public AppCentricTracer( TraceRecorder recorder, String operationSpanName, From ff1d45d9fe0dd08357054c2f520723e3fc6fb256 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 10 Feb 2026 15:07:12 -0500 Subject: [PATCH 23/32] chore: rename to TraceSpan, improve javadocs --- .../google/api/gax/tracing/AppCentricTracer.java | 6 ++++-- .../gax/tracing/OpenTelemetryTraceRecorder.java | 14 +++++++------- .../com/google/api/gax/tracing/TraceRecorder.java | 6 +++--- .../gax/tracing/AppCentricTracerFactoryTest.java | 8 ++++---- .../api/gax/tracing/AppCentricTracerTest.java | 4 ++-- .../tracing/OpenTelemetryTraceRecorderTest.java | 4 ++-- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java index adbb842c09..e9b75bad10 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java @@ -37,6 +37,8 @@ /** * An implementation of {@link ApiTracer} that uses a {@link TraceRecorder} to record traces. + * This implementation is agnostic to the specific {@link TraceRecorder} in order to allow extensions that interact + * with other backends. */ @BetaApi @InternalApi @@ -49,8 +51,8 @@ public class AppCentricTracer extends BaseApiTracer { private final TraceRecorder recorder; private final Map attemptAttributes; private final String attemptSpanName; - private final TraceRecorder.GaxSpan operationHandle; - private TraceRecorder.GaxSpan attemptHandle; + private final TraceRecorder.TraceSpan operationHandle; + private TraceRecorder.TraceSpan attemptHandle; /** * Creates a new instance of {@code AppCentricTracer}. diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java index eff2139c57..ceeb05a252 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java @@ -54,12 +54,12 @@ public OpenTelemetryTraceRecorder(OpenTelemetry openTelemetry) { } @Override - public GaxSpan createSpan(String name, Map attributes) { + public TraceSpan createSpan(String name, Map attributes) { return createSpan(name, attributes, null); } @Override - public GaxSpan createSpan(String name, Map attributes, GaxSpan parent) { + public TraceSpan createSpan(String name, Map attributes, TraceSpan parent) { SpanBuilder spanBuilder = tracer.spanBuilder(name); // Operation and Attempt spans are INTERNAL and CLIENT respectively. @@ -73,19 +73,19 @@ public GaxSpan createSpan(String name, Map attributes, GaxSpan p attributes.forEach((k, v) -> spanBuilder.setAttribute(k, v)); } - if (parent instanceof OtelGaxSpan) { - spanBuilder.setParent(Context.current().with(((OtelGaxSpan) parent).span)); + if (parent instanceof OtelTraceSpan) { + spanBuilder.setParent(Context.current().with(((OtelTraceSpan) parent).span)); } Span span = spanBuilder.startSpan(); - return new OtelGaxSpan(span); + return new OtelTraceSpan(span); } - private static class OtelGaxSpan implements GaxSpan { + private static class OtelTraceSpan implements TraceSpan { private final Span span; - private OtelGaxSpan(Span span) { + private OtelTraceSpan(Span span) { this.span = span; } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TraceRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TraceRecorder.java index 0fcc9950e4..706d15caa4 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TraceRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TraceRecorder.java @@ -42,12 +42,12 @@ @InternalApi public interface TraceRecorder { /** Starts a span and returns a handle to manage its lifecycle. */ - GaxSpan createSpan(String name, Map attributes); + TraceSpan createSpan(String name, Map attributes); /** Starts a span with a parent and returns a handle to manage its lifecycle. */ - GaxSpan createSpan(String name, Map attributes, GaxSpan parent); + TraceSpan createSpan(String name, Map attributes, TraceSpan parent); - interface GaxSpan { + interface TraceSpan { void end(); } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java index 8bf2b3e27c..8203f70261 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java @@ -49,7 +49,7 @@ class AppCentricTracerFactoryTest { @Test void testNewTracer_createsOpenTelemetryTracingTracer() { TraceRecorder recorder = mock(TraceRecorder.class); - when(recorder.createSpan(anyString(), anyMap())).thenReturn(mock(TraceRecorder.GaxSpan.class)); + when(recorder.createSpan(anyString(), anyMap())).thenReturn(mock(TraceRecorder.TraceSpan.class)); AppCentricTracerFactory factory = new AppCentricTracerFactory(recorder); ApiTracer tracer = @@ -61,7 +61,7 @@ void testNewTracer_createsOpenTelemetryTracingTracer() { @Test void testNewTracer_addsAttributes() { TraceRecorder recorder = mock(TraceRecorder.class); - TraceRecorder.GaxSpan operationHandle = mock(TraceRecorder.GaxSpan.class); + TraceRecorder.TraceSpan operationHandle = mock(TraceRecorder.TraceSpan.class); when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); AppCentricTracerFactory factory = @@ -84,7 +84,7 @@ void testNewTracer_addsAttributes() { @Test void testWithContext_addsInferredAttributes() { TraceRecorder recorder = mock(TraceRecorder.class); - TraceRecorder.GaxSpan operationHandle = mock(TraceRecorder.GaxSpan.class); + TraceRecorder.TraceSpan operationHandle = mock(TraceRecorder.TraceSpan.class); when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); ApiTracerContext context = @@ -111,7 +111,7 @@ void testWithContext_addsInferredAttributes() { @Test void testWithContext_noEndpointContext_doesNotAddAttributes() { TraceRecorder recorder = mock(TraceRecorder.class); - TraceRecorder.GaxSpan operationHandle = mock(TraceRecorder.GaxSpan.class); + TraceRecorder.TraceSpan operationHandle = mock(TraceRecorder.TraceSpan.class); when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); ApiTracerContext context = ApiTracerContext.newBuilder().build(); diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerTest.java index 653ac3e7bb..702f16a50b 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerTest.java @@ -44,8 +44,8 @@ @ExtendWith(MockitoExtension.class) class AppCentricTracerTest { @Mock private TraceRecorder recorder; - @Mock private TraceRecorder.GaxSpan operationHandle; - @Mock private TraceRecorder.GaxSpan attemptHandle; + @Mock private TraceRecorder.TraceSpan operationHandle; + @Mock private TraceRecorder.TraceSpan attemptHandle; private AppCentricTracer tracer; private static final String OPERATION_SPAN_NAME = "Service.Method/operation"; private static final String ATTEMPT_SPAN_NAME = "Service/Method/attempt"; diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java index 17b67076a4..8d636244f8 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java @@ -80,7 +80,7 @@ void testCreateSpan_operation_isInternal() { @Test void testCreateSpan_attempt_isClient() { String spanName = "attempt-span"; - TraceRecorder.GaxSpan parent = mock(TraceRecorder.GaxSpan.class); + TraceRecorder.TraceSpan parent = mock(TraceRecorder.TraceSpan.class); when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); @@ -101,7 +101,7 @@ void testCreateSpan_recordsSpan() { when(spanBuilder.setAttribute("key1", "value1")).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); - TraceRecorder.GaxSpan handle = recorder.createSpan(spanName, attributes); + TraceRecorder.TraceSpan handle = recorder.createSpan(spanName, attributes); handle.end(); verify(span).end(); From 043ce62464a9652bba62fea35b97eea2b8477723 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 10 Feb 2026 15:09:45 -0500 Subject: [PATCH 24/32] chore: format --- .../java/com/google/api/gax/tracing/AppCentricTracer.java | 6 +++--- .../google/api/gax/tracing/AppCentricTracerFactoryTest.java | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java index e9b75bad10..97aad00be8 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java @@ -36,9 +36,9 @@ import java.util.Map; /** - * An implementation of {@link ApiTracer} that uses a {@link TraceRecorder} to record traces. - * This implementation is agnostic to the specific {@link TraceRecorder} in order to allow extensions that interact - * with other backends. + * An implementation of {@link ApiTracer} that uses a {@link TraceRecorder} to record traces. This + * implementation is agnostic to the specific {@link TraceRecorder} in order to allow extensions + * that interact with other backends. */ @BetaApi @InternalApi diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java index 8203f70261..c3826d533d 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java @@ -49,7 +49,8 @@ class AppCentricTracerFactoryTest { @Test void testNewTracer_createsOpenTelemetryTracingTracer() { TraceRecorder recorder = mock(TraceRecorder.class); - when(recorder.createSpan(anyString(), anyMap())).thenReturn(mock(TraceRecorder.TraceSpan.class)); + when(recorder.createSpan(anyString(), anyMap())) + .thenReturn(mock(TraceRecorder.TraceSpan.class)); AppCentricTracerFactory factory = new AppCentricTracerFactory(recorder); ApiTracer tracer = From dbc5f41aa3a33ea5239fc52ab643b08ef7236b46 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 10 Feb 2026 15:20:59 -0500 Subject: [PATCH 25/32] Update gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../main/java/com/google/api/gax/tracing/AppCentricTracer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java index 97aad00be8..dd68efe3db 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java @@ -71,7 +71,7 @@ public AppCentricTracer( Map attemptAttributes) { this.recorder = recorder; this.attemptSpanName = attemptSpanName; - this.attemptAttributes = attemptAttributes; + this.attemptAttributes = new HashMap<>(attemptAttributes); this.attemptAttributes.put(LANGUAGE_ATTRIBUTE, DEFAULT_LANGUAGE); // Start the long-lived operation span. From b4bce0d11194e9a34b40d3cb7d490c50ccda4565 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 10 Feb 2026 15:27:32 -0500 Subject: [PATCH 26/32] chore: handle ipv6 in endpoint context --- .../google/api/gax/rpc/EndpointContext.java | 31 ++++++++----------- .../api/gax/rpc/EndpointContextTest.java | 18 +++++++++++ 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java index 7eb111c69f..84111dd620 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -40,9 +40,8 @@ import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; +import com.google.common.net.HostAndPort; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -389,23 +388,19 @@ boolean shouldUseS2A() { } private String parseServerAddress(String endpoint) { + if (Strings.isNullOrEmpty(endpoint)) { + return endpoint; + } + String hostPort = endpoint; + if (hostPort.contains("://")) { + // Strip the scheme if present. HostAndPort doesn't support schemes. + hostPort = hostPort.substring(hostPort.indexOf("://") + 3); + } try { - String urlString = endpoint; - if (!urlString.contains("://")) { - urlString = "http://" + urlString; - } - return new URL(urlString).getHost(); - } catch (MalformedURLException e) { - // Fallback for cases URL can't handle. - int colonPortIndex = endpoint.lastIndexOf(':'); - int doubleSlashIndex = endpoint.lastIndexOf("//"); - if (colonPortIndex == -1) { - return endpoint; - } - if (doubleSlashIndex != -1 && doubleSlashIndex < colonPortIndex) { - return endpoint.substring(doubleSlashIndex + 2, colonPortIndex); - } - return endpoint.substring(0, colonPortIndex); + return HostAndPort.fromString(hostPort).getHost(); + } catch (IllegalArgumentException e) { + // Fallback for cases HostAndPort can't handle. + return hostPort; } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java index 733691d789..c1bcc50512 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -619,5 +619,23 @@ void endpointContextBuild_resolvesPortAndServerAddress() throws IOException { .setTransportChannelProviderEndpoint(null) .build(); Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("test.googleapis.com"); + + // IPv6 literal with port + endpoint = "[2001:db8::1]:443"; + endpointContext = + defaultEndpointContextBuilder + .setClientSettingsEndpoint(endpoint) + .setTransportChannelProviderEndpoint(null) + .build(); + Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("2001:db8::1"); + + // Bare IPv6 literal (no port) + endpoint = "2001:db8::1"; + endpointContext = + defaultEndpointContextBuilder + .setClientSettingsEndpoint(endpoint) + .setTransportChannelProviderEndpoint(null) + .build(); + Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("2001:db8::1"); } } From e8739041931f9bd2e1a8774f13d6a69c2bac372f Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Tue, 10 Feb 2026 15:37:21 -0500 Subject: [PATCH 27/32] chore: add language tests --- .../java/com/google/showcase/v1beta1/it/ITOtelTracing.java | 5 +++++ 1 file changed, 5 insertions(+) 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 df352e9f81..cddf0c5372 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 @@ -166,6 +166,11 @@ void testTracing_successfulEcho_httpjson() throws Exception { .findFirst() .orElseThrow(() -> new AssertionError("Attempt span 'Echo/Echo/attempt' not found")); assertThat(attemptSpan.getKind()).isEqualTo(SpanKind.CLIENT); + assertThat( + attemptSpan + .getAttributes() + .get(AttributeKey.stringKey(AppCentricTracer.LANGUAGE_ATTRIBUTE))) + .isEqualTo(AppCentricTracer.DEFAULT_LANGUAGE); assertThat( attemptSpan .getAttributes() From 68943b0a9dbfdb7e0c9080afb5d6ef1641e7c66d Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Wed, 11 Feb 2026 13:43:42 -0500 Subject: [PATCH 28/32] chore: AppCentricTracer to implement interface --- .../main/java/com/google/api/gax/tracing/AppCentricTracer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java index dd68efe3db..c885e526e0 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java @@ -42,7 +42,7 @@ */ @BetaApi @InternalApi -public class AppCentricTracer extends BaseApiTracer { +public class AppCentricTracer implements ApiTracer { public static final String SERVER_ADDRESS_ATTRIBUTE = "server.address"; public static final String LANGUAGE_ATTRIBUTE = "gcp.client.language"; From cd4d4d05d070076807caa4c01f3cfc168301f05c Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 12 Feb 2026 14:57:39 -0500 Subject: [PATCH 29/32] chore: revert operation implementation --- gax-java/dependencies.properties | 1 - gax-java/gax/BUILD.bazel | 1 - .../api/gax/tracing/AppCentricTracer.java | 17 +------- .../gax/tracing/AppCentricTracerFactory.java | 9 +--- .../tracing/OpenTelemetryTraceRecorder.java | 18 +------- .../google/api/gax/tracing/TraceRecorder.java | 3 -- .../tracing/AppCentricTracerFactoryTest.java | 22 ++++------ .../api/gax/tracing/AppCentricTracerTest.java | 32 ++++++++------- .../OpenTelemetryTraceRecorderTest.java | 8 ++-- .../showcase/v1beta1/it/ITOtelTracing.java | 41 +------------------ 10 files changed, 37 insertions(+), 115 deletions(-) diff --git a/gax-java/dependencies.properties b/gax-java/dependencies.properties index 2fb4fb1316..3b46b82fff 100644 --- a/gax-java/dependencies.properties +++ b/gax-java/dependencies.properties @@ -40,7 +40,6 @@ maven.com_google_api_grpc_grpc_google_common_protos=com.google.api.grpc:grpc-goo maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.42.1 maven.com_google_auth_google_auth_library_credentials=com.google.auth:google-auth-library-credentials:1.42.1 maven.io_opentelemetry_opentelemetry_api=io.opentelemetry:opentelemetry-api:1.47.0 -maven.io_opentelemetry_opentelemetry_context=io.opentelemetry:opentelemetry-context:1.47.0 maven.io_opencensus_opencensus_api=io.opencensus:opencensus-api:0.31.1 maven.io_opencensus_opencensus_contrib_grpc_metrics=io.opencensus:opencensus-contrib-grpc-metrics:0.31.1 maven.io_opencensus_opencensus_contrib_http_util=io.opencensus:opencensus-contrib-http-util:0.31.1 diff --git a/gax-java/gax/BUILD.bazel b/gax-java/gax/BUILD.bazel index 15ed36bcbd..80b26ad785 100644 --- a/gax-java/gax/BUILD.bazel +++ b/gax-java/gax/BUILD.bazel @@ -19,7 +19,6 @@ _COMPILE_DEPS = [ "@com_google_errorprone_error_prone_annotations//jar", "@com_google_guava_guava//jar", "@io_opentelemetry_opentelemetry_api//jar", - "@io_opentelemetry_opentelemetry_context//jar", "@io_opencensus_opencensus_api//jar", "@io_opencensus_opencensus_contrib_http_util//jar", "@io_grpc_grpc_java//context:context", diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java index c885e526e0..eaaec8ff7b 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java @@ -51,38 +51,30 @@ public class AppCentricTracer implements ApiTracer { private final TraceRecorder recorder; private final Map attemptAttributes; private final String attemptSpanName; - private final TraceRecorder.TraceSpan operationHandle; private TraceRecorder.TraceSpan attemptHandle; /** * Creates a new instance of {@code AppCentricTracer}. * * @param recorder the {@link TraceRecorder} to use for recording spans - * @param operationSpanName the name of the long-lived operation span * @param attemptSpanName the name of the individual attempt spans - * @param operationAttributes attributes to be added to the operation span * @param attemptAttributes attributes to be added to each attempt span */ public AppCentricTracer( - TraceRecorder recorder, - String operationSpanName, - String attemptSpanName, - Map operationAttributes, - Map attemptAttributes) { + TraceRecorder recorder, String attemptSpanName, Map attemptAttributes) { this.recorder = recorder; this.attemptSpanName = attemptSpanName; this.attemptAttributes = new HashMap<>(attemptAttributes); this.attemptAttributes.put(LANGUAGE_ATTRIBUTE, DEFAULT_LANGUAGE); // Start the long-lived operation span. - this.operationHandle = recorder.createSpan(operationSpanName, operationAttributes); } @Override public void attemptStarted(Object request, int attemptNumber) { Map attemptAttributes = new HashMap<>(this.attemptAttributes); // Start the specific attempt span with the operation span as parent - this.attemptHandle = recorder.createSpan(attemptSpanName, attemptAttributes, operationHandle); + this.attemptHandle = recorder.createSpan(attemptSpanName, attemptAttributes); } @Override @@ -96,9 +88,4 @@ private void endAttempt() { attemptHandle = null; } } - - @Override - public void operationSucceeded() { - operationHandle.end(); - } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracerFactory.java index c2f4261383..2218bf3f31 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracerFactory.java @@ -80,17 +80,10 @@ public AppCentricTracerFactory(TraceRecorder traceRecorder) { public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { // TODO(diegomarquezp): these are placeholders for span names and will be adjusted as the // feature is developed. - String operationSpanName = - spanName.getClientName() + "." + spanName.getMethodName() + "/operation"; String attemptSpanName = spanName.getClientName() + "/" + spanName.getMethodName() + "/attempt"; AppCentricTracer appCentricTracer = - new AppCentricTracer( - traceRecorder, - operationSpanName, - attemptSpanName, - this.operationAttributes, - this.attemptAttributes); + new AppCentricTracer(traceRecorder, attemptSpanName, this.attemptAttributes); return appCentricTracer; } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java index ceeb05a252..4639be77d3 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java @@ -37,7 +37,6 @@ import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.Context; import java.util.Map; /** @@ -55,28 +54,15 @@ public OpenTelemetryTraceRecorder(OpenTelemetry openTelemetry) { @Override public TraceSpan createSpan(String name, Map attributes) { - return createSpan(name, attributes, null); - } - - @Override - public TraceSpan createSpan(String name, Map attributes, TraceSpan parent) { SpanBuilder spanBuilder = tracer.spanBuilder(name); - // Operation and Attempt spans are INTERNAL and CLIENT respectively. - if (parent == null) { - spanBuilder.setSpanKind(SpanKind.INTERNAL); - } else { - spanBuilder.setSpanKind(SpanKind.CLIENT); - } + // Attempt spans are INTERNAL + spanBuilder.setSpanKind(SpanKind.INTERNAL); if (attributes != null) { attributes.forEach((k, v) -> spanBuilder.setAttribute(k, v)); } - if (parent instanceof OtelTraceSpan) { - spanBuilder.setParent(Context.current().with(((OtelTraceSpan) parent).span)); - } - Span span = spanBuilder.startSpan(); return new OtelTraceSpan(span); diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TraceRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TraceRecorder.java index 706d15caa4..64f063fe62 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TraceRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TraceRecorder.java @@ -44,9 +44,6 @@ public interface TraceRecorder { /** Starts a span and returns a handle to manage its lifecycle. */ TraceSpan createSpan(String name, Map attributes); - /** Starts a span with a parent and returns a handle to manage its lifecycle. */ - TraceSpan createSpan(String name, Map attributes, TraceSpan parent); - interface TraceSpan { void end(); } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java index c3826d533d..3ab6bba53e 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java @@ -33,7 +33,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -62,8 +61,8 @@ void testNewTracer_createsOpenTelemetryTracingTracer() { @Test void testNewTracer_addsAttributes() { TraceRecorder recorder = mock(TraceRecorder.class); - TraceRecorder.TraceSpan operationHandle = mock(TraceRecorder.TraceSpan.class); - when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); + TraceRecorder.TraceSpan attemptHandle = mock(TraceRecorder.TraceSpan.class); + when(recorder.createSpan(anyString(), anyMap())).thenReturn(attemptHandle); AppCentricTracerFactory factory = new AppCentricTracerFactory( @@ -75,8 +74,7 @@ void testNewTracer_addsAttributes() { tracer.attemptStarted(null, 1); ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); - verify(recorder, atLeastOnce()) - .createSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); + verify(recorder, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture()); Map attemptAttributes = attributesCaptor.getValue(); assertThat(attemptAttributes).containsEntry("server.port", "443"); @@ -85,8 +83,8 @@ void testNewTracer_addsAttributes() { @Test void testWithContext_addsInferredAttributes() { TraceRecorder recorder = mock(TraceRecorder.class); - TraceRecorder.TraceSpan operationHandle = mock(TraceRecorder.TraceSpan.class); - when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); + TraceRecorder.TraceSpan attemptHandle = mock(TraceRecorder.TraceSpan.class); + when(recorder.createSpan(anyString(), anyMap())).thenReturn(attemptHandle); ApiTracerContext context = ApiTracerContext.newBuilder().setServerAddress("example.com").build(); @@ -101,8 +99,7 @@ void testWithContext_addsInferredAttributes() { tracer.attemptStarted(null, 1); ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); - verify(recorder, atLeastOnce()) - .createSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); + verify(recorder, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture()); Map attemptAttributes = attributesCaptor.getValue(); assertThat(attemptAttributes) @@ -112,8 +109,8 @@ void testWithContext_addsInferredAttributes() { @Test void testWithContext_noEndpointContext_doesNotAddAttributes() { TraceRecorder recorder = mock(TraceRecorder.class); - TraceRecorder.TraceSpan operationHandle = mock(TraceRecorder.TraceSpan.class); - when(recorder.createSpan(anyString(), anyMap())).thenReturn(operationHandle); + TraceRecorder.TraceSpan attemptHandle = mock(TraceRecorder.TraceSpan.class); + when(recorder.createSpan(anyString(), anyMap())).thenReturn(attemptHandle); ApiTracerContext context = ApiTracerContext.newBuilder().build(); @@ -127,8 +124,7 @@ void testWithContext_noEndpointContext_doesNotAddAttributes() { tracer.attemptStarted(null, 1); ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); - verify(recorder, atLeastOnce()) - .createSpan(anyString(), attributesCaptor.capture(), eq(operationHandle)); + verify(recorder, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture()); Map attemptAttributes = attributesCaptor.getValue(); assertThat(attemptAttributes).doesNotContainKey(AppCentricTracer.SERVER_ADDRESS_ATTRIBUTE); diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerTest.java index 702f16a50b..d04bd94f53 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerTest.java @@ -29,48 +29,52 @@ */ package com.google.api.gax.tracing; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) class AppCentricTracerTest { @Mock private TraceRecorder recorder; - @Mock private TraceRecorder.TraceSpan operationHandle; @Mock private TraceRecorder.TraceSpan attemptHandle; private AppCentricTracer tracer; - private static final String OPERATION_SPAN_NAME = "Service.Method/operation"; private static final String ATTEMPT_SPAN_NAME = "Service/Method/attempt"; @BeforeEach void setUp() { - when(recorder.createSpan(eq(OPERATION_SPAN_NAME), anyMap())).thenReturn(operationHandle); - tracer = - new AppCentricTracer( - recorder, OPERATION_SPAN_NAME, ATTEMPT_SPAN_NAME, new HashMap<>(), new HashMap<>()); - } - - @Test - void testOperationSucceeded_endsSpan() { - tracer.operationSucceeded(); - verify(operationHandle).end(); + tracer = new AppCentricTracer(recorder, ATTEMPT_SPAN_NAME, new HashMap<>()); } @Test void testAttemptLifecycle_startsAndEndsAttemptSpan() { - when(recorder.createSpan(eq(ATTEMPT_SPAN_NAME), anyMap(), eq(operationHandle))) - .thenReturn(attemptHandle); + when(recorder.createSpan(eq(ATTEMPT_SPAN_NAME), anyMap())).thenReturn(attemptHandle); tracer.attemptStarted(new Object(), 1); tracer.attemptSucceeded(); verify(attemptHandle).end(); } + + @Test + void testAttemptStarted_includesLanguageAttribute() { + when(recorder.createSpan(eq(ATTEMPT_SPAN_NAME), anyMap())).thenReturn(attemptHandle); + + tracer.attemptStarted(new Object(), 1); + + ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); + verify(recorder).createSpan(eq(ATTEMPT_SPAN_NAME), attributesCaptor.capture()); + + assertThat(attributesCaptor.getValue()) + .containsEntry(AppCentricTracer.LANGUAGE_ATTRIBUTE, AppCentricTracer.DEFAULT_LANGUAGE); + } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java index 8d636244f8..dc5dc207fe 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java @@ -31,7 +31,6 @@ package com.google.api.gax.tracing; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -80,15 +79,14 @@ void testCreateSpan_operation_isInternal() { @Test void testCreateSpan_attempt_isClient() { String spanName = "attempt-span"; - TraceRecorder.TraceSpan parent = mock(TraceRecorder.TraceSpan.class); when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); - when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); - recorder.createSpan(spanName, null, parent); + recorder.createSpan(spanName, null); - verify(spanBuilder).setSpanKind(SpanKind.CLIENT); + verify(spanBuilder).setSpanKind(SpanKind.INTERNAL); } @Test 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 cddf0c5372..64a05a0de0 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 @@ -48,7 +48,6 @@ import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import java.util.List; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -79,28 +78,6 @@ void tearDown() { GlobalOpenTelemetry.resetForTest(); } - /** - * The {@link com.google.api.gax.tracing.TracedUnaryCallable} implementation uses a callback - * approach to report the operation has been completed. That may cause a slight delay between - * client.echo(...) and the availability of the operation span (as opposed to attemptSuceeeded() - * which is reported immediately). This method waits for up to 50ms for the callback to take - * effect. - * - * @param expectedSpans number of flattened spans to be expected - * @return list of spans - */ - private List waitForSpans(int expectedSpans) throws InterruptedException { - for (int i = 0; i < 10; i++) { - List spans = spanExporter.getFinishedSpanItems(); - if (spans.size() == expectedSpans) { - return spans; - } - Thread.sleep(5); - } - Assertions.fail("Timed out waiting for spans"); - return null; - } - @Test void testTracing_successfulEcho_grpc() throws Exception { AppCentricTracerFactory tracingFactory = @@ -111,16 +88,9 @@ void testTracing_successfulEcho_grpc() throws Exception { client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); - List spans = waitForSpans(2); + List spans = spanExporter.getFinishedSpanItems(); assertThat(spans).isNotEmpty(); - SpanData operationSpan = - spans.stream() - .filter(span -> span.getName().equals("Echo.Echo/operation")) - .findFirst() - .orElseThrow(() -> new AssertionError("Operation span not found")); - assertThat(operationSpan.getKind()).isEqualTo(SpanKind.INTERNAL); - SpanData attemptSpan = spans.stream() .filter(span -> span.getName().equals("Echo/Echo/attempt")) @@ -150,16 +120,9 @@ void testTracing_successfulEcho_httpjson() throws Exception { client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); - List spans = waitForSpans(2); + List spans = spanExporter.getFinishedSpanItems(); assertThat(spans).isNotEmpty(); - SpanData operationSpan = - spans.stream() - .filter(span -> span.getName().equals("google.showcase.v1beta1.Echo/Echo/operation")) - .findFirst() - .orElseThrow(() -> new AssertionError("Operation span not found")); - assertThat(operationSpan.getKind()).isEqualTo(SpanKind.INTERNAL); - SpanData attemptSpan = spans.stream() .filter(span -> span.getName().equals("google.showcase.v1beta1/Echo/Echo/attempt")) From 5b76e403693089af48bf6673445fbb3fa93a68b5 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 12 Feb 2026 15:22:28 -0500 Subject: [PATCH 30/32] chore: extract common span attributes to separate class --- .../api/gax/tracing/AppCentricAttributes.java | 64 +++++++++++++++++++ .../api/gax/tracing/AppCentricTracer.java | 1 - .../gax/tracing/AppCentricTracerFactory.java | 7 +- .../tracing/OpenTelemetryTraceRecorder.java | 4 +- .../gax/tracing/AppCentricAttributesTest.java | 60 +++++++++++++++++ .../tracing/AppCentricTracerFactoryTest.java | 4 +- .../OpenTelemetryTraceRecorderTest.java | 10 +-- .../showcase/v1beta1/it/ITOtelTracing.java | 5 +- 8 files changed, 138 insertions(+), 17 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricAttributes.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricAttributesTest.java diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricAttributes.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricAttributes.java new file mode 100644 index 0000000000..a060ba4a88 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricAttributes.java @@ -0,0 +1,64 @@ +/* + * 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 java.util.HashMap; +import java.util.Map; + +/** + * Utility class for providing common attributes used in app-centric observability. + * + *

This class extracts information from {@link ApiTracerContext} and maps it to standardized + * attribute keys that are expected by {@link ApiTracerFactory} implementations that conform to + * app-centric observability + * + *

For internal use only. + */ +@InternalApi +public class AppCentricAttributes { + /** The address of the server being called (e.g., "pubsub.googleapis.com"). */ + public static final String SERVER_ADDRESS_ATTRIBUTE = "server.address"; + + /** + * Extracts attempt-level attributes from the provided {@link ApiTracerContext}. + * + * @param context the context containing information about the current API call + * @return a map of attributes to be included in attempt-level spans + */ + public static Map getAttemptAttributes(ApiTracerContext context) { + Map attributes = new HashMap<>(); + if (context.getServerAddress() != null) { + attributes.put(SERVER_ADDRESS_ATTRIBUTE, context.getServerAddress()); + } + return attributes; + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java index eaaec8ff7b..c951e71415 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracer.java @@ -43,7 +43,6 @@ @BetaApi @InternalApi public class AppCentricTracer implements ApiTracer { - public static final String SERVER_ADDRESS_ATTRIBUTE = "server.address"; public static final String LANGUAGE_ATTRIBUTE = "gcp.client.language"; public static final String DEFAULT_LANGUAGE = "Java"; diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracerFactory.java index 2218bf3f31..c227095179 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentricTracerFactory.java @@ -78,7 +78,7 @@ public AppCentricTracerFactory(TraceRecorder traceRecorder) { @Override public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { - // TODO(diegomarquezp): these are placeholders for span names and will be adjusted as the + // TODO(diegomarquezp): this is a placeholder for span names and will be adjusted as the // feature is developed. String attemptSpanName = spanName.getClientName() + "/" + spanName.getMethodName() + "/attempt"; @@ -90,10 +90,7 @@ public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType op @Override public ApiTracerFactory withContext(ApiTracerContext context) { Map newAttemptAttributes = new HashMap<>(this.attemptAttributes); - if (context.getServerAddress() != null) { - newAttemptAttributes.put( - AppCentricTracer.SERVER_ADDRESS_ATTRIBUTE, context.getServerAddress()); - } + newAttemptAttributes.putAll(AppCentricAttributes.getAttemptAttributes(context)); return new AppCentricTracerFactory(traceRecorder, operationAttributes, newAttemptAttributes); } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java index 4639be77d3..8e5202e3d9 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorder.java @@ -56,8 +56,8 @@ public OpenTelemetryTraceRecorder(OpenTelemetry openTelemetry) { public TraceSpan createSpan(String name, Map attributes) { SpanBuilder spanBuilder = tracer.spanBuilder(name); - // Attempt spans are INTERNAL - spanBuilder.setSpanKind(SpanKind.INTERNAL); + // Attempt spans are of the CLIENT kind + spanBuilder.setSpanKind(SpanKind.CLIENT); if (attributes != null) { attributes.forEach((k, v) -> spanBuilder.setAttribute(k, v)); diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricAttributesTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricAttributesTest.java new file mode 100644 index 0000000000..8256101b57 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricAttributesTest.java @@ -0,0 +1,60 @@ +/* + * 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 java.util.Map; +import org.junit.jupiter.api.Test; + +class AppCentricAttributesTest { + + @Test + void testGetAttemptAttributes_serverAddress() { + ApiTracerContext context = + ApiTracerContext.newBuilder().setServerAddress("test-address").build(); + + Map attributes = AppCentricAttributes.getAttemptAttributes(context); + + assertThat(attributes).hasSize(1); + assertThat(attributes) + .containsEntry(AppCentricAttributes.SERVER_ADDRESS_ATTRIBUTE, "test-address"); + } + + @Test + void testGetAttemptAttributes_nonePresent() { + ApiTracerContext context = ApiTracerContext.newBuilder().build(); + + Map attributes = AppCentricAttributes.getAttemptAttributes(context); + + assertThat(attributes).isEmpty(); + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java index 3ab6bba53e..9cf81b457c 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/AppCentricTracerFactoryTest.java @@ -103,7 +103,7 @@ void testWithContext_addsInferredAttributes() { Map attemptAttributes = attributesCaptor.getValue(); assertThat(attemptAttributes) - .containsEntry(AppCentricTracer.SERVER_ADDRESS_ATTRIBUTE, "example.com"); + .containsEntry(AppCentricAttributes.SERVER_ADDRESS_ATTRIBUTE, "example.com"); } @Test @@ -127,6 +127,6 @@ void testWithContext_noEndpointContext_doesNotAddAttributes() { verify(recorder, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture()); Map attemptAttributes = attributesCaptor.getValue(); - assertThat(attemptAttributes).doesNotContainKey(AppCentricTracer.SERVER_ADDRESS_ATTRIBUTE); + assertThat(attemptAttributes).doesNotContainKey(AppCentricAttributes.SERVER_ADDRESS_ATTRIBUTE); } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java index dc5dc207fe..53236caddd 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java @@ -68,12 +68,12 @@ void setUp() { void testCreateSpan_operation_isInternal() { String spanName = "operation-span"; when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); - when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); recorder.createSpan(spanName, null); - verify(spanBuilder).setSpanKind(SpanKind.INTERNAL); + verify(spanBuilder).setSpanKind(SpanKind.CLIENT); } @Test @@ -81,12 +81,12 @@ void testCreateSpan_attempt_isClient() { String spanName = "attempt-span"; when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); - when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); recorder.createSpan(spanName, null); - verify(spanBuilder).setSpanKind(SpanKind.INTERNAL); + verify(spanBuilder).setSpanKind(SpanKind.CLIENT); } @Test @@ -95,7 +95,7 @@ void testCreateSpan_recordsSpan() { Map attributes = ImmutableMap.of("key1", "value1"); when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); - when(spanBuilder.setSpanKind(SpanKind.INTERNAL)).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); when(spanBuilder.setAttribute("key1", "value1")).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); 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 64a05a0de0..f8602a6797 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 @@ -32,6 +32,7 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.api.gax.tracing.AppCentricAttributes; import com.google.api.gax.tracing.AppCentricTracer; import com.google.api.gax.tracing.AppCentricTracerFactory; import com.google.api.gax.tracing.OpenTelemetryTraceRecorder; @@ -105,7 +106,7 @@ void testTracing_successfulEcho_grpc() throws Exception { assertThat( attemptSpan .getAttributes() - .get(AttributeKey.stringKey(AppCentricTracer.SERVER_ADDRESS_ATTRIBUTE))) + .get(AttributeKey.stringKey(AppCentricAttributes.SERVER_ADDRESS_ATTRIBUTE))) .isEqualTo(SHOWCASE_SERVER_ADDRESS); } } @@ -137,7 +138,7 @@ void testTracing_successfulEcho_httpjson() throws Exception { assertThat( attemptSpan .getAttributes() - .get(AttributeKey.stringKey(AppCentricTracer.SERVER_ADDRESS_ATTRIBUTE))) + .get(AttributeKey.stringKey(AppCentricAttributes.SERVER_ADDRESS_ATTRIBUTE))) .isEqualTo(SHOWCASE_SERVER_ADDRESS); } } From c783d684033e5d7bba759d518e58a6cd84f0e799 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 12 Feb 2026 15:36:53 -0500 Subject: [PATCH 31/32] chore: remove unused var --- .../google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java index 53236caddd..44e8b84433 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceRecorderTest.java @@ -40,7 +40,6 @@ import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.Scope; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -54,7 +53,6 @@ class OpenTelemetryTraceRecorderTest { @Mock private Tracer tracer; @Mock private SpanBuilder spanBuilder; @Mock private Span span; - @Mock private Scope scope; private OpenTelemetryTraceRecorder recorder; From 3e06b39e7702befc37511cd751bbbe71fa00e028 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 12 Feb 2026 15:47:10 -0500 Subject: [PATCH 32/32] chore: restore context dep --- gax-java/dependencies.properties | 1 + gax-java/gax/BUILD.bazel | 1 + 2 files changed, 2 insertions(+) diff --git a/gax-java/dependencies.properties b/gax-java/dependencies.properties index 3b46b82fff..2fb4fb1316 100644 --- a/gax-java/dependencies.properties +++ b/gax-java/dependencies.properties @@ -40,6 +40,7 @@ maven.com_google_api_grpc_grpc_google_common_protos=com.google.api.grpc:grpc-goo maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.42.1 maven.com_google_auth_google_auth_library_credentials=com.google.auth:google-auth-library-credentials:1.42.1 maven.io_opentelemetry_opentelemetry_api=io.opentelemetry:opentelemetry-api:1.47.0 +maven.io_opentelemetry_opentelemetry_context=io.opentelemetry:opentelemetry-context:1.47.0 maven.io_opencensus_opencensus_api=io.opencensus:opencensus-api:0.31.1 maven.io_opencensus_opencensus_contrib_grpc_metrics=io.opencensus:opencensus-contrib-grpc-metrics:0.31.1 maven.io_opencensus_opencensus_contrib_http_util=io.opencensus:opencensus-contrib-http-util:0.31.1 diff --git a/gax-java/gax/BUILD.bazel b/gax-java/gax/BUILD.bazel index 80b26ad785..15ed36bcbd 100644 --- a/gax-java/gax/BUILD.bazel +++ b/gax-java/gax/BUILD.bazel @@ -19,6 +19,7 @@ _COMPILE_DEPS = [ "@com_google_errorprone_error_prone_annotations//jar", "@com_google_guava_guava//jar", "@io_opentelemetry_opentelemetry_api//jar", + "@io_opentelemetry_opentelemetry_context//jar", "@io_opencensus_opencensus_api//jar", "@io_opencensus_opencensus_contrib_http_util//jar", "@io_grpc_grpc_java//context:context",