Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@

package com.google.api.gax.tracing;

import com.google.api.client.util.Strings;
import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.api.gax.rpc.LibraryMetadata;
import com.google.common.annotations.VisibleForTesting;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
Expand All @@ -48,12 +50,12 @@
@InternalApi
public class SpanTracerFactory implements ApiTracerFactory {
private final Tracer tracer;

private final OpenTelemetry openTelemetry;
private final ApiTracerContext apiTracerContext;

/** Creates a SpanTracerFactory */
public SpanTracerFactory(OpenTelemetry openTelemetry) {
this(openTelemetry.getTracer("gax-java"), ApiTracerContext.empty());
this(openTelemetry, null, ApiTracerContext.empty());
}

/**
Expand All @@ -62,13 +64,18 @@ public SpanTracerFactory(OpenTelemetry openTelemetry) {
* internally.
*/
@VisibleForTesting
SpanTracerFactory(Tracer tracer, ApiTracerContext apiTracerContext) {
SpanTracerFactory(OpenTelemetry openTelemetry, Tracer tracer, ApiTracerContext apiTracerContext) {
this.openTelemetry = openTelemetry;
this.tracer = tracer;
this.apiTracerContext = apiTracerContext;
}

@Override
public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) {
if (tracer == null) {
// Return a no-op tracer if withContext hasn't been called to initialize the tracer properly
return new BaseApiTracer();
}
// TODO(diegomarquezp): this is a placeholder for span names and will be adjusted as the
// feature is developed.
String attemptSpanName = spanName.getClientName() + "/" + spanName.getMethodName() + "/attempt";
Expand All @@ -78,6 +85,10 @@ public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType op

@Override
public ApiTracer newTracer(ApiTracer parent, ApiTracerContext apiTracerContext) {
if (tracer == null) {
// Return a no-op tracer if withContext hasn't been called to initialize the tracer properly
return new BaseApiTracer();
}
ApiTracerContext mergedContext = this.apiTracerContext.merge(apiTracerContext);
return new SpanTracer(tracer, mergedContext);
}
Expand All @@ -87,8 +98,21 @@ public ApiTracerContext getApiTracerContext() {
return apiTracerContext;
}

/**
* Returns a new SpanTracerFactory with the provided context. The Tracer is re-initialized using
* the artifact name and version from the library metadata.
*/
@Override
public ApiTracerFactory withContext(ApiTracerContext context) {
return new SpanTracerFactory(tracer, apiTracerContext.merge(context));
if (context == null) {
return new BaseApiTracerFactory();
}
LibraryMetadata metadata = context.libraryMetadata();
if (metadata == null || metadata.isEmpty() || Strings.isNullOrEmpty(metadata.artifactName())) {
return new BaseApiTracerFactory();
}
Tracer newTracer = openTelemetry.getTracer(metadata.artifactName(), metadata.version());
ApiTracerContext mergedContext = this.apiTracerContext.merge(context);
return new SpanTracerFactory(openTelemetry, newTracer, mergedContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
Expand All @@ -41,6 +42,7 @@
import com.google.api.gax.rpc.LibraryMetadata;
import com.google.api.gax.tracing.ApiTracerContext.Transport;
import com.google.api.gax.tracing.ApiTracerFactory.OperationType;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
Expand All @@ -54,25 +56,36 @@
import org.mockito.ArgumentCaptor;

class SpanTracerFactoryTest {
private OpenTelemetry openTelemetry;
private Tracer tracer;
private SpanBuilder spanBuilder;
private Span span;

private LibraryMetadata validMetadata;

@BeforeEach
void setUp() {
openTelemetry = mock(OpenTelemetry.class);
tracer = mock(Tracer.class);
spanBuilder = mock(SpanBuilder.class);
span = mock(Span.class);
when(openTelemetry.getTracer(nullable(String.class), nullable(String.class)))
.thenReturn(tracer);
when(tracer.spanBuilder(anyString())).thenReturn(spanBuilder);
when(spanBuilder.setSpanKind(any())).thenReturn(spanBuilder);
when(spanBuilder.setAllAttributes(any(Attributes.class))).thenReturn(spanBuilder);
when(spanBuilder.startSpan()).thenReturn(span);

validMetadata = mock(LibraryMetadata.class);
when(validMetadata.artifactName()).thenReturn("gax-java");
when(validMetadata.version()).thenReturn("2.1.0");
}

@ParameterizedTest
@ValueSource(booleans = {false, true})
void testNewTracer_createsSpanTracer(boolean useContext) {
SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty());
SpanTracerFactory factory =
new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty());
ApiTracer tracerInstance;
if (useContext) {
ApiTracerContext context =
Expand All @@ -92,11 +105,12 @@ void testNewTracer_createsSpanTracer(boolean useContext) {
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testNewTracer_addsAttributes(boolean useContext) {
ApiTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty());
ApiTracerFactory factory =
new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty());
factory =
factory.withContext(
ApiTracerContext.newBuilder()
.setLibraryMetadata(LibraryMetadata.empty())
.setLibraryMetadata(validMetadata)
.setServerAddress("test-address")
.build());
ApiTracer tracerInstance;
Expand All @@ -105,7 +119,7 @@ void testNewTracer_addsAttributes(boolean useContext) {
ApiTracerContext.newBuilder()
.setFullMethodName("service/method")
.setTransport(Transport.GRPC)
.setLibraryMetadata(LibraryMetadata.empty())
.setLibraryMetadata(validMetadata)
.build();
tracerInstance = factory.newTracer(null, context);
} else {
Expand All @@ -128,11 +142,12 @@ void testNewTracer_addsAttributes(boolean useContext) {
void testWithContext_addsInferredAttributes(boolean useContext) {
ApiTracerContext context =
ApiTracerContext.newBuilder()
.setLibraryMetadata(LibraryMetadata.empty())
.setLibraryMetadata(validMetadata)
.setServerAddress("example.com")
.build();

SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty());
SpanTracerFactory factory =
new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty());
ApiTracerFactory factoryWithContext = factory.withContext(context);

ApiTracer tracerInstance;
Expand All @@ -141,7 +156,7 @@ void testWithContext_addsInferredAttributes(boolean useContext) {
ApiTracerContext.newBuilder()
.setFullMethodName("service/method")
.setTransport(Transport.GRPC)
.setLibraryMetadata(LibraryMetadata.empty())
.setLibraryMetadata(validMetadata)
.build();
tracerInstance = factoryWithContext.newTracer(null, callContext);
} else {
Expand All @@ -164,9 +179,11 @@ void testWithContext_addsInferredAttributes(boolean useContext) {
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testWithContext_noEndpointContext_doesNotAddServerAddressAttribute(boolean useContext) {
ApiTracerContext context = ApiTracerContext.empty();
ApiTracerContext context =
ApiTracerContext.newBuilder().setLibraryMetadata(validMetadata).build();

SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty());
SpanTracerFactory factory =
new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty());
ApiTracerFactory factoryWithContext = factory.withContext(context);

ApiTracer tracerInstance;
Expand All @@ -175,7 +192,7 @@ void testWithContext_noEndpointContext_doesNotAddServerAddressAttribute(boolean
ApiTracerContext.newBuilder()
.setFullMethodName("service/method")
.setTransport(Transport.GRPC)
.setLibraryMetadata(LibraryMetadata.empty())
.setLibraryMetadata(validMetadata)
.build();
tracerInstance = factoryWithContext.newTracer(null, callContext);
} else {
Expand Down Expand Up @@ -203,7 +220,8 @@ void testNewTracer_withContext_grpc_usesFullMethodName() {
.setLibraryMetadata(LibraryMetadata.empty())
.build();

SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty());
SpanTracerFactory factory =
new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty());
ApiTracer tracerInstance = factory.newTracer(null, context);

tracerInstance.attemptStarted(null, 1);
Expand All @@ -229,7 +247,8 @@ void testNewTracer_withContext_http_usesHttpMethodAndPathTemplate(
.setLibraryMetadata(LibraryMetadata.empty())
.build();

SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty());
SpanTracerFactory factory =
new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty());
ApiTracer tracerInstance = factory.newTracer(null, context);

tracerInstance.attemptStarted(null, 1);
Expand All @@ -246,7 +265,8 @@ void testNewTracer_withContext_http_noHttpMethodOrPathTemplate_usesFullMethodNam
.setLibraryMetadata(LibraryMetadata.empty())
.build();

SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty());
SpanTracerFactory factory =
new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty());
ApiTracer tracerInstance = factory.newTracer(null, context);

tracerInstance.attemptStarted(null, 1);
Expand All @@ -256,7 +276,8 @@ void testNewTracer_withContext_http_noHttpMethodOrPathTemplate_usesFullMethodNam

@Test
void testNewTracer_withSpanName_usesPlaceholder() {
SpanTracerFactory factory = new SpanTracerFactory(tracer, ApiTracerContext.empty());
SpanTracerFactory factory =
new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty());
ApiTracer tracerInstance =
factory.newTracer(null, SpanName.of("Service", "Method"), OperationType.Unary);

Expand All @@ -272,7 +293,7 @@ void testNewTracer_mergesFactoryContext() {
.setServerAddress("factory-address")
.setLibraryMetadata(LibraryMetadata.empty())
.build();
SpanTracerFactory factory = new SpanTracerFactory(tracer, apiTracerContext);
SpanTracerFactory factory = new SpanTracerFactory(openTelemetry, tracer, apiTracerContext);

ApiTracerContext callContext =
ApiTracerContext.newBuilder()
Expand All @@ -293,4 +314,51 @@ void testNewTracer_mergesFactoryContext() {
assertThat(attributes.asMap())
.containsEntry(AttributeKey.stringKey("rpc.method"), "Service/Method");
}

@Test
void testNoOpWhenTracerNull() {
SpanTracerFactory factory =
new SpanTracerFactory(openTelemetry, null, ApiTracerContext.empty());

ApiTracer tracerInstance =
factory.newTracer(null, SpanName.of("Service", "Method"), OperationType.Unary);

assertThat(tracerInstance).isInstanceOf(BaseApiTracer.class);

ApiTracer tracerInstance2 = factory.newTracer(null, ApiTracerContext.empty());

assertThat(tracerInstance2).isInstanceOf(BaseApiTracer.class);
}

@Test
void testWithContext_nullContext_returnsBaseApiTracerFactory() {
SpanTracerFactory factory =
new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty());
ApiTracerFactory factoryWithContext = factory.withContext(null);
assertThat(factoryWithContext).isInstanceOf(BaseApiTracerFactory.class);
}

@Test
void testWithContext_nullMetadata_returnsBaseApiTracerFactory() {
SpanTracerFactory factory =
new SpanTracerFactory(openTelemetry, tracer, ApiTracerContext.empty());
// Assuming ApiTracerContext.empty() has null libraryMetadata
ApiTracerFactory factoryWithContext = factory.withContext(ApiTracerContext.empty());
assertThat(factoryWithContext).isInstanceOf(BaseApiTracerFactory.class);
}

@Test
void testWithContext_nullTracer_returnsBaseApiTracerFactory() {
OpenTelemetry mockOpenTelemetry = mock(OpenTelemetry.class);
when(mockOpenTelemetry.getTracer(nullable(String.class), nullable(String.class)))
.thenReturn(null);

SpanTracerFactory factory =
new SpanTracerFactory(mockOpenTelemetry, tracer, ApiTracerContext.empty());
ApiTracerContext context =
ApiTracerContext.newBuilder().setLibraryMetadata(LibraryMetadata.empty()).build();

ApiTracerFactory factoryWithContext = factory.withContext(context);
assertThat(factoryWithContext).isInstanceOf(BaseApiTracerFactory.class);
}
}
Loading
Loading