From 9e08c8f6e85dbcbe01c4806881f865d9b3ac04b0 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Mon, 2 Mar 2026 17:21:00 -0500 Subject: [PATCH 1/7] feat(o11y): introduce server.port attribute --- .../com/google/api/gax/rpc/ClientContext.java | 1 + .../google/api/gax/rpc/EndpointContext.java | 27 +++++++++++++++++++ .../api/gax/tracing/ApiTracerContext.java | 12 +++++++-- .../gax/tracing/ObservabilityAttributes.java | 3 +++ .../tracing/OpenTelemetryTraceManager.java | 11 ++++++-- .../google/api/gax/tracing/SpanTracer.java | 4 +-- .../google/api/gax/tracing/TraceManager.java | 2 +- .../api/gax/rpc/EndpointContextTest.java | 5 ++++ .../OpenTelemetryTraceManagerTest.java | 2 +- .../gax/tracing/SpanTracerFactoryTest.java | 12 ++++----- .../api/gax/tracing/SpanTracerTest.java | 2 +- .../showcase/v1beta1/it/ITOtelTracing.java | 11 ++++++++ 12 files changed, 77 insertions(+), 15 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 e782fdd926..aff25a95c4 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 @@ -273,6 +273,7 @@ public static ClientContext create(StubSettings settings) throws IOException { ApiTracerContext apiTracerContext = ApiTracerContext.newBuilder() .setServerAddress(endpointContext.resolvedServerAddress()) + .setServerPort(endpointContext.resolvedServerPort()) .build(); ApiTracerFactory apiTracerFactory = settings.getTracerFactory().withContext(apiTracerContext); 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 84111dd620..95ce2d3729 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 @@ -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() { @@ -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); @@ -404,6 +409,27 @@ private String parseServerAddress(String endpoint) { } } + private Integer parseServerPort(String endpoint) { + if (Strings.isNullOrEmpty(endpoint)) { + return null; + } + String hostPort = endpoint; + if (hostPort.contains("://")) { + // Strip the scheme if present. HostAndPort doesn't support schemes. + hostPort = hostPort.substring(hostPort.indexOf("://") + 3); + } + try { + HostAndPort parsedHostPort = HostAndPort.fromString(hostPort); + if (parsedHostPort.hasPort()) { + return parsedHostPort.getPort(); + } + return null; + } catch (IllegalArgumentException e) { + // Fallback for cases HostAndPort can't handle. + return null; + } + } + // Default to port 443 for HTTPS. Using HTTP requires explicitly setting the endpoint private String buildEndpointTemplate(String serviceName, String resolvedUniverseDomain) { return serviceName + "." + resolvedUniverseDomain + ":443"; @@ -441,6 +467,7 @@ public EndpointContext build() throws IOException { String endpoint = determineEndpoint(); setResolvedEndpoint(endpoint); setResolvedServerAddress(parseServerAddress(endpoint)); + setResolvedServerPort(parseServerPort(endpoint)); setUseS2A(shouldUseS2A()); return autoBuild(); } 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 ea15b315f6..7d5866f96c 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 @@ -49,17 +49,23 @@ public abstract class ApiTracerContext { /** * @return a map of attributes to be included in attempt-level spans */ - public Map getAttemptAttributes() { - Map attributes = new HashMap<>(); + public Map getAttemptAttributes() { + Map attributes = new HashMap<>(); if (getServerAddress() != null) { attributes.put(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE, getServerAddress()); } + if (getServerPort() != null) { + attributes.put(ObservabilityAttributes.SERVER_PORT_ATTRIBUTE, getServerPort()); + } return attributes; } @Nullable public abstract String getServerAddress(); + @Nullable + public abstract Integer getServerPort(); + public static Builder newBuilder() { return new AutoValue_ApiTracerContext.Builder(); } @@ -68,6 +74,8 @@ public static Builder newBuilder() { public abstract static class Builder { public abstract Builder setServerAddress(String serverAddress); + public abstract Builder setServerPort(Integer serverPort); + public abstract ApiTracerContext build(); } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java index 9845a5b052..e575ccb78e 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java @@ -43,4 +43,7 @@ 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"; } 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 index fbbdb292b0..923b524648 100644 --- 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 @@ -51,14 +51,21 @@ public OpenTelemetryTraceManager(OpenTelemetry openTelemetry) { } @Override - public Span createSpan(String name, Map attributes) { + public Span createSpan(String name, Map 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); + } + }); } io.opentelemetry.api.trace.Span span = spanBuilder.startSpan(); 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 7bbe435d8b..c5c28aebe0 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 @@ -48,7 +48,7 @@ public class SpanTracer implements ApiTracer { public static final String DEFAULT_LANGUAGE = "Java"; private final TraceManager traceManager; - private final Map attemptAttributes; + private final Map attemptAttributes; private final String attemptSpanName; private final ApiTracerContext apiTracerContext; private TraceManager.Span attemptHandle; @@ -75,7 +75,7 @@ private void buildAttributes() { @Override public void attemptStarted(Object request, int attemptNumber) { - Map attemptAttributes = new HashMap<>(this.attemptAttributes); + Map attemptAttributes = new HashMap<>(this.attemptAttributes); // Start the specific attempt span with the operation span as parent this.attemptHandle = traceManager.createSpan(attemptSpanName, attemptAttributes); } 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 index edafd1dfb2..8572d1ce11 100644 --- 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 @@ -42,7 +42,7 @@ @InternalApi public interface TraceManager { /** Starts a span and returns a handle to manage its lifecycle. */ - Span createSpan(String name, Map attributes); + Span createSpan(String name, Map attributes); interface Span { void end(); 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 c1bcc50512..c9a3c6f7ef 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 @@ -603,6 +603,7 @@ void endpointContextBuild_resolvesPortAndServerAddress() throws IOException { .setTransportChannelProviderEndpoint(null) .build(); Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("localhost"); + Truth.assertThat(endpointContext.resolvedServerPort()).isEqualTo(7469); endpoint = "localhost:7469"; endpointContext = @@ -611,6 +612,7 @@ void endpointContextBuild_resolvesPortAndServerAddress() throws IOException { .setTransportChannelProviderEndpoint(null) .build(); Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("localhost"); + Truth.assertThat(endpointContext.resolvedServerPort()).isEqualTo(7469); endpoint = "test.googleapis.com:443"; endpointContext = @@ -619,6 +621,7 @@ void endpointContextBuild_resolvesPortAndServerAddress() throws IOException { .setTransportChannelProviderEndpoint(null) .build(); Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("test.googleapis.com"); + Truth.assertThat(endpointContext.resolvedServerPort()).isEqualTo(443); // IPv6 literal with port endpoint = "[2001:db8::1]:443"; @@ -628,6 +631,7 @@ void endpointContextBuild_resolvesPortAndServerAddress() throws IOException { .setTransportChannelProviderEndpoint(null) .build(); Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("2001:db8::1"); + Truth.assertThat(endpointContext.resolvedServerPort()).isEqualTo(443); // Bare IPv6 literal (no port) endpoint = "2001:db8::1"; @@ -637,5 +641,6 @@ void endpointContextBuild_resolvesPortAndServerAddress() throws IOException { .setTransportChannelProviderEndpoint(null) .build(); Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("2001:db8::1"); + Truth.assertThat(endpointContext.resolvedServerPort()).isNull(); } } 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 index 9dc692904c..831a706c5d 100644 --- 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 @@ -90,7 +90,7 @@ void testCreateSpan_attempt_isClient() { @Test void testCreateSpan_recordsSpan() { String spanName = "test-span"; - Map attributes = ImmutableMap.of("key1", "value1"); + Map attributes = ImmutableMap.of("key1", "value1"); when(tracer.spanBuilder(spanName)).thenReturn(spanBuilder); when(spanBuilder.setSpanKind(SpanKind.CLIENT)).thenReturn(spanBuilder); 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 84a6418609..e90bdb52e6 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 @@ -72,10 +72,10 @@ void testNewTracer_addsAttributes() { tracer.attemptStarted(null, 1); - ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); + ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); verify(recorder, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture()); - Map attemptAttributes = attributesCaptor.getValue(); + Map attemptAttributes = attributesCaptor.getValue(); assertThat(attemptAttributes).containsEntry("server.address", "test-address"); } @@ -97,10 +97,10 @@ void testWithContext_addsInferredAttributes() { tracer.attemptStarted(null, 1); - ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); + ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); verify(recorder, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture()); - Map attemptAttributes = attributesCaptor.getValue(); + Map attemptAttributes = attributesCaptor.getValue(); assertThat(attemptAttributes) .containsEntry(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE, "example.com"); } @@ -122,10 +122,10 @@ void testWithContext_noEndpointContext_doesNotAddAttributes() { tracer.attemptStarted(null, 1); - ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); + ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); verify(recorder, atLeastOnce()).createSpan(anyString(), attributesCaptor.capture()); - Map attemptAttributes = attributesCaptor.getValue(); + Map attemptAttributes = attributesCaptor.getValue(); assertThat(attemptAttributes) .doesNotContainKey(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE); } 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 23568cce43..dc55762204 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 @@ -70,7 +70,7 @@ void testAttemptStarted_includesLanguageAttribute() { tracer.attemptStarted(new Object(), 1); - ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); + ArgumentCaptor> attributesCaptor = ArgumentCaptor.forClass(Map.class); verify(recorder).createSpan(eq(ATTEMPT_SPAN_NAME), attributesCaptor.capture()); assertThat(attributesCaptor.getValue()) 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 c2270c3ada..732cffb437 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 @@ -54,6 +54,7 @@ class ITOtelTracing { private static final String SHOWCASE_SERVER_ADDRESS = "localhost"; + private static final long SHOWCASE_SERVER_PORT = 7469; private InMemorySpanExporter spanExporter; private OpenTelemetrySdk openTelemetrySdk; @@ -108,6 +109,11 @@ void testTracing_successfulEcho_grpc() throws Exception { .getAttributes() .get(AttributeKey.stringKey(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE))) .isEqualTo(SHOWCASE_SERVER_ADDRESS); + assertThat( + attemptSpan + .getAttributes() + .get(AttributeKey.longKey(ObservabilityAttributes.SERVER_PORT_ATTRIBUTE))) + .isEqualTo(SHOWCASE_SERVER_PORT); } } @@ -140,6 +146,11 @@ void testTracing_successfulEcho_httpjson() throws Exception { .getAttributes() .get(AttributeKey.stringKey(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE))) .isEqualTo(SHOWCASE_SERVER_ADDRESS); + assertThat( + attemptSpan + .getAttributes() + .get(AttributeKey.longKey(ObservabilityAttributes.SERVER_PORT_ATTRIBUTE))) + .isEqualTo(SHOWCASE_SERVER_PORT); } } } From 235fa2b1a9315fbc516a257779444d67628b7b56 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Wed, 4 Mar 2026 11:46:29 -0500 Subject: [PATCH 2/7] fix: deduplicate logic in EndpointContext.getServer[Port|Host] --- .../google/api/gax/rpc/EndpointContext.java | 31 ++++++++----------- 1 file changed, 13 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 95ce2d3729..fd0bb22aff 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 @@ -393,23 +393,22 @@ 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 { - return HostAndPort.fromString(hostPort).getHost(); - } catch (IllegalArgumentException e) { - // Fallback for cases HostAndPort can't handle. - return hostPort; + HostAndPort hostAndPort = parseServerHostAndPort(endpoint); + if (hostAndPort == null) { + return null; } + return hostAndPort.getHost(); } private Integer parseServerPort(String endpoint) { + HostAndPort hostAndPort = parseServerHostAndPort(endpoint); + if (hostAndPort == null || !hostAndPort.hasPort()) { + return null; + } + return hostAndPort.getPort(); + } + + private HostAndPort parseServerHostAndPort(String endpoint) { if (Strings.isNullOrEmpty(endpoint)) { return null; } @@ -419,11 +418,7 @@ private Integer parseServerPort(String endpoint) { hostPort = hostPort.substring(hostPort.indexOf("://") + 3); } try { - HostAndPort parsedHostPort = HostAndPort.fromString(hostPort); - if (parsedHostPort.hasPort()) { - return parsedHostPort.getPort(); - } - return null; + return HostAndPort.fromString(hostPort); } catch (IllegalArgumentException e) { // Fallback for cases HostAndPort can't handle. return null; From 24a2af188db7cccc5a60be9ac4e7b42a4f3138e3 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Wed, 4 Mar 2026 11:52:15 -0500 Subject: [PATCH 3/7] fix: use isNullOrEmpty in ApiTracerContext --- .../google/api/gax/tracing/ApiTracerContext.java | 7 ++++--- .../api/gax/tracing/ApiTracerContextTest.java | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 3 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 ef82706228..073b6ef90c 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 @@ -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; @@ -59,16 +60,16 @@ public abstract class ApiTracerContext { */ public Map getAttemptAttributes() { Map attributes = new HashMap<>(); - if (serverAddress() != null) { + if (!Strings.isNullOrEmpty(serverAddress())) { attributes.put(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE, serverAddress()); } if (serverPort() != null) { attributes.put(ObservabilityAttributes.SERVER_PORT_ATTRIBUTE, serverPort()); } - if (libraryMetadata().repository() != null) { + 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; diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/ApiTracerContextTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/ApiTracerContextTest.java index a4d936ea3c..cc40fffecd 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/ApiTracerContextTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/ApiTracerContextTest.java @@ -81,4 +81,18 @@ void testGetAttemptAttributes_empty() { assertThat(attributes).isEmpty(); } + + @Test + void testGetAttemptAttributes_emptyStrings() { + LibraryMetadata libraryMetadata = + LibraryMetadata.newBuilder().setRepository("").setArtifactName("").build(); + ApiTracerContext context = + ApiTracerContext.newBuilder() + .setLibraryMetadata(libraryMetadata) + .setServerAddress("") + .build(); + Map attributes = context.getAttemptAttributes(); + + assertThat(attributes).isEmpty(); + } } From 816d2638b94d35fd4fde76e7f21b6959628820d7 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Wed, 4 Mar 2026 11:54:12 -0500 Subject: [PATCH 4/7] test: separation of concerns for port and host tests in EndpointContextTest --- .../api/gax/rpc/EndpointContextTest.java | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) 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 c9a3c6f7ef..99a8f63fcf 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 @@ -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 @@ -603,7 +603,6 @@ void endpointContextBuild_resolvesPortAndServerAddress() throws IOException { .setTransportChannelProviderEndpoint(null) .build(); Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("localhost"); - Truth.assertThat(endpointContext.resolvedServerPort()).isEqualTo(7469); endpoint = "localhost:7469"; endpointContext = @@ -612,7 +611,6 @@ void endpointContextBuild_resolvesPortAndServerAddress() throws IOException { .setTransportChannelProviderEndpoint(null) .build(); Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("localhost"); - Truth.assertThat(endpointContext.resolvedServerPort()).isEqualTo(7469); endpoint = "test.googleapis.com:443"; endpointContext = @@ -621,7 +619,6 @@ void endpointContextBuild_resolvesPortAndServerAddress() throws IOException { .setTransportChannelProviderEndpoint(null) .build(); Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("test.googleapis.com"); - Truth.assertThat(endpointContext.resolvedServerPort()).isEqualTo(443); // IPv6 literal with port endpoint = "[2001:db8::1]:443"; @@ -631,7 +628,6 @@ void endpointContextBuild_resolvesPortAndServerAddress() throws IOException { .setTransportChannelProviderEndpoint(null) .build(); Truth.assertThat(endpointContext.resolvedServerAddress()).isEqualTo("2001:db8::1"); - Truth.assertThat(endpointContext.resolvedServerPort()).isEqualTo(443); // Bare IPv6 literal (no port) endpoint = "2001:db8::1"; @@ -641,6 +637,50 @@ void endpointContextBuild_resolvesPortAndServerAddress() throws IOException { .setTransportChannelProviderEndpoint(null) .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(); } } From dda82516e9fa448b285b7e04e62e7f19c1194c72 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Wed, 4 Mar 2026 12:14:04 -0500 Subject: [PATCH 5/7] fix: restore original logic of host parsing in EndpointContext --- .../src/main/java/com/google/api/gax/rpc/EndpointContext.java | 3 +++ 1 file changed, 3 insertions(+) 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 fd0bb22aff..2fe6b950e2 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 @@ -393,6 +393,9 @@ boolean shouldUseS2A() { } private String parseServerAddress(String endpoint) { + if (Strings.isNullOrEmpty(endpoint)) { + return endpoint; + } HostAndPort hostAndPort = parseServerHostAndPort(endpoint); if (hostAndPort == null) { return null; From 14d3b8738120eb5ff2948f04c1e9070916317cb2 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Wed, 4 Mar 2026 13:56:32 -0500 Subject: [PATCH 6/7] test: cover integer logic in OtelSpanManager --- .../tracing/OpenTelemetryTraceManagerTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 index 831a706c5d..1e466cfb20 100644 --- 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 @@ -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; @@ -102,4 +103,19 @@ void testCreateSpan_recordsSpan() { verify(span).end(); } + + @Test + void testCreateSpan_recordsIntegerAttribute() { + String spanName = "test-span"; + Map 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); + } } From 50667c3a0f157bc1981b19645f6b6cc64bf3bb3b Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Thu, 5 Mar 2026 11:33:44 -0500 Subject: [PATCH 7/7] fix: use resolvedEndpoint() --- .../com/google/api/gax/rpc/EndpointContext.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 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 2fe6b950e2..09e105cceb 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 @@ -269,6 +269,8 @@ public abstract static class Builder { abstract String resolvedUniverseDomain(); + abstract String resolvedEndpoint(); + abstract EndpointContext autoBuild(); private String determineUniverseDomain() { @@ -393,7 +395,7 @@ boolean shouldUseS2A() { } private String parseServerAddress(String endpoint) { - if (Strings.isNullOrEmpty(endpoint)) { + if (endpoint.isEmpty()) { return endpoint; } HostAndPort hostAndPort = parseServerHostAndPort(endpoint); @@ -404,17 +406,17 @@ private String parseServerAddress(String endpoint) { } private Integer parseServerPort(String endpoint) { + if (endpoint.isEmpty()) { + return null; + } HostAndPort hostAndPort = parseServerHostAndPort(endpoint); - if (hostAndPort == null || !hostAndPort.hasPort()) { + if (!hostAndPort.hasPort()) { return null; } return hostAndPort.getPort(); } private HostAndPort parseServerHostAndPort(String endpoint) { - if (Strings.isNullOrEmpty(endpoint)) { - return null; - } String hostPort = endpoint; if (hostPort.contains("://")) { // Strip the scheme if present. HostAndPort doesn't support schemes. @@ -464,8 +466,8 @@ public EndpointContext build() throws IOException { setResolvedUniverseDomain(determineUniverseDomain()); String endpoint = determineEndpoint(); setResolvedEndpoint(endpoint); - setResolvedServerAddress(parseServerAddress(endpoint)); - setResolvedServerPort(parseServerPort(endpoint)); + setResolvedServerAddress(parseServerAddress(resolvedEndpoint())); + setResolvedServerPort(parseServerPort(resolvedEndpoint())); setUseS2A(shouldUseS2A()); return autoBuild(); }