Skip to content

Commit 0b7fdaf

Browse files
authored
feat: Add opt-in flag and ClientInterceptor to propagate trace context for Spanner end to end tracing (#3162)
* feat(spanner): Add x-goog-spanner-end-to-end-tracing header for requests to SpanFE * Add Grpc Telemetry client interceptor for trace context propagation * Remove print statement * copy grpc telemetry client interceptor code * rename spanner option for end to end tracing * add test for custom client interceptor code * resolve comments * Make trace context interceptor conditional based on client opt-in * resolve comments * reformat code * resolve comments * fix clirr build failure * fix lint errors * combine enable/disable fn into single fn * Rename server side tracing to spanner tracing * Rename spanner tracing to end to end tracing * fix comments * Fix lint error
1 parent 1719f44 commit 0b7fdaf

10 files changed

+364
-0
lines changed

google-cloud-spanner/clirr-ignored-differences.xml

+7
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,13 @@
695695
<method>boolean isEnableApiTracing()</method>
696696
</difference>
697697

698+
<!-- Added end to end tracing option -->
699+
<difference>
700+
<differenceType>7012</differenceType>
701+
<className>com/google/cloud/spanner/SpannerOptions$SpannerEnvironment</className>
702+
<method>boolean isEnableEndToEndTracing()</method>
703+
</difference>
704+
698705
<!-- Added Built In Metrics option -->
699706
<difference>
700707
<differenceType>7012</differenceType>

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java

+35
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
163163
private final boolean enableApiTracing;
164164
private final boolean enableBuiltInMetrics;
165165
private final boolean enableExtendedTracing;
166+
private final boolean enableEndToEndTracing;
166167

167168
enum TracingFramework {
168169
OPEN_CENSUS,
@@ -670,6 +671,7 @@ protected SpannerOptions(Builder builder) {
670671
enableApiTracing = builder.enableApiTracing;
671672
enableExtendedTracing = builder.enableExtendedTracing;
672673
enableBuiltInMetrics = builder.enableBuiltInMetrics;
674+
enableEndToEndTracing = builder.enableEndToEndTracing;
673675
}
674676

675677
/**
@@ -706,6 +708,10 @@ default boolean isEnableApiTracing() {
706708
default boolean isEnableBuiltInMetrics() {
707709
return false;
708710
}
711+
712+
default boolean isEnableEndToEndTracing() {
713+
return false;
714+
}
709715
}
710716

711717
/**
@@ -720,6 +726,8 @@ private static class SpannerEnvironmentImpl implements SpannerEnvironment {
720726
private static final String SPANNER_ENABLE_EXTENDED_TRACING = "SPANNER_ENABLE_EXTENDED_TRACING";
721727
private static final String SPANNER_ENABLE_API_TRACING = "SPANNER_ENABLE_API_TRACING";
722728
private static final String SPANNER_ENABLE_BUILTIN_METRICS = "SPANNER_ENABLE_BUILTIN_METRICS";
729+
private static final String SPANNER_ENABLE_END_TO_END_TRACING =
730+
"SPANNER_ENABLE_END_TO_END_TRACING";
723731

724732
private SpannerEnvironmentImpl() {}
725733

@@ -752,6 +760,11 @@ public boolean isEnableBuiltInMetrics() {
752760
// removed in the future.
753761
return Boolean.parseBoolean(System.getenv(SPANNER_ENABLE_BUILTIN_METRICS));
754762
}
763+
764+
@Override
765+
public boolean isEnableEndToEndTracing() {
766+
return Boolean.parseBoolean(System.getenv(SPANNER_ENABLE_END_TO_END_TRACING));
767+
}
755768
}
756769

757770
/** Builder for {@link SpannerOptions} instances. */
@@ -816,6 +829,7 @@ public static class Builder
816829
private boolean enableApiTracing = SpannerOptions.environment.isEnableApiTracing();
817830
private boolean enableExtendedTracing = SpannerOptions.environment.isEnableExtendedTracing();
818831
private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics();
832+
private boolean enableEndToEndTracing = SpannerOptions.environment.isEnableEndToEndTracing();
819833

820834
private static String createCustomClientLibToken(String token) {
821835
return token + " " + ServiceOptions.getGoogApiClientLibName();
@@ -882,6 +896,7 @@ protected Builder() {
882896
this.enableApiTracing = options.enableApiTracing;
883897
this.enableExtendedTracing = options.enableExtendedTracing;
884898
this.enableBuiltInMetrics = options.enableBuiltInMetrics;
899+
this.enableEndToEndTracing = options.enableEndToEndTracing;
885900
}
886901

887902
@Override
@@ -1415,6 +1430,17 @@ public Builder setEnableExtendedTracing(boolean enableExtendedTracing) {
14151430
return this;
14161431
}
14171432

1433+
/**
1434+
* Sets whether to enable end to end tracing. Enabling this option will create the trace spans
1435+
* at the Spanner layer. By default, end to end tracing is disabled. Enabling end to end tracing
1436+
* requires OpenTelemetry to be set up. Simply enabling this option won't generate traces at
1437+
* Spanner layer.
1438+
*/
1439+
public Builder setEnableEndToEndTracing(boolean enableEndToEndTracing) {
1440+
this.enableEndToEndTracing = enableEndToEndTracing;
1441+
return this;
1442+
}
1443+
14181444
@SuppressWarnings("rawtypes")
14191445
@Override
14201446
public SpannerOptions build() {
@@ -1504,6 +1530,7 @@ public static void enableOpenCensusTraces() {
15041530
*/
15051531
@ObsoleteApi(
15061532
"The OpenCensus project is deprecated. Use enableOpenTelemetryTraces to switch to OpenTelemetry traces")
1533+
@VisibleForTesting
15071534
static void resetActiveTracingFramework() {
15081535
activeTracingFramework = null;
15091536
}
@@ -1745,6 +1772,14 @@ public boolean isEnableExtendedTracing() {
17451772
return enableExtendedTracing;
17461773
}
17471774

1775+
/**
1776+
* Returns whether end to end tracing is enabled. If this option is enabled then trace spans will
1777+
* be created at the Spanner layer.
1778+
*/
1779+
public boolean isEndToEndTracingEnabled() {
1780+
return enableEndToEndTracing;
1781+
}
1782+
17481783
/** Returns the default query options to use for the specific database. */
17491784
public QueryOptions getDefaultQueryOptions(DatabaseId databaseId) {
17501785
// Use the specific query options for the database if any have been specified. These have

google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java

+7
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ public class GapicSpannerRpc implements SpannerRpc {
272272
private static final ConcurrentMap<String, RateLimiter> ADMINISTRATIVE_REQUESTS_RATE_LIMITERS =
273273
new ConcurrentHashMap<>();
274274
private final boolean leaderAwareRoutingEnabled;
275+
private final boolean endToEndTracingEnabled;
275276
private final int numChannels;
276277
private final boolean isGrpcGcpExtensionEnabled;
277278

@@ -325,6 +326,7 @@ public GapicSpannerRpc(final SpannerOptions options) {
325326
this.callCredentialsProvider = options.getCallCredentialsProvider();
326327
this.compressorName = options.getCompressorName();
327328
this.leaderAwareRoutingEnabled = options.isLeaderAwareRoutingEnabled();
329+
this.endToEndTracingEnabled = options.isEndToEndTracingEnabled();
328330
this.numChannels = options.getNumChannels();
329331
this.isGrpcGcpExtensionEnabled = options.isGrpcGcpExtensionEnabled();
330332

@@ -350,6 +352,8 @@ public GapicSpannerRpc(final SpannerOptions options) {
350352
MoreObjects.firstNonNull(
351353
options.getInterceptorProvider(),
352354
SpannerInterceptorProvider.createDefault(options.getOpenTelemetry())))
355+
// This sets the trace context headers.
356+
.withTraceContext(endToEndTracingEnabled, options.getOpenTelemetry())
353357
// This sets the response compressor (Server -> Client).
354358
.withEncoding(compressorName))
355359
.setHeaderProvider(headerProviderWithUserAgent)
@@ -2007,6 +2011,9 @@ <ReqT, RespT> GrpcCallContext newCallContext(
20072011
if (routeToLeader && leaderAwareRoutingEnabled) {
20082012
context = context.withExtraHeaders(metadataProvider.newRouteToLeaderHeader());
20092013
}
2014+
if (endToEndTracingEnabled) {
2015+
context = context.withExtraHeaders(metadataProvider.newEndToEndTracingHeader());
2016+
}
20102017
if (callCredentialsProvider != null) {
20112018
CallCredentials callCredentials = callCredentialsProvider.getCallCredentials();
20122019
if (callCredentials != null) {

google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java

+8
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ SpannerInterceptorProvider withEncoding(String encoding) {
7474
return this;
7575
}
7676

77+
SpannerInterceptorProvider withTraceContext(
78+
boolean endToEndTracingEnabled, OpenTelemetry openTelemetry) {
79+
if (endToEndTracingEnabled) {
80+
return with(new TraceContextInterceptor(openTelemetry));
81+
}
82+
return this;
83+
}
84+
7785
@Override
7886
public List<ClientInterceptor> getInterceptors() {
7987
return clientInterceptors;

google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java

+7
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@ class SpannerMetadataProvider {
3737
private final Map<Metadata.Key<String>, String> headers;
3838
private final String resourceHeaderKey;
3939
private static final String ROUTE_TO_LEADER_HEADER_KEY = "x-goog-spanner-route-to-leader";
40+
private static final String END_TO_END_TRACING_HEADER_KEY = "x-goog-spanner-end-to-end-tracing";
4041
private static final Pattern[] RESOURCE_TOKEN_PATTERNS = {
4142
Pattern.compile("^(?<headerValue>projects/[^/]*/instances/[^/]*/databases/[^/]*)(.*)?"),
4243
Pattern.compile("^(?<headerValue>projects/[^/]*/instances/[^/]*)(.*)?")
4344
};
4445

4546
private static final Map<String, List<String>> ROUTE_TO_LEADER_HEADER_MAP =
4647
ImmutableMap.of(ROUTE_TO_LEADER_HEADER_KEY, Collections.singletonList("true"));
48+
private static final Map<String, List<String>> END_TO_END_TRACING_HEADER_MAP =
49+
ImmutableMap.of(END_TO_END_TRACING_HEADER_KEY, Collections.singletonList("true"));
4750

4851
private SpannerMetadataProvider(Map<String, String> headers, String resourceHeaderKey) {
4952
this.resourceHeaderKey = resourceHeaderKey;
@@ -89,6 +92,10 @@ Map<String, List<String>> newRouteToLeaderHeader() {
8992
return ROUTE_TO_LEADER_HEADER_MAP;
9093
}
9194

95+
Map<String, List<String>> newEndToEndTracingHeader() {
96+
return END_TO_END_TRACING_HEADER_MAP;
97+
}
98+
9299
private Map<Metadata.Key<String>, String> constructHeadersAsMetadata(
93100
Map<String, String> headers) {
94101
ImmutableMap.Builder<Metadata.Key<String>, String> headersAsMetadataBuilder =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.spanner.spi.v1;
17+
18+
import io.grpc.CallOptions;
19+
import io.grpc.Channel;
20+
import io.grpc.ClientCall;
21+
import io.grpc.ClientInterceptor;
22+
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
23+
import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
24+
import io.grpc.Metadata;
25+
import io.grpc.MethodDescriptor;
26+
import io.opentelemetry.api.OpenTelemetry;
27+
import io.opentelemetry.context.Context;
28+
import io.opentelemetry.context.propagation.TextMapPropagator;
29+
import io.opentelemetry.context.propagation.TextMapSetter;
30+
31+
/**
32+
* Intercepts all gRPC calls and injects trace context related headers to propagate trace context to
33+
* Spanner. This class takes reference from OpenTelemetry's JAVA instrumentation library for gRPC.
34+
* 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
35+
*/
36+
class TraceContextInterceptor implements ClientInterceptor {
37+
38+
private final TextMapPropagator textMapPropagator;
39+
40+
TraceContextInterceptor(OpenTelemetry openTelemetry) {
41+
this.textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator();
42+
}
43+
44+
enum MetadataSetter implements TextMapSetter<Metadata> {
45+
INSTANCE;
46+
47+
@SuppressWarnings("null")
48+
@Override
49+
public void set(Metadata carrier, String key, String value) {
50+
carrier.put(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value);
51+
}
52+
}
53+
54+
private static final class NoopSimpleForwardingClientCallListener<RespT>
55+
extends SimpleForwardingClientCallListener<RespT> {
56+
public NoopSimpleForwardingClientCallListener(ClientCall.Listener<RespT> responseListener) {
57+
super(responseListener);
58+
}
59+
}
60+
61+
@Override
62+
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
63+
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
64+
return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
65+
@Override
66+
public void start(Listener<RespT> responseListener, Metadata headers) {
67+
Context parentContext = Context.current();
68+
textMapPropagator.inject(parentContext, headers, MetadataSetter.INSTANCE);
69+
super.start(new NoopSimpleForwardingClientCallListener<RespT>(responseListener), headers);
70+
}
71+
};
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner;
18+
19+
/** Helper to configure SpannerOptions for tests. */
20+
public class SpannerOptionsHelper {
21+
22+
/**
23+
* Resets the activeTracingFramework. This variable is used for internal testing, and is not a
24+
* valid production scenario.
25+
*/
26+
public static void resetActiveTracingFramework() {
27+
SpannerOptions.resetActiveTracingFramework();
28+
}
29+
}

google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java

+18
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,24 @@ public void testLeaderAwareRoutingEnablement() {
736736
.isLeaderAwareRoutingEnabled());
737737
}
738738

739+
@Test
740+
public void testEndToEndTracingEnablement() {
741+
// Test that end to end tracing is disabled by default.
742+
assertFalse(SpannerOptions.newBuilder().setProjectId("p").build().isEndToEndTracingEnabled());
743+
assertTrue(
744+
SpannerOptions.newBuilder()
745+
.setProjectId("p")
746+
.setEnableEndToEndTracing(true)
747+
.build()
748+
.isEndToEndTracingEnabled());
749+
assertFalse(
750+
SpannerOptions.newBuilder()
751+
.setProjectId("p")
752+
.setEnableEndToEndTracing(false)
753+
.build()
754+
.isEndToEndTracingEnabled());
755+
}
756+
739757
@Test
740758
public void testSetDirectedReadOptions() {
741759
final DirectedReadOptions directedReadOptions =

0 commit comments

Comments
 (0)