Skip to content

Commit 4b5e8af

Browse files
authored
feat(sdk-platform-java): Add CompositeTracer and CompositeTracerFactory. (#12321)
This PR - Introduces CompositeTracer and CompositeTracerFactory into the gax-java tracing architecture, enabling the delegation of telemetry, logging, and metrics events to multiple underlying ApiTracer instances. - Creates a LoggingTracerFactory if logging is enabled in ClientContext.
1 parent 6ed37d3 commit 4b5e8af

File tree

18 files changed

+1074
-28
lines changed

18 files changed

+1074
-28
lines changed

sdk-platform-java/gax-java/gax/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@
139139
<configuration>
140140
<argLine>@{argLine} -Djava.util.logging.SimpleFormatter.format="%1$tY %1$tl:%1$tM:%1$tS.%1$tL %2$s %4$s: %5$s%6$s%n"</argLine>
141141
<!-- These tests require an Env Var to be set. Use -PenvVarTest to ONLY run these tests -->
142-
<test>!EndpointContextTest#endpointContextBuild_universeDomainEnvVarSet+endpointContextBuild_multipleUniverseDomainConfigurations_clientSettingsHasPriority,!LoggingEnabledTest,!LoggingTracerTest</test>
142+
<test>!EndpointContextTest#endpointContextBuild_universeDomainEnvVarSet+endpointContextBuild_multipleUniverseDomainConfigurations_clientSettingsHasPriority,!LoggingEnabledTest,!LoggingTracerTest,!ClientContextTest#testGetApiTracerFactory_loggingEnabled</test>
143143
</configuration>
144144
</plugin>
145145
</plugins>
@@ -154,7 +154,7 @@
154154
<groupId>org.apache.maven.plugins</groupId>
155155
<artifactId>maven-surefire-plugin</artifactId>
156156
<configuration>
157-
<test>EndpointContextTest#endpointContextBuild_universeDomainEnvVarSet+endpointContextBuild_multipleUniverseDomainConfigurations_clientSettingsHasPriority,LoggingEnabledTest,LoggingTracerTest</test>
157+
<test>EndpointContextTest#endpointContextBuild_universeDomainEnvVarSet+endpointContextBuild_multipleUniverseDomainConfigurations_clientSettingsHasPriority,LoggingEnabledTest,LoggingTracerTest, ClientContextTest#testGetApiTracerFactory_loggingEnabled</test>
158158
</configuration>
159159
</plugin>
160160
</plugins>

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
@InternalApi
3737
public class LoggingUtils {
3838

39-
static final String GOOGLE_SDK_JAVA_LOGGING = "GOOGLE_SDK_JAVA_LOGGING";
39+
private static final String GOOGLE_SDK_JAVA_LOGGING = "GOOGLE_SDK_JAVA_LOGGING";
4040

4141
private static boolean loggingEnabled = checkLoggingEnabled(GOOGLE_SDK_JAVA_LOGGING);
4242

@@ -45,7 +45,7 @@ public class LoggingUtils {
4545
*
4646
* @return true if logging is enabled, false otherwise.
4747
*/
48-
static boolean isLoggingEnabled() {
48+
public static boolean isLoggingEnabled() {
4949
return loggingEnabled;
5050
}
5151

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@
4040
import com.google.api.gax.core.BackgroundResource;
4141
import com.google.api.gax.core.ExecutorAsBackgroundResource;
4242
import com.google.api.gax.core.ExecutorProvider;
43+
import com.google.api.gax.logging.LoggingUtils;
4344
import com.google.api.gax.rpc.internal.QuotaProjectIdHidingCredentials;
4445
import com.google.api.gax.tracing.ApiTracerContext;
4546
import com.google.api.gax.tracing.ApiTracerFactory;
4647
import com.google.api.gax.tracing.BaseApiTracerFactory;
47-
import com.google.api.gax.tracing.SpanTracerFactory;
48+
import com.google.api.gax.tracing.CompositeTracerFactory;
49+
import com.google.api.gax.tracing.LoggingTracerFactory;
4850
import com.google.auth.ApiKeyCredentials;
4951
import com.google.auth.CredentialTypeForMetrics;
5052
import com.google.auth.Credentials;
@@ -279,18 +281,7 @@ public static ClientContext create(StubSettings settings) throws IOException {
279281
backgroundResources.add(watchdog);
280282
}
281283

282-
ApiTracerContext apiTracerContext =
283-
ApiTracerContext.newBuilder()
284-
.setServerAddress(endpointContext.resolvedServerAddress())
285-
.setServerPort(endpointContext.resolvedServerPort())
286-
.setLibraryMetadata(settings.getLibraryMetadata())
287-
.setUrlDomain(endpointContext.getUrlDomain())
288-
.setServiceName(endpointContext.serviceName())
289-
.build();
290-
ApiTracerFactory apiTracerFactory = settings.getTracerFactory();
291-
if (apiTracerFactory instanceof SpanTracerFactory) {
292-
apiTracerFactory = apiTracerFactory.withContext(apiTracerContext);
293-
}
284+
ApiTracerFactory apiTracerFactory = getApiTracerFactory(settings, endpointContext);
294285

295286
return newBuilder()
296287
.setBackgroundResources(backgroundResources.build())
@@ -311,6 +302,32 @@ public static ClientContext create(StubSettings settings) throws IOException {
311302
.build();
312303
}
313304

305+
@VisibleForTesting
306+
static ApiTracerFactory getApiTracerFactory(
307+
StubSettings settings, EndpointContext endpointContext) {
308+
ApiTracerFactory apiTracerFactory = settings.getTracerFactory();
309+
310+
if (LoggingUtils.isLoggingEnabled()) {
311+
apiTracerFactory =
312+
new CompositeTracerFactory(
313+
ImmutableList.of(new LoggingTracerFactory(), apiTracerFactory));
314+
}
315+
316+
if (apiTracerFactory.needsContext()) {
317+
ApiTracerContext apiTracerContext =
318+
ApiTracerContext.newBuilder()
319+
.setServerAddress(endpointContext.resolvedServerAddress())
320+
.setServerPort(endpointContext.resolvedServerPort())
321+
.setServiceName(endpointContext.serviceName())
322+
.setLibraryMetadata(settings.getLibraryMetadata())
323+
.setUrlDomain(endpointContext.getUrlDomain())
324+
.build();
325+
apiTracerFactory = apiTracerFactory.withContext(apiTracerContext);
326+
}
327+
328+
return apiTracerFactory;
329+
}
330+
314331
/** Determines which credentials to use. API key overrides credentials provided by provider. */
315332
private static Credentials getCredentials(StubSettings settings) throws IOException {
316333
Credentials credentials;

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ApiTracerFactory.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,14 @@ default ApiTracer newTracer(ApiTracer parent, ApiTracerContext tracerContext) {
7474
}
7575

7676
/**
77-
* @return the {@link ApiTracerContext} for this factory
77+
* Indicates whether this factory requires an {@link ApiTracerContext} to be injected via {@link
78+
* #withContext(ApiTracerContext)} before creating tracers.
79+
*
80+
* @return {@code true} if an {@link ApiTracerContext} should be injected, {@code false}
81+
* otherwise.
7882
*/
79-
default ApiTracerContext getApiTracerContext() {
80-
return ApiTracerContext.empty();
83+
default boolean needsContext() {
84+
return false;
8185
}
8286

8387
/**
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
* * Neither the name of Google LLC nor the names of its
15+
* contributors may be used to endorse or promote products derived from
16+
* this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
package com.google.api.gax.tracing;
31+
32+
import com.google.api.core.InternalApi;
33+
import com.google.common.collect.ImmutableList;
34+
import java.util.ArrayList;
35+
import java.util.List;
36+
import java.util.Map;
37+
38+
/**
39+
* A composite implementation of {@link ApiTracer} that delegates all tracing events to a list of
40+
* underlying tracers.
41+
*
42+
* <p>For internal use only.
43+
*/
44+
@InternalApi
45+
class CompositeTracer extends BaseApiTracer {
46+
private final List<ApiTracer> children;
47+
48+
public CompositeTracer(List<ApiTracer> children) {
49+
this.children = ImmutableList.copyOf(children);
50+
}
51+
52+
@Override
53+
public Scope inScope() {
54+
final List<Scope> childScopes = new ArrayList<>(children.size());
55+
56+
try {
57+
for (ApiTracer child : children) {
58+
childScopes.add(child.inScope());
59+
}
60+
} catch (RuntimeException e) {
61+
for (int i = childScopes.size() - 1; i >= 0; i--) {
62+
try {
63+
childScopes.get(i).close();
64+
} catch (RuntimeException suppressed) {
65+
e.addSuppressed(suppressed);
66+
}
67+
}
68+
throw e;
69+
}
70+
71+
return () -> {
72+
RuntimeException exception = null;
73+
for (int i = childScopes.size() - 1; i >= 0; i--) {
74+
try {
75+
childScopes.get(i).close();
76+
} catch (RuntimeException e) {
77+
if (exception == null) {
78+
exception = e;
79+
} else {
80+
exception.addSuppressed(e);
81+
}
82+
}
83+
}
84+
if (exception != null) {
85+
throw exception;
86+
}
87+
};
88+
}
89+
90+
@Override
91+
public void operationSucceeded() {
92+
for (int i = children.size() - 1; i >= 0; i--) {
93+
children.get(i).operationSucceeded();
94+
}
95+
}
96+
97+
@Override
98+
public void operationCancelled() {
99+
for (int i = children.size() - 1; i >= 0; i--) {
100+
children.get(i).operationCancelled();
101+
}
102+
}
103+
104+
@Override
105+
public void operationFailed(Throwable error) {
106+
for (int i = children.size() - 1; i >= 0; i--) {
107+
children.get(i).operationFailed(error);
108+
}
109+
}
110+
111+
@Override
112+
public void connectionSelected(String id) {
113+
for (ApiTracer child : children) {
114+
child.connectionSelected(id);
115+
}
116+
}
117+
118+
@Override
119+
@Deprecated
120+
public void attemptStarted(int attemptNumber) {
121+
for (ApiTracer child : children) {
122+
child.attemptStarted(attemptNumber);
123+
}
124+
}
125+
126+
@Override
127+
public void attemptStarted(Object request, int attemptNumber) {
128+
for (ApiTracer child : children) {
129+
child.attemptStarted(request, attemptNumber);
130+
}
131+
}
132+
133+
@Override
134+
public void attemptSucceeded() {
135+
for (int i = children.size() - 1; i >= 0; i--) {
136+
children.get(i).attemptSucceeded();
137+
}
138+
}
139+
140+
@Override
141+
public void attemptCancelled() {
142+
for (int i = children.size() - 1; i >= 0; i--) {
143+
children.get(i).attemptCancelled();
144+
}
145+
}
146+
147+
@Override
148+
public void attemptFailed(Throwable error, org.threeten.bp.Duration delay) {
149+
for (int i = children.size() - 1; i >= 0; i--) {
150+
children.get(i).attemptFailed(error, delay);
151+
}
152+
}
153+
154+
@Override
155+
public void attemptFailedDuration(Throwable error, java.time.Duration delay) {
156+
for (int i = children.size() - 1; i >= 0; i--) {
157+
children.get(i).attemptFailedDuration(error, delay);
158+
}
159+
}
160+
161+
@Override
162+
public void attemptFailedRetriesExhausted(Throwable error) {
163+
for (int i = children.size() - 1; i >= 0; i--) {
164+
children.get(i).attemptFailedRetriesExhausted(error);
165+
}
166+
}
167+
168+
@Override
169+
public void attemptPermanentFailure(Throwable error) {
170+
for (int i = children.size() - 1; i >= 0; i--) {
171+
children.get(i).attemptPermanentFailure(error);
172+
}
173+
}
174+
175+
@Override
176+
public void lroStartFailed(Throwable error) {
177+
for (int i = children.size() - 1; i >= 0; i--) {
178+
children.get(i).lroStartFailed(error);
179+
}
180+
}
181+
182+
@Override
183+
public void lroStartSucceeded() {
184+
for (int i = children.size() - 1; i >= 0; i--) {
185+
children.get(i).lroStartSucceeded();
186+
}
187+
}
188+
189+
@Override
190+
public void responseReceived() {
191+
for (int i = children.size() - 1; i >= 0; i--) {
192+
children.get(i).responseReceived();
193+
}
194+
}
195+
196+
@Override
197+
public void responseHeadersReceived(Map<String, Object> headers) {
198+
for (int i = children.size() - 1; i >= 0; i--) {
199+
children.get(i).responseHeadersReceived(headers);
200+
}
201+
}
202+
203+
@Override
204+
public void requestSent() {
205+
for (ApiTracer child : children) {
206+
child.requestSent();
207+
}
208+
}
209+
210+
@Override
211+
public void batchRequestSent(long elementCount, long requestSize) {
212+
for (ApiTracer child : children) {
213+
child.batchRequestSent(elementCount, requestSize);
214+
}
215+
}
216+
}

0 commit comments

Comments
 (0)