diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml
index db1526835e2..8e7a392302a 100644
--- a/google-cloud-spanner/clirr-ignored-differences.xml
+++ b/google-cloud-spanner/clirr-ignored-differences.xml
@@ -695,6 +695,13 @@
boolean isEnableApiTracing()
+
+
+ 7012
+ com/google/cloud/spanner/SpannerOptions$SpannerEnvironment
+ boolean isEnableEndToEndTracing()
+
+
7012
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java
index 3bfa3ee4069..5756ff64b89 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java
@@ -163,6 +163,7 @@ public class SpannerOptions extends ServiceOptions {
private final boolean enableApiTracing;
private final boolean enableBuiltInMetrics;
private final boolean enableExtendedTracing;
+ private final boolean enableEndToEndTracing;
enum TracingFramework {
OPEN_CENSUS,
@@ -670,6 +671,7 @@ protected SpannerOptions(Builder builder) {
enableApiTracing = builder.enableApiTracing;
enableExtendedTracing = builder.enableExtendedTracing;
enableBuiltInMetrics = builder.enableBuiltInMetrics;
+ enableEndToEndTracing = builder.enableEndToEndTracing;
}
/**
@@ -706,6 +708,10 @@ default boolean isEnableApiTracing() {
default boolean isEnableBuiltInMetrics() {
return false;
}
+
+ default boolean isEnableEndToEndTracing() {
+ return false;
+ }
}
/**
@@ -720,6 +726,8 @@ private static class SpannerEnvironmentImpl implements SpannerEnvironment {
private static final String SPANNER_ENABLE_EXTENDED_TRACING = "SPANNER_ENABLE_EXTENDED_TRACING";
private static final String SPANNER_ENABLE_API_TRACING = "SPANNER_ENABLE_API_TRACING";
private static final String SPANNER_ENABLE_BUILTIN_METRICS = "SPANNER_ENABLE_BUILTIN_METRICS";
+ private static final String SPANNER_ENABLE_END_TO_END_TRACING =
+ "SPANNER_ENABLE_END_TO_END_TRACING";
private SpannerEnvironmentImpl() {}
@@ -752,6 +760,11 @@ public boolean isEnableBuiltInMetrics() {
// removed in the future.
return Boolean.parseBoolean(System.getenv(SPANNER_ENABLE_BUILTIN_METRICS));
}
+
+ @Override
+ public boolean isEnableEndToEndTracing() {
+ return Boolean.parseBoolean(System.getenv(SPANNER_ENABLE_END_TO_END_TRACING));
+ }
}
/** Builder for {@link SpannerOptions} instances. */
@@ -816,6 +829,7 @@ public static class Builder
private boolean enableApiTracing = SpannerOptions.environment.isEnableApiTracing();
private boolean enableExtendedTracing = SpannerOptions.environment.isEnableExtendedTracing();
private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics();
+ private boolean enableEndToEndTracing = SpannerOptions.environment.isEnableEndToEndTracing();
private static String createCustomClientLibToken(String token) {
return token + " " + ServiceOptions.getGoogApiClientLibName();
@@ -882,6 +896,7 @@ protected Builder() {
this.enableApiTracing = options.enableApiTracing;
this.enableExtendedTracing = options.enableExtendedTracing;
this.enableBuiltInMetrics = options.enableBuiltInMetrics;
+ this.enableEndToEndTracing = options.enableEndToEndTracing;
}
@Override
@@ -1415,6 +1430,17 @@ public Builder setEnableExtendedTracing(boolean enableExtendedTracing) {
return this;
}
+ /**
+ * Sets whether to enable end to end tracing. Enabling this option will create the trace spans
+ * at the Spanner layer. By default, end to end tracing is disabled. Enabling end to end tracing
+ * requires OpenTelemetry to be set up. Simply enabling this option won't generate traces at
+ * Spanner layer.
+ */
+ public Builder setEnableEndToEndTracing(boolean enableEndToEndTracing) {
+ this.enableEndToEndTracing = enableEndToEndTracing;
+ return this;
+ }
+
@SuppressWarnings("rawtypes")
@Override
public SpannerOptions build() {
@@ -1504,6 +1530,7 @@ public static void enableOpenCensusTraces() {
*/
@ObsoleteApi(
"The OpenCensus project is deprecated. Use enableOpenTelemetryTraces to switch to OpenTelemetry traces")
+ @VisibleForTesting
static void resetActiveTracingFramework() {
activeTracingFramework = null;
}
@@ -1745,6 +1772,14 @@ public boolean isEnableExtendedTracing() {
return enableExtendedTracing;
}
+ /**
+ * Returns whether end to end tracing is enabled. If this option is enabled then trace spans will
+ * be created at the Spanner layer.
+ */
+ public boolean isEndToEndTracingEnabled() {
+ return enableEndToEndTracing;
+ }
+
/** Returns the default query options to use for the specific database. */
public QueryOptions getDefaultQueryOptions(DatabaseId databaseId) {
// Use the specific query options for the database if any have been specified. These have
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java
index b389ea6a31a..2360b5d5173 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java
@@ -272,6 +272,7 @@ public class GapicSpannerRpc implements SpannerRpc {
private static final ConcurrentMap ADMINISTRATIVE_REQUESTS_RATE_LIMITERS =
new ConcurrentHashMap<>();
private final boolean leaderAwareRoutingEnabled;
+ private final boolean endToEndTracingEnabled;
private final int numChannels;
private final boolean isGrpcGcpExtensionEnabled;
@@ -325,6 +326,7 @@ public GapicSpannerRpc(final SpannerOptions options) {
this.callCredentialsProvider = options.getCallCredentialsProvider();
this.compressorName = options.getCompressorName();
this.leaderAwareRoutingEnabled = options.isLeaderAwareRoutingEnabled();
+ this.endToEndTracingEnabled = options.isEndToEndTracingEnabled();
this.numChannels = options.getNumChannels();
this.isGrpcGcpExtensionEnabled = options.isGrpcGcpExtensionEnabled();
@@ -350,6 +352,8 @@ public GapicSpannerRpc(final SpannerOptions options) {
MoreObjects.firstNonNull(
options.getInterceptorProvider(),
SpannerInterceptorProvider.createDefault(options.getOpenTelemetry())))
+ // This sets the trace context headers.
+ .withTraceContext(endToEndTracingEnabled, options.getOpenTelemetry())
// This sets the response compressor (Server -> Client).
.withEncoding(compressorName))
.setHeaderProvider(headerProviderWithUserAgent)
@@ -2007,6 +2011,9 @@ GrpcCallContext newCallContext(
if (routeToLeader && leaderAwareRoutingEnabled) {
context = context.withExtraHeaders(metadataProvider.newRouteToLeaderHeader());
}
+ if (endToEndTracingEnabled) {
+ context = context.withExtraHeaders(metadataProvider.newEndToEndTracingHeader());
+ }
if (callCredentialsProvider != null) {
CallCredentials callCredentials = callCredentialsProvider.getCallCredentials();
if (callCredentials != null) {
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java
index 9b1a2fd3c1f..b4d28ef0789 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java
@@ -74,6 +74,14 @@ SpannerInterceptorProvider withEncoding(String encoding) {
return this;
}
+ SpannerInterceptorProvider withTraceContext(
+ boolean endToEndTracingEnabled, OpenTelemetry openTelemetry) {
+ if (endToEndTracingEnabled) {
+ return with(new TraceContextInterceptor(openTelemetry));
+ }
+ return this;
+ }
+
@Override
public List getInterceptors() {
return clientInterceptors;
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java
index 0b8d76d52df..2ebc4925788 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java
@@ -37,6 +37,7 @@ class SpannerMetadataProvider {
private final Map, String> headers;
private final String resourceHeaderKey;
private static final String ROUTE_TO_LEADER_HEADER_KEY = "x-goog-spanner-route-to-leader";
+ private static final String END_TO_END_TRACING_HEADER_KEY = "x-goog-spanner-end-to-end-tracing";
private static final Pattern[] RESOURCE_TOKEN_PATTERNS = {
Pattern.compile("^(?projects/[^/]*/instances/[^/]*/databases/[^/]*)(.*)?"),
Pattern.compile("^(?projects/[^/]*/instances/[^/]*)(.*)?")
@@ -44,6 +45,8 @@ class SpannerMetadataProvider {
private static final Map> ROUTE_TO_LEADER_HEADER_MAP =
ImmutableMap.of(ROUTE_TO_LEADER_HEADER_KEY, Collections.singletonList("true"));
+ private static final Map> END_TO_END_TRACING_HEADER_MAP =
+ ImmutableMap.of(END_TO_END_TRACING_HEADER_KEY, Collections.singletonList("true"));
private SpannerMetadataProvider(Map headers, String resourceHeaderKey) {
this.resourceHeaderKey = resourceHeaderKey;
@@ -89,6 +92,10 @@ Map> newRouteToLeaderHeader() {
return ROUTE_TO_LEADER_HEADER_MAP;
}
+ Map> newEndToEndTracingHeader() {
+ return END_TO_END_TRACING_HEADER_MAP;
+ }
+
private Map, String> constructHeadersAsMetadata(
Map headers) {
ImmutableMap.Builder, String> headersAsMetadataBuilder =
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/TraceContextInterceptor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/TraceContextInterceptor.java
new file mode 100644
index 00000000000..3b46ba4f880
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/TraceContextInterceptor.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.cloud.spanner.spi.v1;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.propagation.TextMapPropagator;
+import io.opentelemetry.context.propagation.TextMapSetter;
+
+/**
+ * Intercepts all gRPC calls and injects trace context related headers to propagate trace context to
+ * Spanner. This class takes reference from OpenTelemetry's JAVA instrumentation library for gRPC.
+ * https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/9ecf7965aa455d41ea8cc0761b6c6b6eeb106324/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/TracingClientInterceptor.java#L27
+ */
+class TraceContextInterceptor implements ClientInterceptor {
+
+ private final TextMapPropagator textMapPropagator;
+
+ TraceContextInterceptor(OpenTelemetry openTelemetry) {
+ this.textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator();
+ }
+
+ enum MetadataSetter implements TextMapSetter {
+ INSTANCE;
+
+ @SuppressWarnings("null")
+ @Override
+ public void set(Metadata carrier, String key, String value) {
+ carrier.put(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value);
+ }
+ }
+
+ private static final class NoopSimpleForwardingClientCallListener
+ extends SimpleForwardingClientCallListener {
+ public NoopSimpleForwardingClientCallListener(ClientCall.Listener responseListener) {
+ super(responseListener);
+ }
+ }
+
+ @Override
+ public ClientCall interceptCall(
+ MethodDescriptor method, CallOptions callOptions, Channel next) {
+ return new SimpleForwardingClientCall(next.newCall(method, callOptions)) {
+ @Override
+ public void start(Listener responseListener, Metadata headers) {
+ Context parentContext = Context.current();
+ textMapPropagator.inject(parentContext, headers, MetadataSetter.INSTANCE);
+ super.start(new NoopSimpleForwardingClientCallListener(responseListener), headers);
+ }
+ };
+ }
+}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsHelper.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsHelper.java
new file mode 100644
index 00000000000..db02c625099
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsHelper.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.spanner;
+
+/** Helper to configure SpannerOptions for tests. */
+public class SpannerOptionsHelper {
+
+ /**
+ * Resets the activeTracingFramework. This variable is used for internal testing, and is not a
+ * valid production scenario.
+ */
+ public static void resetActiveTracingFramework() {
+ SpannerOptions.resetActiveTracingFramework();
+ }
+}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java
index 73455f06688..e8421cd235c 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java
@@ -736,6 +736,24 @@ public void testLeaderAwareRoutingEnablement() {
.isLeaderAwareRoutingEnabled());
}
+ @Test
+ public void testEndToEndTracingEnablement() {
+ // Test that end to end tracing is disabled by default.
+ assertFalse(SpannerOptions.newBuilder().setProjectId("p").build().isEndToEndTracingEnabled());
+ assertTrue(
+ SpannerOptions.newBuilder()
+ .setProjectId("p")
+ .setEnableEndToEndTracing(true)
+ .build()
+ .isEndToEndTracingEnabled());
+ assertFalse(
+ SpannerOptions.newBuilder()
+ .setProjectId("p")
+ .setEnableEndToEndTracing(false)
+ .build()
+ .isEndToEndTracingEnabled());
+ }
+
@Test
public void testSetDirectedReadOptions() {
final DirectedReadOptions directedReadOptions =
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java
index 42a07ed9ea6..b3ff3b8f1c2 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java
@@ -47,6 +47,7 @@
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.SpannerOptions.CallContextConfigurator;
+import com.google.cloud.spanner.SpannerOptionsHelper;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.TransactionRunner;
import com.google.cloud.spanner.spi.v1.GapicSpannerRpc.AdminRequestsLimitExceededRetryAlgorithm;
@@ -76,6 +77,12 @@
import io.grpc.auth.MoreCallCredentials;
import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
import io.grpc.protobuf.lite.ProtoLiteUtils;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
+import io.opentelemetry.context.propagation.ContextPropagators;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.samplers.Sampler;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
@@ -148,6 +155,8 @@ public class GapicSpannerRpcTest {
private static String defaultUserAgent;
private static Spanner spanner;
private static boolean isRouteToLeader;
+ private static boolean isEndToEndTracing;
+ private static boolean isTraceContextPresent;
@Parameter public Dialect dialect;
@@ -158,6 +167,10 @@ public static Object[] data() {
@Before
public void startServer() throws IOException {
+ // Enable OpenTelemetry tracing.
+ SpannerOptionsHelper.resetActiveTracingFramework();
+ SpannerOptions.enableOpenTelemetryTraces();
+
assumeTrue(
"Skip tests when emulator is enabled as this test interferes with the check whether the emulator is running",
System.getenv("SPANNER_EMULATOR_HOST") == null);
@@ -194,13 +207,23 @@ public ServerCall.Listener interceptCall(
if (call.getMethodDescriptor()
.equals(SpannerGrpc.getExecuteStreamingSqlMethod())
|| call.getMethodDescriptor().equals(SpannerGrpc.getExecuteSqlMethod())) {
+ String traceParentHeader =
+ headers.get(Key.of("traceparent", Metadata.ASCII_STRING_MARSHALLER));
+ isTraceContextPresent = (traceParentHeader != null);
String routeToLeaderHeader =
headers.get(
Key.of(
"x-goog-spanner-route-to-leader",
Metadata.ASCII_STRING_MARSHALLER));
+ String endToEndTracingHeader =
+ headers.get(
+ Key.of(
+ "x-goog-spanner-end-to-end-tracing",
+ Metadata.ASCII_STRING_MARSHALLER));
isRouteToLeader =
(routeToLeaderHeader != null && routeToLeaderHeader.equals("true"));
+ isEndToEndTracing =
+ (endToEndTracingHeader != null && endToEndTracingHeader.equals("true"));
}
return Contexts.interceptCall(Context.current(), call, headers, next);
}
@@ -224,6 +247,8 @@ public void reset() throws InterruptedException {
server.awaitTermination();
}
isRouteToLeader = false;
+ isEndToEndTracing = false;
+ isTraceContextPresent = false;
}
@Test
@@ -464,6 +489,83 @@ public void testNewCallContextWithRouteToLeaderHeaderAndLarDisabled() {
rpc.shutdown();
}
+ @Test
+ public void testNewCallContextWithEndToEndTracingHeader() {
+ SpannerOptions options =
+ SpannerOptions.newBuilder()
+ .setProjectId("some-project")
+ .setEnableEndToEndTracing(true)
+ .build();
+ GapicSpannerRpc rpc = new GapicSpannerRpc(options, false);
+ GrpcCallContext callContext =
+ rpc.newCallContext(
+ optionsMap,
+ "/some/resource",
+ ExecuteSqlRequest.getDefaultInstance(),
+ SpannerGrpc.getExecuteSqlMethod());
+ assertNotNull(callContext);
+ assertEquals(
+ ImmutableList.of("true"),
+ callContext.getExtraHeaders().get("x-goog-spanner-end-to-end-tracing"));
+ assertEquals(
+ ImmutableList.of("projects/some-project"),
+ callContext.getExtraHeaders().get(ApiClientHeaderProvider.getDefaultResourceHeaderKey()));
+ rpc.shutdown();
+ }
+
+ @Test
+ public void testNewCallContextWithoutEndToEndTracingHeader() {
+ SpannerOptions options =
+ SpannerOptions.newBuilder()
+ .setProjectId("some-project")
+ .setEnableEndToEndTracing(false)
+ .build();
+ GapicSpannerRpc rpc = new GapicSpannerRpc(options, false);
+ GrpcCallContext callContext =
+ rpc.newCallContext(
+ optionsMap,
+ "/some/resource",
+ ExecuteSqlRequest.getDefaultInstance(),
+ SpannerGrpc.getExecuteSqlMethod());
+ assertNotNull(callContext);
+ assertNull(callContext.getExtraHeaders().get("x-goog-spanner-end-to-end-tracing"));
+ rpc.shutdown();
+ }
+
+ @Test
+ public void testEndToEndTracingHeaderWithEnabledTracing() {
+ final SpannerOptions options =
+ createSpannerOptions().toBuilder().setEnableEndToEndTracing(true).build();
+ try (Spanner spanner = options.getService()) {
+ final DatabaseClient databaseClient =
+ spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]"));
+ TransactionRunner runner = databaseClient.readWriteTransaction();
+ runner.run(
+ transaction -> {
+ transaction.executeUpdate(UPDATE_FOO_STATEMENT);
+ return null;
+ });
+ }
+ assertTrue(isEndToEndTracing);
+ }
+
+ @Test
+ public void testEndToEndTracingHeaderWithDisabledTracing() {
+ final SpannerOptions options =
+ createSpannerOptions().toBuilder().setEnableEndToEndTracing(false).build();
+ try (Spanner spanner = options.getService()) {
+ final DatabaseClient databaseClient =
+ spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]"));
+ TransactionRunner runner = databaseClient.readWriteTransaction();
+ runner.run(
+ transaction -> {
+ transaction.executeUpdate(UPDATE_FOO_STATEMENT);
+ return null;
+ });
+ }
+ assertFalse(isEndToEndTracing);
+ }
+
@Test
public void testAdminRequestsLimitExceededRetryAlgorithm() {
AdminRequestsLimitExceededRetryAlgorithm alg =
@@ -535,6 +637,73 @@ public void testCustomUserAgent() {
}
}
+ @Test
+ public void testTraceContextHeaderWithOpenTelemetryAndEndToEndTracingEnabled() {
+ OpenTelemetry openTelemetry =
+ OpenTelemetrySdk.builder()
+ .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
+ .setTracerProvider(SdkTracerProvider.builder().setSampler(Sampler.alwaysOn()).build())
+ .build();
+
+ final SpannerOptions options =
+ createSpannerOptions()
+ .toBuilder()
+ .setOpenTelemetry(openTelemetry)
+ .setEnableEndToEndTracing(true)
+ .build();
+ try (Spanner spanner = options.getService()) {
+ final DatabaseClient databaseClient =
+ spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]"));
+
+ try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) {
+ rs.next();
+ }
+
+ assertTrue(isTraceContextPresent);
+ }
+ }
+
+ @Test
+ public void testTraceContextHeaderWithOpenTelemetryAndEndToEndTracingDisabled() {
+ OpenTelemetry openTelemetry =
+ OpenTelemetrySdk.builder()
+ .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
+ .setTracerProvider(SdkTracerProvider.builder().setSampler(Sampler.alwaysOn()).build())
+ .build();
+
+ final SpannerOptions options =
+ createSpannerOptions()
+ .toBuilder()
+ .setOpenTelemetry(openTelemetry)
+ .setEnableEndToEndTracing(false)
+ .build();
+ try (Spanner spanner = options.getService()) {
+ final DatabaseClient databaseClient =
+ spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]"));
+
+ try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) {
+ rs.next();
+ }
+
+ assertFalse(isTraceContextPresent);
+ }
+ }
+
+ @Test
+ public void testTraceContextHeaderWithoutOpenTelemetry() {
+ final SpannerOptions options = createSpannerOptions();
+ try (Spanner spanner = options.getService()) {
+ final DatabaseClient databaseClient =
+ spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]"));
+
+ try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) {
+ rs.next();
+ }
+
+ assertFalse(isTraceContextPresent);
+ }
+ }
+
@Test
public void testRouteToLeaderHeaderForReadOnly() {
final SpannerOptions options =
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProviderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProviderTest.java
index cc43e2dc334..c4fdd6200af 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProviderTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProviderTest.java
@@ -94,6 +94,17 @@ public void testNewRouteToLeaderHeader() {
assertTrue(Maps.difference(extraHeaders, expectedHeaders).areEqual());
}
+ @Test
+ public void testNewEndToEndTracingHeader() {
+ SpannerMetadataProvider metadataProvider =
+ SpannerMetadataProvider.create(ImmutableMap.of(), "header1");
+ Map> extraHeaders = metadataProvider.newEndToEndTracingHeader();
+ Map> expectedHeaders =
+ ImmutableMap.>of(
+ "x-goog-spanner-end-to-end-tracing", ImmutableList.of("true"));
+ assertTrue(Maps.difference(extraHeaders, expectedHeaders).areEqual());
+ }
+
private String getResourceHeaderValue(
SpannerMetadataProvider headerProvider, String resourceTokenTemplate) {
Metadata metadata = headerProvider.newMetadata(resourceTokenTemplate, "projects/p");