Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7082247
feat: introduce minimal implementation of OTel tracing
diegomarquezp Feb 4, 2026
bfe6a6d
chore: avoid new public methods
diegomarquezp Feb 5, 2026
ea6cb18
feat: add api tracer context
diegomarquezp Feb 5, 2026
97bec08
Merge remote-tracking branch 'origin/main' into observability/initial…
diegomarquezp Feb 5, 2026
097f701
chore: concise comment
diegomarquezp Feb 5, 2026
f547e5a
fix: use internal span kind for operations
diegomarquezp Feb 5, 2026
b7b9e31
chore: remove unnecessary inScope implementation
diegomarquezp Feb 5, 2026
556c84c
Revert "chore: remove unnecessary inScope implementation"
diegomarquezp Feb 5, 2026
0359b7d
test: add test for inScope()
diegomarquezp Feb 5, 2026
402cb89
Merge remote-tracking branch 'origin/main' into observability/initial…
diegomarquezp Feb 5, 2026
420278b
chore: use suggested server address resolution impl
diegomarquezp Feb 5, 2026
6de9fb6
fix: use concurrent hash map
diegomarquezp Feb 5, 2026
a61dacf
chore: remove default impl for startSpan with parent
diegomarquezp Feb 5, 2026
b000d52
chore: add opentelemery-context to tests
diegomarquezp Feb 5, 2026
9ce0c34
test: increase coverage for TracingTracerTest
diegomarquezp Feb 5, 2026
9c6c737
deps: include opentelemetry context in gax
diegomarquezp Feb 5, 2026
e77de58
chore: simplify and remove error handling
diegomarquezp Feb 9, 2026
8f07f61
chore: review refactor
diegomarquezp Feb 10, 2026
2cfcd68
chore: make TracingTracerFactory(recorder, opAtts, atAtts) package pr…
diegomarquezp Feb 10, 2026
2415c6b
chore: remove unnecessary inScope
diegomarquezp Feb 10, 2026
0f5e9b5
chore: rename startSpan to createSpan
diegomarquezp Feb 10, 2026
0c3bd22
chore: use server address instead of endpoint context
diegomarquezp Feb 10, 2026
bdeb291
chore: rename classes
diegomarquezp Feb 10, 2026
5c07a3c
chore: add javadoc for tracer
diegomarquezp Feb 10, 2026
ff1d45d
chore: rename to TraceSpan, improve javadocs
diegomarquezp Feb 10, 2026
043ce62
chore: format
diegomarquezp Feb 10, 2026
dbc5f41
Update gax-java/gax/src/main/java/com/google/api/gax/tracing/AppCentr…
diegomarquezp Feb 10, 2026
b4bce0d
chore: handle ipv6 in endpoint context
diegomarquezp Feb 10, 2026
6e95cf4
Merge branch 'observability/initial-tracing-impl' of https://github.c…
diegomarquezp Feb 10, 2026
e873904
chore: add language tests
diegomarquezp Feb 10, 2026
68943b0
chore: AppCentricTracer to implement interface
diegomarquezp Feb 11, 2026
cd4d4d0
chore: revert operation implementation
diegomarquezp Feb 12, 2026
5b76e40
chore: extract common span attributes to separate class
diegomarquezp Feb 12, 2026
c783d68
chore: remove unused var
diegomarquezp Feb 12, 2026
3e06b39
chore: restore context dep
diegomarquezp Feb 12, 2026
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
1 change: 1 addition & 0 deletions gax-java/dependencies.properties
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ maven.com_google_api_grpc_grpc_google_common_protos=com.google.api.grpc:grpc-goo
maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.42.1
maven.com_google_auth_google_auth_library_credentials=com.google.auth:google-auth-library-credentials:1.42.1
maven.io_opentelemetry_opentelemetry_api=io.opentelemetry:opentelemetry-api:1.47.0
maven.io_opentelemetry_opentelemetry_context=io.opentelemetry:opentelemetry-context:1.47.0
maven.io_opencensus_opencensus_api=io.opencensus:opencensus-api:0.31.1
maven.io_opencensus_opencensus_contrib_grpc_metrics=io.opencensus:opencensus-contrib-grpc-metrics:0.31.1
maven.io_opencensus_opencensus_contrib_http_util=io.opencensus:opencensus-contrib-http-util:0.31.1
Expand Down
1 change: 1 addition & 0 deletions gax-java/gax/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ _COMPILE_DEPS = [
"@com_google_errorprone_error_prone_annotations//jar",
"@com_google_guava_guava//jar",
"@io_opentelemetry_opentelemetry_api//jar",
"@io_opentelemetry_opentelemetry_context//jar",
"@io_opencensus_opencensus_api//jar",
"@io_opencensus_opencensus_contrib_http_util//jar",
"@io_grpc_grpc_java//context:context",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.google.api.gax.core.ExecutorAsBackgroundResource;
import com.google.api.gax.core.ExecutorProvider;
import com.google.api.gax.rpc.internal.QuotaProjectIdHidingCredentials;
import com.google.api.gax.tracing.ApiTracerContext;
import com.google.api.gax.tracing.ApiTracerFactory;
import com.google.api.gax.tracing.BaseApiTracerFactory;
import com.google.auth.ApiKeyCredentials;
Expand Down Expand Up @@ -269,6 +270,11 @@ public static ClientContext create(StubSettings settings) throws IOException {
if (watchdogProvider != null && watchdogProvider.shouldAutoClose()) {
backgroundResources.add(watchdog);
}
ApiTracerContext apiTracerContext =
ApiTracerContext.newBuilder()
.setServerAddress(endpointContext.resolvedServerAddress())
.build();
ApiTracerFactory apiTracerFactory = settings.getTracerFactory().withContext(apiTracerContext);

return newBuilder()
.setBackgroundResources(backgroundResources.build())
Expand All @@ -284,7 +290,7 @@ public static ClientContext create(StubSettings settings) throws IOException {
.setQuotaProjectId(settings.getQuotaProjectId())
.setStreamWatchdog(watchdog)
.setStreamWatchdogCheckIntervalDuration(settings.getStreamWatchdogCheckIntervalDuration())
.setTracerFactory(settings.getTracerFactory())
.setTracerFactory(apiTracerFactory)
.setEndpointContext(endpointContext)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.net.HostAndPort;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -133,6 +134,8 @@ public static EndpointContext getDefaultInstance() {

public abstract String resolvedEndpoint();

public abstract String resolvedServerAddress();

public abstract Builder toBuilder();

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

public abstract Builder setResolvedEndpoint(String resolvedEndpoint);

public abstract Builder setResolvedServerAddress(String serverAddress);

public abstract Builder setResolvedUniverseDomain(String resolvedUniverseDomain);

abstract Builder setUseS2A(boolean useS2A);
Expand Down Expand Up @@ -382,6 +387,23 @@ boolean shouldUseS2A() {
return mtlsEndpoint().contains(Credentials.GOOGLE_DEFAULT_UNIVERSE);
}

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;
}
}

// Default to port 443 for HTTPS. Using HTTP requires explicitly setting the endpoint
private String buildEndpointTemplate(String serviceName, String resolvedUniverseDomain) {
return serviceName + "." + resolvedUniverseDomain + ":443";
Expand Down Expand Up @@ -416,7 +438,9 @@ String mtlsEndpointResolver(
public EndpointContext build() throws IOException {
// The Universe Domain is used to resolve the Endpoint. It should be resolved first
setResolvedUniverseDomain(determineUniverseDomain());
setResolvedEndpoint(determineEndpoint());
String endpoint = determineEndpoint();
setResolvedEndpoint(endpoint);
setResolvedServerAddress(parseServerAddress(endpoint));
setUseS2A(shouldUseS2A());
return autoBuild();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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.InternalApi;
import com.google.auto.value.AutoValue;
import javax.annotation.Nullable;

/**
* A context object that contains information used to infer attributes that are common for all
* {@link ApiTracer}s.
*
* <p>For internal use only.
*/
@InternalApi
@AutoValue
public abstract class ApiTracerContext {

@Nullable
public abstract String getServerAddress();

public static Builder newBuilder() {
return new AutoValue_ApiTracerContext.Builder();
}

@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setServerAddress(String serverAddress);

public abstract ApiTracerContext build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,15 @@ enum OperationType {
* @param operationType the type of operation that the tracer will trace
*/
ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType);

/**
* Returns a new {@link ApiTracerFactory} that will use the provided context to infer attributes
* for all tracers created by the factory.
*
* @param context an {@link ApiTracerContext} object containing information to construct
* attributes
*/
default ApiTracerFactory withContext(ApiTracerContext context) {
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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.InternalApi;
import java.util.HashMap;
import java.util.Map;

/**
* Utility class for providing common attributes used in app-centric observability.
*
* <p>This class extracts information from {@link ApiTracerContext} and maps it to standardized
* attribute keys that are expected by {@link ApiTracerFactory} implementations that conform to
* app-centric observability
*
* <p>For internal use only.
*/
@InternalApi
public class AppCentricAttributes {
/** The address of the server being called (e.g., "pubsub.googleapis.com"). */
public static final String SERVER_ADDRESS_ATTRIBUTE = "server.address";

/**
* Extracts attempt-level attributes from the provided {@link ApiTracerContext}.
*
* @param context the context containing information about the current API call
* @return a map of attributes to be included in attempt-level spans
*/
public static Map<String, String> getAttemptAttributes(ApiTracerContext context) {
Map<String, String> attributes = new HashMap<>();
if (context.getServerAddress() != null) {
attributes.put(SERVER_ADDRESS_ATTRIBUTE, context.getServerAddress());
}
return attributes;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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.HashMap;
import java.util.Map;

/**
* An implementation of {@link ApiTracer} that uses a {@link TraceRecorder} to record traces. This
* implementation is agnostic to the specific {@link TraceRecorder} in order to allow extensions
* that interact with other backends.
*/
@BetaApi
@InternalApi
public class AppCentricTracer implements ApiTracer {
public static final String LANGUAGE_ATTRIBUTE = "gcp.client.language";

public static final String DEFAULT_LANGUAGE = "Java";

private final TraceRecorder recorder;
private final Map<String, String> attemptAttributes;
private final String attemptSpanName;
private TraceRecorder.TraceSpan attemptHandle;

/**
* Creates a new instance of {@code AppCentricTracer}.
*
* @param recorder the {@link TraceRecorder} to use for recording spans
* @param attemptSpanName the name of the individual attempt spans
* @param attemptAttributes attributes to be added to each attempt span
*/
public AppCentricTracer(
TraceRecorder recorder, String attemptSpanName, Map<String, String> attemptAttributes) {
this.recorder = recorder;
this.attemptSpanName = attemptSpanName;
this.attemptAttributes = new HashMap<>(attemptAttributes);
this.attemptAttributes.put(LANGUAGE_ATTRIBUTE, DEFAULT_LANGUAGE);

// Start the long-lived operation span.
}

@Override
public void attemptStarted(Object request, int attemptNumber) {
Map<String, String> attemptAttributes = new HashMap<>(this.attemptAttributes);
// Start the specific attempt span with the operation span as parent
this.attemptHandle = recorder.createSpan(attemptSpanName, attemptAttributes);
}

@Override
public void attemptSucceeded() {
Copy link
Contributor

Choose a reason for hiding this comment

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

There are other scenarios we want to end span as well. For example, attemptFailed and attemptCancelled.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed these in e77de58. I'm planning to introduce them as we implement error codes (I think attempt cancelled is a cancelled status code).

endAttempt();
}

private void endAttempt() {
if (attemptHandle != null) {
attemptHandle.end();
attemptHandle = null;
}
}
}
Loading
Loading