diff --git a/sdk-platform-java/gax-java/gax-grpc/pom.xml b/sdk-platform-java/gax-java/gax-grpc/pom.xml
index c669281e31f5..c41d684b7d93 100644
--- a/sdk-platform-java/gax-java/gax-grpc/pom.xml
+++ b/sdk-platform-java/gax-java/gax-grpc/pom.xml
@@ -100,6 +100,16 @@
+
+ io.opentelemetry
+ opentelemetry-sdk
+ test
+
+
+ io.opentelemetry
+ opentelemetry-sdk-testing
+ test
+
io.grpc
grpc-s2a
diff --git a/sdk-platform-java/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcClientCalls.java b/sdk-platform-java/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcClientCalls.java
index e797842c9eb8..06e2b9ea06be 100644
--- a/sdk-platform-java/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcClientCalls.java
+++ b/sdk-platform-java/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcClientCalls.java
@@ -90,9 +90,16 @@ public static ClientCall newCall(
channel = ((ChannelPool) channel).getChannel(grpcContext.getChannelAffinity());
}
- if (!grpcContext.getExtraHeaders().isEmpty()) {
- ClientInterceptor interceptor =
- MetadataUtils.newAttachHeadersInterceptor(grpcContext.getMetadata());
+ java.util.Map traceContext = new java.util.HashMap<>();
+ grpcContext.getTracer().injectTraceContext(traceContext);
+
+ if (!grpcContext.getExtraHeaders().isEmpty() || !traceContext.isEmpty()) {
+ Metadata metadata = grpcContext.getMetadata();
+ for (java.util.Map.Entry entry : traceContext.entrySet()) {
+ metadata.put(
+ Metadata.Key.of(entry.getKey(), Metadata.ASCII_STRING_MARSHALLER), entry.getValue());
+ }
+ ClientInterceptor interceptor = MetadataUtils.newAttachHeadersInterceptor(metadata);
channel = ClientInterceptors.intercept(channel, interceptor);
}
diff --git a/sdk-platform-java/gax-java/gax-httpjson/pom.xml b/sdk-platform-java/gax-java/gax-httpjson/pom.xml
index 259b360ba4ee..5b94d4a1f4d5 100644
--- a/sdk-platform-java/gax-java/gax-httpjson/pom.xml
+++ b/sdk-platform-java/gax-java/gax-httpjson/pom.xml
@@ -86,6 +86,16 @@
+
+ io.opentelemetry
+ opentelemetry-sdk
+ test
+
+
+ io.opentelemetry
+ opentelemetry-sdk-testing
+ test
+
com.google.api
gax
diff --git a/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonClientCalls.java b/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonClientCalls.java
index c98d00afb961..6a2f97401101 100644
--- a/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonClientCalls.java
+++ b/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonClientCalls.java
@@ -80,15 +80,25 @@ public static HttpJsonClientCall newC
return httpJsonContext.getChannel().newCall(methodDescriptor, httpJsonContext.getCallOptions());
}
+ static HttpJsonMetadata getMetadataWithTraceContext(HttpJsonCallContext context) {
+ java.util.Map traceHeaders = new java.util.HashMap<>();
+ context.getTracer().injectTraceContext(traceHeaders);
+
+ java.util.Map> finalHeaders =
+ new java.util.HashMap<>(context.getExtraHeaders());
+ for (java.util.Map.Entry entry : traceHeaders.entrySet()) {
+ finalHeaders.put(entry.getKey(), java.util.Collections.singletonList(entry.getValue()));
+ }
+ return HttpJsonMetadata.newBuilder().build().withHeaders(finalHeaders);
+ }
+
static ApiFuture futureUnaryCall(
HttpJsonClientCall clientCall,
RequestT request,
HttpJsonCallContext context) {
// Start the call
HttpJsonFuture future = new HttpJsonFuture<>(clientCall);
- clientCall.start(
- new FutureListener<>(future),
- HttpJsonMetadata.newBuilder().build().withHeaders(context.getExtraHeaders()));
+ clientCall.start(new FutureListener<>(future), getMetadataWithTraceContext(context));
// Send the request
try {
diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java
index 249cec843ad3..a67cdc855506 100644
--- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java
+++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java
@@ -196,6 +196,9 @@ default void requestSent() {}
default void batchRequestSent(long elementCount, long requestSize) {}
;
+ /** Extract the trace context from the tracer and add it to the given headers map. */
+ default void injectTraceContext(java.util.Map carrier) {}
+
/**
* Annotates the attempt with the full resolved HTTP URL. Only relevant for HTTP transport.
*
diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java
index eae14b1d9dd1..eac96d1d7529 100644
--- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java
+++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java
@@ -55,6 +55,27 @@ public class SpanTracer implements ApiTracer {
private final ApiTracerContext apiTracerContext;
private Span attemptSpan;
+ @Override
+ public void injectTraceContext(java.util.Map carrier) {
+ if (attemptSpan != null) {
+ try {
+ io.opentelemetry.context.Context context =
+ io.opentelemetry.context.Context.current().with(attemptSpan);
+ io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator.getInstance()
+ .inject(
+ context,
+ carrier,
+ (c, k, v) -> {
+ if (c != null) {
+ c.put(k, v);
+ }
+ });
+ } catch (NoSuchMethodError e) {
+ // Silently ignore if incompatible OpenTelemetry version
+ }
+ }
+ }
+
/**
* Creates a new instance of {@code SpanTracer}.
*