Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
42 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
40a97a5
chore: process attributes in context and tracer classes
diegomarquezp Feb 13, 2026
5b43fa5
chore: rename to GaxSpan
diegomarquezp Feb 13, 2026
ed4af8e
chore: use fq name for otel tracer
diegomarquezp Feb 13, 2026
3bfd1ce
chore: add otel context to maven deps
diegomarquezp Feb 13, 2026
5382d78
chore: rename TraceRecorder to TraceManager and GaxSpan to Span
diegomarquezp Feb 13, 2026
4ece157
chore: remove attrs from factory
diegomarquezp Feb 13, 2026
fb57471
chore: adjust javadoc in TraceManager
diegomarquezp Feb 13, 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,9 @@ public static ClientContext create(StubSettings settings) throws IOException {
if (watchdogProvider != null && watchdogProvider.shouldAutoClose()) {
backgroundResources.add(watchdog);
}
ApiTracerContext apiTracerContext =
ApiTracerContext.newBuilder().setEndpointContext(endpointContext).build();
ApiTracerFactory apiTracerFactory = settings.getTracerFactory().withContext(apiTracerContext);

return newBuilder()
.setBackgroundResources(backgroundResources.build())
Expand All @@ -284,7 +288,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 @@ -41,6 +41,8 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -133,6 +135,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 +232,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 +388,27 @@ boolean shouldUseS2A() {
return mtlsEndpoint().contains(Credentials.GOOGLE_DEFAULT_UNIVERSE);
}

private String parseServerAddress(String endpoint) {
try {
String urlString = endpoint;
if (!urlString.contains("://")) {
urlString = "http://" + urlString;
}
return new URL(urlString).getHost();
} catch (MalformedURLException e) {
// Fallback for cases URL can't handle.
int colonPortIndex = endpoint.lastIndexOf(':');
int doubleSlashIndex = endpoint.lastIndexOf("//");
if (colonPortIndex == -1) {
return endpoint;
}
if (doubleSlashIndex != -1 && doubleSlashIndex < colonPortIndex) {
return endpoint.substring(doubleSlashIndex + 2, colonPortIndex);
}
return endpoint.substring(0, colonPortIndex);
}
}

// 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 +443,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,61 @@
/*
* 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.api.gax.rpc.EndpointContext;
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 EndpointContext getEndpointContext();

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

@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setEndpointContext(EndpointContext endpointContext);

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,120 @@
/*
* 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 io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import java.util.Map;

/**
* OpenTelemetry implementation of recording traces. This implementation collects the measurements
* related to the lifecyle of an RPC.
*/
@BetaApi
@InternalApi
public class OpenTelemetryTracingRecorder implements TracingRecorder {
private final Tracer tracer;

public OpenTelemetryTracingRecorder(OpenTelemetry openTelemetry) {
this.tracer = openTelemetry.getTracer("gax-java");
}

@Override
public SpanHandle startSpan(String name, Map<String, String> attributes) {
return startSpan(name, attributes, null);
}

@Override
public SpanHandle startSpan(String name, Map<String, String> attributes, SpanHandle parent) {
SpanBuilder spanBuilder = tracer.spanBuilder(name);

// Operation and Attempt spans are INTERNAL and CLIENT respectively.
if (parent == null) {
spanBuilder.setSpanKind(SpanKind.INTERNAL);
} else {
spanBuilder.setSpanKind(SpanKind.CLIENT);
}

if (attributes != null) {
attributes.forEach((k, v) -> spanBuilder.setAttribute(k, v));
}

if (parent instanceof OtelSpanHandle) {
spanBuilder.setParent(Context.current().with(((OtelSpanHandle) parent).span));
}

Span span = spanBuilder.startSpan();

return new OtelSpanHandle(span);
}

@Override
@SuppressWarnings("MustBeClosedChecker")
public ApiTracer.Scope inScope(SpanHandle handle) {
if (handle instanceof OtelSpanHandle) {
Scope scope = ((OtelSpanHandle) handle).span.makeCurrent();
return scope::close;
}
return () -> {};
}

private static class OtelSpanHandle implements SpanHandle {
private final Span span;

private OtelSpanHandle(Span span) {
this.span = span;
}

@Override
public void end() {
span.end();
}

@Override
public void recordError(Throwable error) {
span.recordException(error);
span.setStatus(StatusCode.ERROR);
}

@Override
public void setAttribute(String key, String value) {
span.setAttribute(key, value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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.Map;

/**
* Provides an interface for tracing recording. The implementer is expected to use an observability
* framework, e.g. OpenTelemetry. There should be only one instance of TracingRecorder per client,
* all the methods in this class are expected to be called from multiple threads, hence the
* implementation must be thread safe.
*/
@BetaApi
@InternalApi
public interface TracingRecorder {
/** Starts a span and returns a handle to manage its lifecycle. */
SpanHandle startSpan(String name, Map<String, String> attributes);

/** Starts a span with a parent and returns a handle to manage its lifecycle. */
SpanHandle startSpan(String name, Map<String, String> attributes, SpanHandle parent);

/**
* Installs the span into the current thread-local context.
*
* @return a scope that must be closed to remove the span from the context.
*/
default ApiTracer.Scope inScope(SpanHandle handle) {
return () -> {};
}

interface SpanHandle {
void end();

void recordError(Throwable error);

void setAttribute(String key, String value);
}
}
Loading
Loading