From 5ef9243d77a7a35860166650d7c93519f563a2cc Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 30 Mar 2026 15:14:21 -0400 Subject: [PATCH 1/5] feat(observability): implement url.domain attribute --- .../com/google/api/gax/rpc/ClientContext.java | 7 +++ .../api/gax/tracing/ApiTracerContext.java | 9 ++- .../api/gax/tracing/ApiTracerContextTest.java | 16 ++++- .../showcase/v1beta1/it/ITOtelTracing.java | 61 +++++++++++++++++-- 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index 43fdd848b6c7..0ee74cfc422e 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -271,11 +271,18 @@ public static ClientContext create(StubSettings settings) throws IOException { if (watchdogProvider != null && watchdogProvider.shouldAutoClose()) { backgroundResources.add(watchdog); } + String serviceName = endpointContext.serviceName(); + String urlDomain = null; + if (!Strings.isNullOrEmpty(serviceName)) { + urlDomain = serviceName + "." + endpointContext.resolvedUniverseDomain(); + } + ApiTracerContext apiTracerContext = ApiTracerContext.newBuilder() .setServerAddress(endpointContext.resolvedServerAddress()) .setServerPort(endpointContext.resolvedServerPort()) .setLibraryMetadata(settings.getLibraryMetadata()) + .setUrlDomain(urlDomain) .build(); ApiTracerFactory apiTracerFactory = settings.getTracerFactory(); if (apiTracerFactory instanceof SpanTracerFactory) { diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java index 49bce86cd1fe..dffc1df0695b 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerContext.java @@ -212,6 +212,9 @@ public Map getAttemptAttributes() { attributes.put( ObservabilityAttributes.DESTINATION_RESOURCE_ID_ATTRIBUTE, destinationResourceId()); } + if (!Strings.isNullOrEmpty(urlDomain())) { + attributes.put(ObservabilityAttributes.URL_DOMAIN_ATTRIBUTE, urlDomain()); + } return attributes; } @@ -232,10 +235,10 @@ Map getMetricsAttributes() { if (!Strings.isNullOrEmpty(fullMethodName())) { attributes.put(ObservabilityAttributes.GRPC_RPC_METHOD_ATTRIBUTE, fullMethodName()); } + if (!Strings.isNullOrEmpty(urlDomain())) { + attributes.put(ObservabilityAttributes.URL_DOMAIN_ATTRIBUTE, urlDomain()); + } if (transport() == Transport.HTTP) { - if (!Strings.isNullOrEmpty(urlDomain())) { - attributes.put(ObservabilityAttributes.URL_DOMAIN_ATTRIBUTE, urlDomain()); - } if (!Strings.isNullOrEmpty(httpPathTemplate())) { attributes.put(ObservabilityAttributes.URL_TEMPLATE_ATTRIBUTE, httpPathTemplate()); } diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ApiTracerContextTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ApiTracerContextTest.java index b465dfbebe02..b141d1de3349 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ApiTracerContextTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ApiTracerContextTest.java @@ -186,6 +186,19 @@ void testGetAttemptAttributes_emptyStrings() { assertThat(attributes).isEmpty(); } + @Test + void testGetAttemptAttributes_urlDomain() { + ApiTracerContext context = + ApiTracerContext.newBuilder() + .setLibraryMetadata(LibraryMetadata.empty()) + .setUrlDomain("test-domain.com") + .build(); + Map attributes = context.getAttemptAttributes(); + + assertThat(attributes) + .containsEntry(ObservabilityAttributes.URL_DOMAIN_ATTRIBUTE, "test-domain.com"); + } + @Test void testGetMetricsAttributes_serverPort() { ApiTracerContext context = @@ -304,7 +317,8 @@ void testGetMetricsAttributes_urlDomain_notHttp() { .build(); Map attributes = context.getMetricsAttributes(); - assertThat(attributes).doesNotContainKey(ObservabilityAttributes.URL_DOMAIN_ATTRIBUTE); + assertThat(attributes) + .containsEntry(ObservabilityAttributes.URL_DOMAIN_ATTRIBUTE, "test-domain.com"); } @Test diff --git a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java index ff4731c99d36..328b654d3846 100644 --- a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java +++ b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java @@ -34,6 +34,7 @@ import static org.junit.Assert.assertThrows; import com.google.api.client.http.javanet.NetHttpTransport; +import io.grpc.ManagedChannelBuilder; import com.google.api.gax.core.NoCredentialsProvider; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.StatusCode; @@ -98,8 +99,28 @@ void tearDown() { void testTracing_successfulEcho_grpc() throws Exception { SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk); - try (EchoClient client = - TestClientInitializer.createGrpcEchoClientOpentelemetry(tracingFactory)) { + EchoSettings grpcEchoSettings = + EchoSettings.newBuilder() + .setCredentialsProvider(NoCredentialsProvider.create()) + .setTransportChannelProvider( + EchoSettings.defaultGrpcTransportProviderBuilder() + .setChannelConfigurator(ManagedChannelBuilder::usePlaintext) + .build()) + .setEndpoint("localhost:7469") + .build(); + + EchoStubSettings.Builder stubSettingsBuilder = (EchoStubSettings.Builder) grpcEchoSettings.getStubSettings().toBuilder(); + stubSettingsBuilder.setTracerFactory(tracingFactory); + + EchoStubSettings stubSettings = + new EchoStubSettings(stubSettingsBuilder) { + @Override + public String getServiceName() { + return "showcase"; + } + }; + + try (EchoClient client = EchoClient.create(stubSettings.createStub())) { client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); @@ -147,8 +168,12 @@ void testTracing_successfulEcho_grpc() throws Exception { .getAttributes() .get(AttributeKey.stringKey(ObservabilityAttributes.GRPC_RPC_METHOD_ATTRIBUTE))) .isEqualTo("google.showcase.v1beta1.Echo/Echo"); + assertThat( + attemptSpan + .getAttributes() + .get(AttributeKey.stringKey(ObservabilityAttributes.URL_DOMAIN_ATTRIBUTE))) + .isEqualTo("showcase.googleapis.com"); assertThat(attemptSpan.getInstrumentationScopeInfo().getName()).isEqualTo(SHOWCASE_ARTIFACT); - // {x-version-update-end} } } @@ -156,8 +181,29 @@ void testTracing_successfulEcho_grpc() throws Exception { void testTracing_successfulEcho_httpjson() throws Exception { SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk); - try (EchoClient client = - TestClientInitializer.createHttpJsonEchoClientOpentelemetry(tracingFactory)) { + EchoSettings httpJsonEchoSettings = + EchoSettings.newHttpJsonBuilder() + .setCredentialsProvider(NoCredentialsProvider.create()) + .setTransportChannelProvider( + EchoSettings.defaultHttpJsonTransportProviderBuilder() + .setHttpTransport( + new NetHttpTransport.Builder().doNotValidateCertificate().build()) + .setEndpoint("http://localhost:7469") + .build()) + .build(); + + EchoStubSettings.Builder stubSettingsBuilder = (EchoStubSettings.Builder) httpJsonEchoSettings.getStubSettings().toBuilder(); + stubSettingsBuilder.setTracerFactory(tracingFactory); + + EchoStubSettings stubSettings = + new EchoStubSettings(stubSettingsBuilder) { + @Override + public String getServiceName() { + return "showcase"; + } + }; + + try (EchoClient client = EchoClient.create(stubSettings.createStub())) { client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); @@ -206,6 +252,11 @@ void testTracing_successfulEcho_httpjson() throws Exception { .getAttributes() .get(AttributeKey.stringKey(ObservabilityAttributes.HTTP_URL_TEMPLATE_ATTRIBUTE))) .isEqualTo("v1beta1/echo:echo"); + assertThat( + attemptSpan + .getAttributes() + .get(AttributeKey.stringKey(ObservabilityAttributes.URL_DOMAIN_ATTRIBUTE))) + .isEqualTo("showcase.googleapis.com"); assertThat(attemptSpan.getInstrumentationScopeInfo().getName()).isEqualTo(SHOWCASE_ARTIFACT); } } From b8b18bd927a33e433474e76630ccc7a672103a1a Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 30 Mar 2026 17:14:45 -0400 Subject: [PATCH 2/5] fix: consider null values in ClientContext --- .../src/main/java/com/google/api/gax/rpc/ClientContext.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index 0ee74cfc422e..278843643841 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -272,9 +272,10 @@ public static ClientContext create(StubSettings settings) throws IOException { backgroundResources.add(watchdog); } String serviceName = endpointContext.serviceName(); + String universeDomain = endpointContext.resolvedUniverseDomain(); String urlDomain = null; - if (!Strings.isNullOrEmpty(serviceName)) { - urlDomain = serviceName + "." + endpointContext.resolvedUniverseDomain(); + if (!Strings.isNullOrEmpty(serviceName) && !Strings.isNullOrEmpty(universeDomain)) { + urlDomain = serviceName + "." + universeDomain; } ApiTracerContext apiTracerContext = From 71f2e7b97719c8cddde24b7fbe3f3217dfc8af10 Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 31 Mar 2026 16:25:17 -0400 Subject: [PATCH 3/5] fix: move url domain logic to EndpointContext --- .../com/google/api/gax/rpc/ClientContext.java | 7 +---- .../google/api/gax/rpc/EndpointContext.java | 8 ++++++ .../api/gax/rpc/EndpointContextTest.java | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index 278843643841..1a9cf60e9d6b 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -271,12 +271,7 @@ public static ClientContext create(StubSettings settings) throws IOException { if (watchdogProvider != null && watchdogProvider.shouldAutoClose()) { backgroundResources.add(watchdog); } - String serviceName = endpointContext.serviceName(); - String universeDomain = endpointContext.resolvedUniverseDomain(); - String urlDomain = null; - if (!Strings.isNullOrEmpty(serviceName) && !Strings.isNullOrEmpty(universeDomain)) { - urlDomain = serviceName + "." + universeDomain; - } + String urlDomain = endpointContext.getUrlDomain(); ApiTracerContext apiTracerContext = ApiTracerContext.newBuilder() diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java index 2a775ec4dd6c..1f408497dc7c 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -140,6 +140,14 @@ public static EndpointContext getDefaultInstance() { @Nullable public abstract Integer resolvedServerPort(); + @Nullable + String getUrlDomain() { + if (!Strings.isNullOrEmpty(serviceName()) && !Strings.isNullOrEmpty(resolvedUniverseDomain())) { + return serviceName() + "." + resolvedUniverseDomain(); + } + return null; + } + public abstract Builder toBuilder(); public static Builder newBuilder() { diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java index d517614ba4a0..32d4d6330563 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -698,4 +698,31 @@ void endpointContextBuild_resolvesInvalidEndpointAndPort() throws Exception { Truth.assertThat(endpointContext.resolvedServerAddress()).isNull(); Truth.assertThat(endpointContext.resolvedServerPort()).isNull(); } + + @Test + void getUrlDomain_success() throws IOException { + EndpointContext endpointContext = defaultEndpointContextBuilder.build(); + Truth.assertThat(endpointContext.getUrlDomain()).isEqualTo("test.googleapis.com"); + } + + @Test + void getUrlDomain_nullServiceName() throws IOException { + EndpointContext endpointContext = + defaultEndpointContextBuilder.setServiceName(null).build(); + Truth.assertThat(endpointContext.getUrlDomain()).isNull(); + } + + @Test + void getUrlDomain_emptyServiceName() throws IOException { + EndpointContext endpointContext = + defaultEndpointContextBuilder.setServiceName("").build(); + Truth.assertThat(endpointContext.getUrlDomain()).isNull(); + } + + @Test + void getUrlDomain_nonGDUUniverseDomain() throws IOException { + EndpointContext endpointContext = + defaultEndpointContextBuilder.setUniverseDomain("random.com").build(); + Truth.assertThat(endpointContext.getUrlDomain()).isEqualTo("test.random.com"); + } } From 74c66e14eeb1832d5d9d8bd595f375be07e8984c Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 31 Mar 2026 16:42:05 -0400 Subject: [PATCH 4/5] test: deduplication of IT code --- .../showcase/v1beta1/it/ITOtelTracing.java | 97 ++++++++++--------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java index f2c454bfb6cc..48de6009c76e 100644 --- a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java +++ b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java @@ -59,6 +59,7 @@ import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; import org.junit.jupiter.api.AfterEach; @@ -100,29 +101,9 @@ void tearDown() { void testTracing_successfulEcho_grpc() throws Exception { SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk); - EchoSettings grpcEchoSettings = - EchoSettings.newBuilder() - .setCredentialsProvider(NoCredentialsProvider.create()) - .setTransportChannelProvider( - EchoSettings.defaultGrpcTransportProviderBuilder() - .setChannelConfigurator(ManagedChannelBuilder::usePlaintext) - .build()) - .setEndpoint("localhost:7469") - .build(); - - EchoStubSettings.Builder stubSettingsBuilder = - (EchoStubSettings.Builder) grpcEchoSettings.getStubSettings().toBuilder(); - stubSettingsBuilder.setTracerFactory(tracingFactory); - - EchoStubSettings stubSettings = - new EchoStubSettings(stubSettingsBuilder) { - @Override - public String getServiceName() { - return "showcase"; - } - }; - - try (EchoClient client = EchoClient.create(stubSettings.createStub())) { + EchoSettings grpcEchoSettings = createEchoSettings(false); + EchoStub stub = createStubWithServiceName(grpcEchoSettings, tracingFactory); + try (EchoClient client = EchoClient.create(stub)) { client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); @@ -183,30 +164,9 @@ public String getServiceName() { void testTracing_successfulEcho_httpjson() throws Exception { SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk); - EchoSettings httpJsonEchoSettings = - EchoSettings.newHttpJsonBuilder() - .setCredentialsProvider(NoCredentialsProvider.create()) - .setTransportChannelProvider( - EchoSettings.defaultHttpJsonTransportProviderBuilder() - .setHttpTransport( - new NetHttpTransport.Builder().doNotValidateCertificate().build()) - .setEndpoint("http://localhost:7469") - .build()) - .build(); - - EchoStubSettings.Builder stubSettingsBuilder = - (EchoStubSettings.Builder) httpJsonEchoSettings.getStubSettings().toBuilder(); - stubSettingsBuilder.setTracerFactory(tracingFactory); - - EchoStubSettings stubSettings = - new EchoStubSettings(stubSettingsBuilder) { - @Override - public String getServiceName() { - return "showcase"; - } - }; - - try (EchoClient client = EchoClient.create(stubSettings.createStub())) { + EchoSettings httpJsonEchoSettings = createEchoSettings(true); + EchoStub stub = createStubWithServiceName(httpJsonEchoSettings, tracingFactory); + try (EchoClient client = EchoClient.create(stub)) { client.echo(EchoRequest.newBuilder().setContent("tracing-test").build()); @@ -432,4 +392,47 @@ void testTracing_retry_httpjson() throws Exception { .collect(java.util.stream.Collectors.toList()); assertThat(resendCounts).containsExactlyElementsIn(expectedCounts).inOrder(); } + + private EchoSettings createEchoSettings(boolean isHttpJson) throws Exception { + if (isHttpJson) { + return EchoSettings.newHttpJsonBuilder() + .setCredentialsProvider(NoCredentialsProvider.create()) + .setTransportChannelProvider( + EchoSettings.defaultHttpJsonTransportProviderBuilder() + .setHttpTransport( + new NetHttpTransport.Builder().doNotValidateCertificate().build()) + .setEndpoint("http://localhost:7469") + .build()) + .build(); + } else { + return EchoSettings.newBuilder() + .setCredentialsProvider(NoCredentialsProvider.create()) + .setTransportChannelProvider( + EchoSettings.defaultGrpcTransportProviderBuilder() + .setChannelConfigurator(ManagedChannelBuilder::usePlaintext) + .build()) + .setEndpoint("localhost:7469") + .build(); + } + } + + private EchoStub createStubWithServiceName( + EchoSettings settings, SpanTracerFactory tracingFactory) throws IOException { + EchoStubSettings.Builder builder = + (EchoStubSettings.Builder) settings.getStubSettings().toBuilder(); + builder.setTracerFactory(tracingFactory); + return new ExtendedEchoStubSettings(builder).createStub(); + } + + /** Custom wrapper to set a service name for showcase clients, which lack one by default. */ + private static class ExtendedEchoStubSettings extends EchoStubSettings { + protected ExtendedEchoStubSettings(EchoStubSettings.Builder builder) throws IOException { + super(builder); + } + + @Override + public String getServiceName() { + return "showcase"; + } + } } From bbddceaba917a26ea71daf7c0ca48a700301c6ce Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 1 Apr 2026 13:10:26 -0400 Subject: [PATCH 5/5] chore: format --- .../java/com/google/api/gax/rpc/EndpointContextTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java index 32d4d6330563..a86286f88066 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -707,15 +707,13 @@ void getUrlDomain_success() throws IOException { @Test void getUrlDomain_nullServiceName() throws IOException { - EndpointContext endpointContext = - defaultEndpointContextBuilder.setServiceName(null).build(); + EndpointContext endpointContext = defaultEndpointContextBuilder.setServiceName(null).build(); Truth.assertThat(endpointContext.getUrlDomain()).isNull(); } @Test void getUrlDomain_emptyServiceName() throws IOException { - EndpointContext endpointContext = - defaultEndpointContextBuilder.setServiceName("").build(); + EndpointContext endpointContext = defaultEndpointContextBuilder.setServiceName("").build(); Truth.assertThat(endpointContext.getUrlDomain()).isNull(); }