diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceManager.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceManager.java deleted file mode 100644 index 833e56fda4..0000000000 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceManager.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; -import java.util.Map; - -/** - * OpenTelemetry implementation of managing traces. This implementation collects the measurements - * related to the lifecyle of an RPC. - */ -@BetaApi -@InternalApi -public class OpenTelemetryTraceManager implements TraceManager { - private final io.opentelemetry.api.trace.Tracer tracer; - - public OpenTelemetryTraceManager(OpenTelemetry openTelemetry) { - this.tracer = openTelemetry.getTracer("gax-java"); - } - - @Override - public Span createSpan(String name, Map attributes) { - SpanBuilder spanBuilder = tracer.spanBuilder(name); - - // Attempt spans are of the CLIENT kind - spanBuilder.setSpanKind(SpanKind.CLIENT); - - spanBuilder.setAllAttributes(ObservabilityUtils.toOtelAttributes(attributes)); - - io.opentelemetry.api.trace.Span span = spanBuilder.startSpan(); - - return new OtelSpan(span); - } - - private static class OtelSpan implements Span { - private final io.opentelemetry.api.trace.Span span; - - private OtelSpan(io.opentelemetry.api.trace.Span span) { - this.span = span; - } - - @Override - public void end() { - span.end(); - } - } -} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java index c5c28aebe0..5bb10374b4 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java @@ -32,14 +32,14 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; import java.util.HashMap; import java.util.Map; -/** - * An implementation of {@link ApiTracer} that uses a {@link TraceManager} to record traces. This - * implementation is agnostic to the specific {@link TraceManager} in order to allow extensions that - * interact with other backends. - */ +/** An implementation of {@link ApiTracer} that uses OpenTelemetry to record traces. */ @BetaApi @InternalApi public class SpanTracer implements ApiTracer { @@ -47,27 +47,57 @@ public class SpanTracer implements ApiTracer { public static final String DEFAULT_LANGUAGE = "Java"; - private final TraceManager traceManager; + private final Tracer tracer; private final Map attemptAttributes; private final String attemptSpanName; private final ApiTracerContext apiTracerContext; - private TraceManager.Span attemptHandle; + private Span attemptSpan; /** * Creates a new instance of {@code SpanTracer}. * - * @param traceManager the {@link TraceManager} to use for recording spans + * @param tracer the {@link Tracer} to use for recording spans + * @param apiTracerContext the {@link ApiTracerContext} to use for recording spans + */ + public SpanTracer(Tracer tracer, ApiTracerContext apiTracerContext) { + this.tracer = tracer; + this.apiTracerContext = apiTracerContext; + this.attemptSpanName = resolveAttemptSpanName(apiTracerContext); + this.attemptAttributes = new HashMap<>(); + buildAttributes(); + } + + /** + * Creates a new instance of {@code SpanTracer} with an explicitly provided span name. + * + * @param tracer the {@link Tracer} to use for recording spans + * @param apiTracerContext the {@link ApiTracerContext} to use for recording spans * @param attemptSpanName the name of the individual attempt spans */ - public SpanTracer( - TraceManager traceManager, ApiTracerContext apiTracerContext, String attemptSpanName) { - this.traceManager = traceManager; + @InternalApi + SpanTracer(Tracer tracer, ApiTracerContext apiTracerContext, String attemptSpanName) { + this.tracer = tracer; this.attemptSpanName = attemptSpanName; this.apiTracerContext = apiTracerContext; this.attemptAttributes = new HashMap<>(); buildAttributes(); } + private static String resolveAttemptSpanName(ApiTracerContext apiTracerContext) { + if (apiTracerContext.transport() == ApiTracerContext.Transport.GRPC) { + // gRPC Uses the full method name as span name. + return apiTracerContext.fullMethodName(); + } else if (apiTracerContext.httpMethod() == null + || apiTracerContext.httpPathTemplate() == null) { + // HTTP method name without necessary components defaults to the full method name + return apiTracerContext.fullMethodName(); + } else { + // We construct the span name with HTTP method and path template. + return String.format( + "%s %s", apiTracerContext.httpMethod(), apiTracerContext.httpPathTemplate()); + } + } + private void buildAttributes() { this.attemptAttributes.put(LANGUAGE_ATTRIBUTE, DEFAULT_LANGUAGE); this.attemptAttributes.putAll(this.apiTracerContext.getAttemptAttributes()); @@ -75,9 +105,14 @@ private void buildAttributes() { @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 = traceManager.createSpan(attemptSpanName, attemptAttributes); + SpanBuilder spanBuilder = tracer.spanBuilder(attemptSpanName); + + // Attempt spans are of the CLIENT kind + spanBuilder.setSpanKind(SpanKind.CLIENT); + + spanBuilder.setAllAttributes(ObservabilityUtils.toOtelAttributes(this.attemptAttributes)); + + this.attemptSpan = spanBuilder.startSpan(); } @Override @@ -86,9 +121,9 @@ public void attemptSucceeded() { } private void endAttempt() { - if (attemptHandle != null) { - attemptHandle.end(); - attemptHandle = null; + if (attemptSpan != null) { + attemptSpan.end(); + attemptSpan = null; } } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java index 29fdd17afd..f891795564 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracerFactory.java @@ -33,25 +33,27 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.common.annotations.VisibleForTesting; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Tracer; /** * A {@link ApiTracerFactory} to build instances of {@link SpanTracer}. * - *

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

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

This class is expected to be initialized once during client initialization. */ @BetaApi @InternalApi public class SpanTracerFactory implements ApiTracerFactory { - private final TraceManager traceManager; + private final Tracer tracer; private final ApiTracerContext apiTracerContext; /** Creates a SpanTracerFactory */ - public SpanTracerFactory(TraceManager traceManager) { - this(traceManager, ApiTracerContext.empty()); + public SpanTracerFactory(OpenTelemetry openTelemetry) { + this(openTelemetry.getTracer("gax-java"), ApiTracerContext.empty()); } /** @@ -60,8 +62,8 @@ public SpanTracerFactory(TraceManager traceManager) { * internally. */ @VisibleForTesting - SpanTracerFactory(TraceManager traceManager, ApiTracerContext apiTracerContext) { - this.traceManager = traceManager; + SpanTracerFactory(Tracer tracer, ApiTracerContext apiTracerContext) { + this.tracer = tracer; this.apiTracerContext = apiTracerContext; } @@ -71,28 +73,13 @@ public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType op // feature is developed. String attemptSpanName = spanName.getClientName() + "/" + spanName.getMethodName() + "/attempt"; - SpanTracer spanTracer = new SpanTracer(traceManager, this.apiTracerContext, attemptSpanName); - return spanTracer; + return new SpanTracer(tracer, this.apiTracerContext, attemptSpanName); } @Override public ApiTracer newTracer(ApiTracer parent, ApiTracerContext apiTracerContext) { ApiTracerContext mergedContext = this.apiTracerContext.merge(apiTracerContext); - - String attemptSpanName; - if (mergedContext.transport() == ApiTracerContext.Transport.GRPC) { - // gRPC Uses the full method name as span name. - attemptSpanName = mergedContext.fullMethodName(); - } else if (mergedContext.httpMethod() == null || mergedContext.httpPathTemplate() == null) { - // HTTP method name without necessary components defaults to the full method name - attemptSpanName = mergedContext.fullMethodName(); - } else { - // We construct the span name with HTTP method and path template. - attemptSpanName = - String.format("%s %s", mergedContext.httpMethod(), mergedContext.httpPathTemplate()); - } - - return new SpanTracer(traceManager, mergedContext, attemptSpanName); + return new SpanTracer(tracer, mergedContext); } @Override @@ -102,6 +89,6 @@ public ApiTracerContext getApiTracerContext() { @Override public ApiTracerFactory withContext(ApiTracerContext context) { - return new SpanTracerFactory(traceManager, apiTracerContext.merge(context)); + return new SpanTracerFactory(tracer, apiTracerContext.merge(context)); } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TraceManager.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/TraceManager.java deleted file mode 100644 index 8572d1ce11..0000000000 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/TraceManager.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 management. The implementer is expected to use an observability - * framework, e.g. OpenTelemetry. There should be only one instance of TraceManager per client. - */ -@BetaApi -@InternalApi -public interface TraceManager { - /** Starts a span and returns a handle to manage its lifecycle. */ - Span createSpan(String name, Map attributes); - - interface Span { - void end(); - } -} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceManagerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceManagerTest.java deleted file mode 100644 index e1a46e13a9..0000000000 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTraceManagerTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.Tracer; -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 OpenTelemetryTraceManagerTest { - @Mock private OpenTelemetry openTelemetry; - @Mock private Tracer tracer; - @Mock private SpanBuilder spanBuilder; - @Mock private Span span; - - private OpenTelemetryTraceManager recorder; - - @BeforeEach - void setUp() { - when(openTelemetry.getTracer(anyString())).thenReturn(tracer); - recorder = new OpenTelemetryTraceManager(openTelemetry); - } - - @Test - void testCreateSpan_operation_isInternal() { - String spanName = "operation-span"; - when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); - when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); - when(spanBuilder.startSpan()).thenReturn(span); - - recorder.createSpan(spanName, null); - - verify(spanBuilder).setSpanKind(SpanKind.CLIENT); - } - - @Test - void testCreateSpan_attempt_isClient() { - String spanName = "attempt-span"; - - when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); - when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); - when(spanBuilder.startSpan()).thenReturn(span); - - recorder.createSpan(spanName, null); - - verify(spanBuilder).setSpanKind(SpanKind.CLIENT); - } - - @Test - void testCreateSpan_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.setAllAttributes(Attributes.of(AttributeKey.stringKey("key1"), "value1"))) - .thenReturn(spanBuilder); - when(spanBuilder.startSpan()).thenReturn(span); - - TraceManager.Span handle = recorder.createSpan(spanName, attributes); - handle.end(); - - verify(spanBuilder).setAllAttributes(ObservabilityUtils.toOtelAttributes(attributes)); - verify(span).end(); - } -} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerFactoryTest.java index 5689bfe8a5..6054c67ca7 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerFactoryTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerFactoryTest.java @@ -31,9 +31,8 @@ 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.any; 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; @@ -42,7 +41,11 @@ import com.google.api.gax.rpc.LibraryMetadata; import com.google.api.gax.tracing.ApiTracerContext.Transport; import com.google.api.gax.tracing.ApiTracerFactory.OperationType; -import java.util.Map; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.Tracer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -51,21 +54,26 @@ import org.mockito.ArgumentCaptor; class SpanTracerFactoryTest { - private TraceManager traceManager; - private TraceManager.Span span; + private Tracer tracer; + private SpanBuilder spanBuilder; + private Span span; @BeforeEach void setUp() { - traceManager = mock(TraceManager.class); - span = mock(TraceManager.Span.class); - when(traceManager.createSpan(anyString(), anyMap())).thenReturn(span); + tracer = mock(Tracer.class); + spanBuilder = mock(SpanBuilder.class); + span = mock(Span.class); + when(tracer.spanBuilder(anyString())).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(any())).thenReturn(spanBuilder); + when(spanBuilder.setAllAttributes(any(Attributes.class))).thenReturn(spanBuilder); + when(spanBuilder.startSpan()).thenReturn(span); } @ParameterizedTest @ValueSource(booleans = {false, true}) void testNewTracer_createsSpanTracer(boolean useContext) { - SpanTracerFactory factory = new SpanTracerFactory(traceManager); - ApiTracer tracer; + SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + ApiTracer tracerInstance; if (useContext) { ApiTracerContext context = ApiTracerContext.newBuilder() @@ -73,24 +81,25 @@ void testNewTracer_createsSpanTracer(boolean useContext) { .setTransport(Transport.GRPC) .setLibraryMetadata(LibraryMetadata.empty()) .build(); - tracer = factory.newTracer(null, context); + tracerInstance = factory.newTracer(null, context); } else { - tracer = factory.newTracer(null, SpanName.of("service", "method"), OperationType.Unary); + tracerInstance = + factory.newTracer(null, SpanName.of("service", "method"), OperationType.Unary); } - assertThat(tracer).isInstanceOf(SpanTracer.class); + assertThat(tracerInstance).isInstanceOf(SpanTracer.class); } @ParameterizedTest @ValueSource(booleans = {false, true}) void testNewTracer_addsAttributes(boolean useContext) { - ApiTracerFactory factory = new SpanTracerFactory(traceManager, ApiTracerContext.empty()); + ApiTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); factory = factory.withContext( ApiTracerContext.newBuilder() .setLibraryMetadata(LibraryMetadata.empty()) .setServerAddress("test-address") .build()); - ApiTracer tracer; + ApiTracer tracerInstance; if (useContext) { ApiTracerContext context = ApiTracerContext.newBuilder() @@ -98,18 +107,20 @@ void testNewTracer_addsAttributes(boolean useContext) { .setTransport(Transport.GRPC) .setLibraryMetadata(LibraryMetadata.empty()) .build(); - tracer = factory.newTracer(null, context); + tracerInstance = factory.newTracer(null, context); } else { - tracer = factory.newTracer(null, SpanName.of("service", "method"), OperationType.Unary); + tracerInstance = + factory.newTracer(null, SpanName.of("service", "method"), OperationType.Unary); } - tracer.attemptStarted(null, 1); + tracerInstance.attemptStarted(null, 1); - ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); - verify(traceManager, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture()); + ArgumentCaptor attributesCaptor = ArgumentCaptor.forClass(Attributes.class); + verify(spanBuilder, atLeastOnce()).setAllAttributes(attributesCaptor.capture()); - Map attemptAttributes = attributesCaptor.getValue(); - assertThat(attemptAttributes).containsEntry("server.address", "test-address"); + Attributes attemptAttributes = attributesCaptor.getValue(); + assertThat(attemptAttributes.asMap()) + .containsEntry(AttributeKey.stringKey("server.address"), "test-address"); } @ParameterizedTest @@ -121,10 +132,10 @@ void testWithContext_addsInferredAttributes(boolean useContext) { .setServerAddress("example.com") .build(); - SpanTracerFactory factory = new SpanTracerFactory(traceManager); + SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); ApiTracerFactory factoryWithContext = factory.withContext(context); - ApiTracer tracer; + ApiTracer tracerInstance; if (useContext) { ApiTracerContext callContext = ApiTracerContext.newBuilder() @@ -132,20 +143,22 @@ void testWithContext_addsInferredAttributes(boolean useContext) { .setTransport(Transport.GRPC) .setLibraryMetadata(LibraryMetadata.empty()) .build(); - tracer = factoryWithContext.newTracer(null, callContext); + tracerInstance = factoryWithContext.newTracer(null, callContext); } else { - tracer = + tracerInstance = factoryWithContext.newTracer(null, SpanName.of("service", "method"), OperationType.Unary); } - tracer.attemptStarted(null, 1); + tracerInstance.attemptStarted(null, 1); - ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); - verify(traceManager, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture()); + ArgumentCaptor attributesCaptor = ArgumentCaptor.forClass(Attributes.class); + verify(spanBuilder, atLeastOnce()).setAllAttributes(attributesCaptor.capture()); - Map attemptAttributes = attributesCaptor.getValue(); - assertThat(attemptAttributes) - .containsEntry(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE, "example.com"); + Attributes attemptAttributes = attributesCaptor.getValue(); + assertThat(attemptAttributes.asMap()) + .containsEntry( + AttributeKey.stringKey(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE), + "example.com"); } @ParameterizedTest @@ -153,10 +166,10 @@ void testWithContext_addsInferredAttributes(boolean useContext) { void testWithContext_noEndpointContext_doesNotAddServerAddressAttribute(boolean useContext) { ApiTracerContext context = ApiTracerContext.empty(); - SpanTracerFactory factory = new SpanTracerFactory(traceManager); + SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); ApiTracerFactory factoryWithContext = factory.withContext(context); - ApiTracer tracer; + ApiTracer tracerInstance; if (useContext) { ApiTracerContext callContext = ApiTracerContext.newBuilder() @@ -164,20 +177,21 @@ void testWithContext_noEndpointContext_doesNotAddServerAddressAttribute(boolean .setTransport(Transport.GRPC) .setLibraryMetadata(LibraryMetadata.empty()) .build(); - tracer = factoryWithContext.newTracer(null, callContext); + tracerInstance = factoryWithContext.newTracer(null, callContext); } else { - tracer = + tracerInstance = factoryWithContext.newTracer(null, SpanName.of("service", "method"), OperationType.Unary); } - tracer.attemptStarted(null, 1); + tracerInstance.attemptStarted(null, 1); - ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); - verify(traceManager, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture()); + ArgumentCaptor attributesCaptor = ArgumentCaptor.forClass(Attributes.class); + verify(spanBuilder, atLeastOnce()).setAllAttributes(attributesCaptor.capture()); - Map attemptAttributes = attributesCaptor.getValue(); - assertThat(attemptAttributes) - .doesNotContainKey(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE); + Attributes attemptAttributes = attributesCaptor.getValue(); + assertThat(attemptAttributes.asMap()) + .doesNotContainKey( + AttributeKey.stringKey(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE)); } @Test @@ -189,12 +203,12 @@ void testNewTracer_withContext_grpc_usesFullMethodName() { .setLibraryMetadata(LibraryMetadata.empty()) .build(); - SpanTracerFactory factory = new SpanTracerFactory(traceManager); - ApiTracer tracer = factory.newTracer(null, context); + SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + ApiTracer tracerInstance = factory.newTracer(null, context); - tracer.attemptStarted(null, 1); + tracerInstance.attemptStarted(null, 1); - verify(traceManager).createSpan(eq("google.cloud.v1.Service/Method"), anyMap()); + verify(tracer).spanBuilder("google.cloud.v1.Service/Method"); } @ParameterizedTest @@ -215,12 +229,12 @@ void testNewTracer_withContext_http_usesHttpMethodAndPathTemplate( .setLibraryMetadata(LibraryMetadata.empty()) .build(); - SpanTracerFactory factory = new SpanTracerFactory(traceManager); - ApiTracer tracer = factory.newTracer(null, context); + SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + ApiTracer tracerInstance = factory.newTracer(null, context); - tracer.attemptStarted(null, 1); + tracerInstance.attemptStarted(null, 1); - verify(traceManager).createSpan(eq(expectedSpanName), anyMap()); + verify(tracer).spanBuilder(expectedSpanName); } @Test @@ -232,23 +246,23 @@ void testNewTracer_withContext_http_noHttpMethodOrPathTemplate_usesFullMethodNam .setLibraryMetadata(LibraryMetadata.empty()) .build(); - SpanTracerFactory factory = new SpanTracerFactory(traceManager); - ApiTracer tracer = factory.newTracer(null, context); + SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + ApiTracer tracerInstance = factory.newTracer(null, context); - tracer.attemptStarted(null, 1); + tracerInstance.attemptStarted(null, 1); - verify(traceManager).createSpan(eq("google.cloud.v1.Service.Method"), anyMap()); + verify(tracer).spanBuilder("google.cloud.v1.Service.Method"); } @Test void testNewTracer_withSpanName_usesPlaceholder() { - SpanTracerFactory factory = new SpanTracerFactory(traceManager); - ApiTracer tracer = + SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty()); + ApiTracer tracerInstance = factory.newTracer(null, SpanName.of("Service", "Method"), OperationType.Unary); - tracer.attemptStarted(null, 1); + tracerInstance.attemptStarted(null, 1); - verify(traceManager).createSpan(eq("Service/Method/attempt"), anyMap()); + verify(tracer).spanBuilder("Service/Method/attempt"); } @Test @@ -258,7 +272,7 @@ void testNewTracer_mergesFactoryContext() { .setServerAddress("factory-address") .setLibraryMetadata(LibraryMetadata.empty()) .build(); - SpanTracerFactory factory = new SpanTracerFactory(traceManager, apiTracerContext); + SpanTracerFactory factory = new SpanTracerFactory(tracer, apiTracerContext); ApiTracerContext callContext = ApiTracerContext.newBuilder() @@ -267,14 +281,16 @@ void testNewTracer_mergesFactoryContext() { .setLibraryMetadata(LibraryMetadata.empty()) .build(); - ApiTracer tracer = factory.newTracer(null, callContext); - tracer.attemptStarted(null, 1); + ApiTracer tracerInstance = factory.newTracer(null, callContext); + tracerInstance.attemptStarted(null, 1); - ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); - verify(traceManager).createSpan(anyString(), attributesCaptor.capture()); + ArgumentCaptor attributesCaptor = ArgumentCaptor.forClass(Attributes.class); + verify(spanBuilder).setAllAttributes(attributesCaptor.capture()); - Map attributes = attributesCaptor.getValue(); - assertThat(attributes).containsEntry("server.address", "factory-address"); - assertThat(attributes).containsEntry("rpc.method", "Service/Method"); + Attributes attributes = attributesCaptor.getValue(); + assertThat(attributes.asMap()) + .containsEntry(AttributeKey.stringKey("server.address"), "factory-address"); + assertThat(attributes.asMap()) + .containsEntry(AttributeKey.stringKey("rpc.method"), "Service/Method"); } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerTest.java index b5e6100fe6..6d20cdfcf4 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerTest.java @@ -30,12 +30,16 @@ 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.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.Map; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -45,35 +49,41 @@ @ExtendWith(MockitoExtension.class) class SpanTracerTest { - @Mock private TraceManager recorder; - @Mock private TraceManager.Span attemptHandle; - private SpanTracer tracer; + @Mock private Tracer tracer; + @Mock private SpanBuilder spanBuilder; + @Mock private Span span; + private SpanTracer spanTracer; private static final String ATTEMPT_SPAN_NAME = "Service/Method/attempt"; @BeforeEach void setUp() { - tracer = new SpanTracer(recorder, ApiTracerContext.empty(), ATTEMPT_SPAN_NAME); + when(tracer.spanBuilder(anyString())).thenReturn(spanBuilder); + when(spanBuilder.setSpanKind(any(SpanKind.class))).thenReturn(spanBuilder); + when(spanBuilder.setAllAttributes(any(Attributes.class))).thenReturn(spanBuilder); + when(spanBuilder.startSpan()).thenReturn(span); + spanTracer = new SpanTracer(tracer, ApiTracerContext.empty(), ATTEMPT_SPAN_NAME); } @Test void testAttemptLifecycle_startsAndEndsAttemptSpan() { - when(recorder.createSpan(eq(ATTEMPT_SPAN_NAME), anyMap())).thenReturn(attemptHandle); - tracer.attemptStarted(new Object(), 1); - tracer.attemptSucceeded(); + spanTracer.attemptStarted(new Object(), 1); + spanTracer.attemptSucceeded(); - verify(attemptHandle).end(); + verify(tracer).spanBuilder(ATTEMPT_SPAN_NAME); + verify(spanBuilder).setSpanKind(SpanKind.CLIENT); + verify(span).end(); } @Test void testAttemptStarted_includesLanguageAttribute() { - when(recorder.createSpan(eq(ATTEMPT_SPAN_NAME), anyMap())).thenReturn(attemptHandle); + spanTracer.attemptStarted(new Object(), 1); - tracer.attemptStarted(new Object(), 1); + ArgumentCaptor attributesCaptor = ArgumentCaptor.forClass(Attributes.class); + verify(spanBuilder).setAllAttributes(attributesCaptor.capture()); - ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); - verify(recorder).createSpan(eq(ATTEMPT_SPAN_NAME), attributesCaptor.capture()); - - assertThat(attributesCaptor.getValue()) - .containsEntry(SpanTracer.LANGUAGE_ATTRIBUTE, SpanTracer.DEFAULT_LANGUAGE); + assertThat(attributesCaptor.getValue().asMap()) + .containsEntry( + io.opentelemetry.api.common.AttributeKey.stringKey(SpanTracer.LANGUAGE_ATTRIBUTE), + SpanTracer.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 index e0f0295cc9..b43351c2ac 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 @@ -33,7 +33,6 @@ import static com.google.common.truth.Truth.assertThat; import com.google.api.gax.tracing.ObservabilityAttributes; -import com.google.api.gax.tracing.OpenTelemetryTraceManager; import com.google.api.gax.tracing.SpanTracer; import com.google.api.gax.tracing.SpanTracerFactory; import com.google.showcase.v1beta1.EchoClient; @@ -84,8 +83,7 @@ void tearDown() { @Test void testTracing_successfulEcho_grpc() throws Exception { - SpanTracerFactory tracingFactory = - new SpanTracerFactory(new OpenTelemetryTraceManager(openTelemetrySdk)); + SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk); try (EchoClient client = TestClientInitializer.createGrpcEchoClientOpentelemetry(tracingFactory)) { @@ -148,8 +146,7 @@ void testTracing_successfulEcho_grpc() throws Exception { @Test void testTracing_successfulEcho_httpjson() throws Exception { - SpanTracerFactory tracingFactory = - new SpanTracerFactory(new OpenTelemetryTraceManager(openTelemetrySdk)); + SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk); try (EchoClient client = TestClientInitializer.createHttpJsonEchoClientOpentelemetry(tracingFactory)) {