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 @@ -274,6 +274,7 @@ public static ClientContext create(StubSettings settings) throws IOException {
ApiTracerContext apiTracerContext =
ApiTracerContext.newBuilder()
.setServerAddress(endpointContext.resolvedServerAddress())
.setServerPort(endpointContext.resolvedServerPort())
.setLibraryMetadata(settings.getLibraryMetadata())
.build();
ApiTracerFactory apiTracerFactory = settings.getTracerFactory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ public static EndpointContext getDefaultInstance() {

public abstract String resolvedServerAddress();

@Nullable
public abstract Integer resolvedServerPort();

public abstract Builder toBuilder();

public static Builder newBuilder() {
Expand Down Expand Up @@ -233,6 +236,8 @@ public abstract static class Builder {

public abstract Builder setResolvedServerAddress(String serverAddress);

public abstract Builder setResolvedServerPort(Integer serverPort);

public abstract Builder setResolvedUniverseDomain(String resolvedUniverseDomain);

abstract Builder setUseS2A(boolean useS2A);
Expand Down Expand Up @@ -264,6 +269,8 @@ public abstract static class Builder {

abstract String resolvedUniverseDomain();

abstract String resolvedEndpoint();

abstract EndpointContext autoBuild();

private String determineUniverseDomain() {
Expand Down Expand Up @@ -388,19 +395,38 @@ boolean shouldUseS2A() {
}

private String parseServerAddress(String endpoint) {
if (Strings.isNullOrEmpty(endpoint)) {
if (endpoint.isEmpty()) {
return endpoint;
}
HostAndPort hostAndPort = parseServerHostAndPort(endpoint);
if (hostAndPort == null) {
return null;
}
return hostAndPort.getHost();
}

private Integer parseServerPort(String endpoint) {
if (endpoint.isEmpty()) {
return null;
}
HostAndPort hostAndPort = parseServerHostAndPort(endpoint);
if (!hostAndPort.hasPort()) {
return null;
}
return hostAndPort.getPort();
}

private HostAndPort parseServerHostAndPort(String endpoint) {
String hostPort = endpoint;
if (hostPort.contains("://")) {
// Strip the scheme if present. HostAndPort doesn't support schemes.
hostPort = hostPort.substring(hostPort.indexOf("://") + 3);
}
try {
return HostAndPort.fromString(hostPort).getHost();
return HostAndPort.fromString(hostPort);
} catch (IllegalArgumentException e) {
// Fallback for cases HostAndPort can't handle.
return hostPort;
return null;
}
}

Expand Down Expand Up @@ -440,7 +466,8 @@ public EndpointContext build() throws IOException {
setResolvedUniverseDomain(determineUniverseDomain());
String endpoint = determineEndpoint();
setResolvedEndpoint(endpoint);
setResolvedServerAddress(parseServerAddress(endpoint));
setResolvedServerAddress(parseServerAddress(resolvedEndpoint()));
setResolvedServerPort(parseServerPort(resolvedEndpoint()));
setUseS2A(shouldUseS2A());
return autoBuild();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.google.api.core.InternalApi;
import com.google.api.gax.rpc.LibraryMetadata;
import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
Expand All @@ -49,20 +50,26 @@ public abstract class ApiTracerContext {
@Nullable
public abstract String serverAddress();

@Nullable
public abstract Integer serverPort();

public abstract LibraryMetadata libraryMetadata();

/**
* @return a map of attributes to be included in attempt-level spans
*/
public Map<String, String> getAttemptAttributes() {
Map<String, String> attributes = new HashMap<>();
if (serverAddress() != null) {
public Map<String, Object> getAttemptAttributes() {
Map<String, Object> attributes = new HashMap<>();
if (!Strings.isNullOrEmpty(serverAddress())) {
attributes.put(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE, serverAddress());
}
if (libraryMetadata().repository() != null) {
if (serverPort() != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to check both null and empty, this applies to all attributes too. We can use Strings.isNullOrEmpty.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the check for the other if-statements, but serverPort() is an Integer.

attributes.put(ObservabilityAttributes.SERVER_PORT_ATTRIBUTE, serverPort());
}
if (!Strings.isNullOrEmpty(libraryMetadata().repository())) {
attributes.put(ObservabilityAttributes.REPO_ATTRIBUTE, libraryMetadata().repository());
}
if (libraryMetadata().artifactName() != null) {
if (!Strings.isNullOrEmpty(libraryMetadata().artifactName())) {
attributes.put(ObservabilityAttributes.ARTIFACT_ATTRIBUTE, libraryMetadata().artifactName());
}
return attributes;
Expand All @@ -82,6 +89,8 @@ public abstract static class Builder {

public abstract Builder setLibraryMetadata(LibraryMetadata gapicProperties);

public abstract Builder setServerPort(Integer serverPort);

public abstract ApiTracerContext build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public class ObservabilityAttributes {
/** The address of the server being called (e.g., "pubsub.googleapis.com"). */
public static final String SERVER_ADDRESS_ATTRIBUTE = "server.address";

/** The port of the server being called (e.g., 443). */
public static final String SERVER_PORT_ATTRIBUTE = "server.port";

/** The repository of the client library (e.g., "googleapis/google-cloud-java"). */
public static final String REPO_ATTRIBUTE = "gcp.client.repo";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,21 @@
}

@Override
public Span createSpan(String name, Map<String, String> attributes) {
public Span createSpan(String name, Map<String, Object> attributes) {
SpanBuilder spanBuilder = tracer.spanBuilder(name);

// Attempt spans are of the CLIENT kind
spanBuilder.setSpanKind(SpanKind.CLIENT);

if (attributes != null) {
attributes.forEach((k, v) -> spanBuilder.setAttribute(k, v));
attributes.forEach(
(k, v) -> {
if (v instanceof String) {
spanBuilder.setAttribute(k, (String) v);
} else if (v instanceof Integer) {
spanBuilder.setAttribute(k, (long) (Integer) v);

Check warning on line 66 in gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceManager.java

View check run for this annotation

SonarQubeCloud / [gapic-generator-java-root] SonarCloud Code Analysis

Remove this unnecessary cast to "long".

See more on https://sonarcloud.io/project/issues?id=googleapis_gapic-generator-java&issues=AZywvCGW-IR5czq3Uy6v&open=AZywvCGW-IR5czq3Uy6v&pullRequest=4128

Check warning on line 66 in gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTraceManager.java

View check run for this annotation

SonarQubeCloud / [java_showcase_integration_tests] SonarCloud Code Analysis

Remove this unnecessary cast to "long".

See more on https://sonarcloud.io/project/issues?id=googleapis_gapic-generator-java_integration_tests&issues=AZywv6UVIO2amdAQe0SJ&open=AZywv6UVIO2amdAQe0SJ&pullRequest=4128
}
Comment on lines +62 to +67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation for setting attributes on the span only handles String and Integer types. While this is sufficient for the current changes, it makes the code brittle. If other attribute types (like Long, Double, or Boolean) are added in the future, they will be silently ignored. To improve future maintainability, it would be more robust to handle all primitive types supported by OpenTelemetry attributes.

          (k, v) -> {
            if (v instanceof String) {
              spanBuilder.setAttribute(k, (String) v);
            } else if (v instanceof Integer) {
              spanBuilder.setAttribute(k, (long) (Integer) v);
            } else if (v instanceof Long) {
              spanBuilder.setAttribute(k, (Long) v);
            } else if (v instanceof Double) {
              spanBuilder.setAttribute(k, (Double) v);
            } else if (v instanceof Boolean) {
              spanBuilder.setAttribute(k, (Boolean) v);
            }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/gemini I think it's worth to restrict it to only types we are introducing for now. New types are not expected and expanding this list will also imply additional testing (maintenance) for adding only one more type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the type matter? Would it cause any differences in the UI? If not, can we just use String?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Offline discussion: let's conform to SemConv and keep port as an Integer.

});
}

io.opentelemetry.api.trace.Span span = spanBuilder.startSpan();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public class SpanTracer implements ApiTracer {
public static final String DEFAULT_LANGUAGE = "Java";

private final TraceManager traceManager;
private final Map<String, String> attemptAttributes;
private final Map<String, Object> attemptAttributes;
private final String attemptSpanName;
private final ApiTracerContext apiTracerContext;
private TraceManager.Span attemptHandle;
Expand All @@ -75,7 +75,7 @@ private void buildAttributes() {

@Override
public void attemptStarted(Object request, int attemptNumber) {
Map<String, String> attemptAttributes = new HashMap<>(this.attemptAttributes);
Map<String, Object> attemptAttributes = new HashMap<>(this.attemptAttributes);
// Start the specific attempt span with the operation span as parent
this.attemptHandle = traceManager.createSpan(attemptSpanName, attemptAttributes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
@InternalApi
public interface TraceManager {
/** Starts a span and returns a handle to manage its lifecycle. */
Span createSpan(String name, Map<String, String> attributes);
Span createSpan(String name, Map<String, Object> attributes);

interface Span {
void end();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ void shouldUseS2A_success() throws IOException {
}

@Test
void endpointContextBuild_resolvesPortAndServerAddress() throws IOException {
void endpointContextBuild_resolvesServerAddress() throws IOException {
String endpoint = "http://localhost:7469";
EndpointContext endpointContext =
defaultEndpointContextBuilder
Expand Down Expand Up @@ -638,4 +638,49 @@ void endpointContextBuild_resolvesPortAndServerAddress() throws IOException {
.build();
Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("2001:db8::1");
}

@Test
void endpointContextBuild_resolvesPort() throws IOException {
String endpoint = "http://localhost:7469";
EndpointContext endpointContext =
defaultEndpointContextBuilder
.setClientSettingsEndpoint(endpoint)
.setTransportChannelProviderEndpoint(null)
.build();
Truth.assertThat(endpointContext.resolvedServerPort()).isEqualTo(7469);

endpoint = "localhost:7469";
endpointContext =
defaultEndpointContextBuilder
.setClientSettingsEndpoint(endpoint)
.setTransportChannelProviderEndpoint(null)
.build();
Truth.assertThat(endpointContext.resolvedServerPort()).isEqualTo(7469);

endpoint = "test.googleapis.com:443";
endpointContext =
defaultEndpointContextBuilder
.setClientSettingsEndpoint(endpoint)
.setTransportChannelProviderEndpoint(null)
.build();
Truth.assertThat(endpointContext.resolvedServerPort()).isEqualTo(443);

// IPv6 literal with port
endpoint = "[2001:db8::1]:443";
endpointContext =
defaultEndpointContextBuilder
.setClientSettingsEndpoint(endpoint)
.setTransportChannelProviderEndpoint(null)
.build();
Truth.assertThat(endpointContext.resolvedServerPort()).isEqualTo(443);

// Bare IPv6 literal (no port)
endpoint = "2001:db8::1";
endpointContext =
defaultEndpointContextBuilder
.setClientSettingsEndpoint(endpoint)
.setTransportChannelProviderEndpoint(null)
.build();
Truth.assertThat(endpointContext.resolvedServerPort()).isNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ void testGetAttemptAttributes_serverAddress() {
.setLibraryMetadata(LibraryMetadata.empty())
.setServerAddress("test-address")
.build();
Map<String, String> attributes = context.getAttemptAttributes();
Map<String, Object> attributes = context.getAttemptAttributes();

assertThat(attributes)
.containsEntry(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE, "test-address");
Expand All @@ -57,7 +57,7 @@ void testGetAttemptAttributes_repo() {
LibraryMetadata.newBuilder().setRepository("test-repo").build();
ApiTracerContext context =
ApiTracerContext.newBuilder().setLibraryMetadata(libraryMetadata).build();
Map<String, String> attributes = context.getAttemptAttributes();
Map<String, Object> attributes = context.getAttemptAttributes();

assertThat(attributes).containsEntry(ObservabilityAttributes.REPO_ATTRIBUTE, "test-repo");
}
Expand All @@ -68,7 +68,7 @@ void testGetAttemptAttributes_artifact() {
LibraryMetadata.newBuilder().setArtifactName("test-artifact").build();
ApiTracerContext context =
ApiTracerContext.newBuilder().setLibraryMetadata(libraryMetadata).build();
Map<String, String> attributes = context.getAttemptAttributes();
Map<String, Object> attributes = context.getAttemptAttributes();

assertThat(attributes)
.containsEntry(ObservabilityAttributes.ARTIFACT_ATTRIBUTE, "test-artifact");
Expand All @@ -77,7 +77,21 @@ void testGetAttemptAttributes_artifact() {
@Test
void testGetAttemptAttributes_empty() {
ApiTracerContext context = ApiTracerContext.empty();
Map<String, String> attributes = context.getAttemptAttributes();
Map<String, Object> attributes = context.getAttemptAttributes();

assertThat(attributes).isEmpty();
}

@Test
void testGetAttemptAttributes_emptyStrings() {
LibraryMetadata libraryMetadata =
LibraryMetadata.newBuilder().setRepository("").setArtifactName("").build();
ApiTracerContext context =
ApiTracerContext.newBuilder()
.setLibraryMetadata(libraryMetadata)
.setServerAddress("")
.build();
Map<String, Object> attributes = context.getAttemptAttributes();

assertThat(attributes).isEmpty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

package com.google.api.gax.tracing;

import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -90,7 +91,7 @@ void testCreateSpan_attempt_isClient() {
@Test
void testCreateSpan_recordsSpan() {
String spanName = "test-span";
Map<String, String> attributes = ImmutableMap.of("key1", "value1");
Map<String, Object> attributes = ImmutableMap.of("key1", "value1");

when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder);
when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder);
Expand All @@ -102,4 +103,19 @@ void testCreateSpan_recordsSpan() {

verify(span).end();
}

@Test
void testCreateSpan_recordsIntegerAttribute() {
String spanName = "test-span";
Map<String, Object> attributes = ImmutableMap.of("port", 443);

when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder);
when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder);
when(spanBuilder.setAttribute(anyString(), anyLong())).thenReturn(spanBuilder);
when(spanBuilder.startSpan()).thenReturn(span);

recorder.createSpan(spanName, attributes);

verify(spanBuilder).setAttribute("port", 443L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ void testNewTracer_addsAttributes() {

tracer.attemptStarted(null, 1);

ArgumentCaptor<Map<String, String>> attributesCaptor = ArgumentCaptor.forClass(Map.class);
ArgumentCaptor<Map<String, Object>> attributesCaptor = ArgumentCaptor.forClass(Map.class);
verify(recorder, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture());

Map<String, String> attemptAttributes = attributesCaptor.getValue();
Map<String, Object> attemptAttributes = attributesCaptor.getValue();
assertThat(attemptAttributes).containsEntry("server.address", "test-address");
}

Expand All @@ -104,10 +104,10 @@ void testWithContext_addsInferredAttributes() {

tracer.attemptStarted(null, 1);

ArgumentCaptor<Map<String, String>> attributesCaptor = ArgumentCaptor.forClass(Map.class);
ArgumentCaptor<Map<String, Object>> attributesCaptor = ArgumentCaptor.forClass(Map.class);
verify(recorder, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture());

Map<String, String> attemptAttributes = attributesCaptor.getValue();
Map<String, Object> attemptAttributes = attributesCaptor.getValue();
assertThat(attemptAttributes)
.containsEntry(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE, "example.com");
}
Expand All @@ -129,10 +129,10 @@ void testWithContext_noEndpointContext_doesNotAddAttributes() {

tracer.attemptStarted(null, 1);

ArgumentCaptor<Map<String, String>> attributesCaptor = ArgumentCaptor.forClass(Map.class);
ArgumentCaptor<Map<String, Object>> attributesCaptor = ArgumentCaptor.forClass(Map.class);
verify(recorder, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture());

Map<String, String> attemptAttributes = attributesCaptor.getValue();
Map<String, Object> attemptAttributes = attributesCaptor.getValue();
assertThat(attemptAttributes)
.doesNotContainKey(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ void testAttemptStarted_includesLanguageAttribute() {

tracer.attemptStarted(new Object(), 1);

ArgumentCaptor<Map<String, String>> attributesCaptor = ArgumentCaptor.forClass(Map.class);
ArgumentCaptor<Map<String, Object>> attributesCaptor = ArgumentCaptor.forClass(Map.class);
verify(recorder).createSpan(eq(ATTEMPT_SPAN_NAME), attributesCaptor.capture());

assertThat(attributesCaptor.getValue())
Expand Down
Loading
Loading