Skip to content

Commit eb6ef7c

Browse files
committed
feat(gax): implement CompositeTracer to wrap API tracers
1 parent 269c749 commit eb6ef7c

File tree

6 files changed

+304
-5
lines changed

6 files changed

+304
-5
lines changed

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
import com.google.api.gax.tracing.ApiTracerContext;
4545
import com.google.api.gax.tracing.ApiTracerFactory;
4646
import com.google.api.gax.tracing.BaseApiTracerFactory;
47-
import com.google.api.gax.tracing.SpanTracerFactory;
4847
import com.google.auth.ApiKeyCredentials;
4948
import com.google.auth.CredentialTypeForMetrics;
5049
import com.google.auth.Credentials;
@@ -278,8 +277,13 @@ public static ClientContext create(StubSettings settings) throws IOException {
278277
.setLibraryMetadata(settings.getLibraryMetadata())
279278
.build();
280279
ApiTracerFactory apiTracerFactory = settings.getTracerFactory();
281-
if (apiTracerFactory instanceof SpanTracerFactory) {
282-
apiTracerFactory = apiTracerFactory.withContext(apiTracerContext);
280+
apiTracerFactory = apiTracerFactory.withContext(apiTracerContext);
281+
if (!(apiTracerFactory instanceof com.google.api.gax.tracing.CompositeTracerFactory)) {
282+
com.google.api.gax.tracing.ApiTracerFactory loggingTracerFactory =
283+
new com.google.api.gax.tracing.LoggingTracerFactory().withContext(apiTracerContext);
284+
apiTracerFactory =
285+
new com.google.api.gax.tracing.CompositeTracerFactory(
286+
apiTracerFactory, loggingTracerFactory);
283287
}
284288

285289
return newBuilder()
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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+
31+
package com.google.api.gax.tracing;
32+
33+
import com.google.api.core.BetaApi;
34+
import com.google.api.core.InternalApi;
35+
import java.util.List;
36+
37+
/** A composite {@link ApiTracer} that delegates to a list of {@link ApiTracer}s. */
38+
@BetaApi
39+
@InternalApi
40+
public class CompositeTracer implements ApiTracer {
41+
private final List<ApiTracer> tracers;
42+
43+
public CompositeTracer(List<ApiTracer> tracers) {
44+
this.tracers = tracers;
45+
}
46+
47+
public List<ApiTracer> getTracers() {
48+
return tracers;
49+
}
50+
51+
@Override
52+
public Scope inScope() {
53+
// Returning a scope that closes all sub-scopes
54+
final Scope[] scopes = new Scope[tracers.size()];
55+
for (int i = 0; i < tracers.size(); i++) {
56+
scopes[i] = tracers.get(i).inScope();
57+
}
58+
return () -> {
59+
for (int i = scopes.length - 1; i >= 0; i--) {
60+
Scope scope = scopes[i];
61+
if (scope != null) {
62+
scope.close();
63+
}
64+
}
65+
};
66+
}
67+
68+
@Override
69+
public void operationSucceeded() {
70+
for (int i = tracers.size() - 1; i >= 0; i--) {
71+
tracers.get(i).operationSucceeded();
72+
}
73+
}
74+
75+
@Override
76+
public void operationCancelled() {
77+
for (int i = tracers.size() - 1; i >= 0; i--) {
78+
tracers.get(i).operationCancelled();
79+
}
80+
}
81+
82+
@Override
83+
public void operationFailed(Throwable error) {
84+
for (int i = tracers.size() - 1; i >= 0; i--) {
85+
tracers.get(i).operationFailed(error);
86+
}
87+
}
88+
89+
@Override
90+
public void connectionSelected(String id) {
91+
for (ApiTracer tracer : tracers) {
92+
tracer.connectionSelected(id);
93+
}
94+
}
95+
96+
@Override
97+
@SuppressWarnings("deprecation")
98+
public void attemptStarted(int attemptNumber) {
99+
for (ApiTracer tracer : tracers) {
100+
tracer.attemptStarted(attemptNumber);
101+
}
102+
}
103+
104+
@Override
105+
public void attemptStarted(Object request, int attemptNumber) {
106+
for (ApiTracer tracer : tracers) {
107+
tracer.attemptStarted(request, attemptNumber);
108+
}
109+
}
110+
111+
@Override
112+
public void attemptSucceeded() {
113+
for (int i = tracers.size() - 1; i >= 0; i--) {
114+
tracers.get(i).attemptSucceeded();
115+
}
116+
}
117+
118+
@Override
119+
public void attemptCancelled() {
120+
for (int i = tracers.size() - 1; i >= 0; i--) {
121+
tracers.get(i).attemptCancelled();
122+
}
123+
}
124+
125+
@Override
126+
@SuppressWarnings("deprecation")
127+
public void attemptFailed(Throwable error, org.threeten.bp.Duration delay) {
128+
for (int i = tracers.size() - 1; i >= 0; i--) {
129+
tracers.get(i).attemptFailed(error, delay);
130+
}
131+
}
132+
133+
@Override
134+
public void attemptFailedDuration(Throwable error, java.time.Duration delay) {
135+
for (int i = tracers.size() - 1; i >= 0; i--) {
136+
tracers.get(i).attemptFailedDuration(error, delay);
137+
}
138+
}
139+
140+
@Override
141+
public void attemptFailedRetriesExhausted(Throwable error) {
142+
for (int i = tracers.size() - 1; i >= 0; i--) {
143+
tracers.get(i).attemptFailedRetriesExhausted(error);
144+
}
145+
}
146+
147+
@Override
148+
public void attemptPermanentFailure(Throwable error) {
149+
for (int i = tracers.size() - 1; i >= 0; i--) {
150+
tracers.get(i).attemptPermanentFailure(error);
151+
}
152+
}
153+
154+
@Override
155+
public void lroStartFailed(Throwable error) {
156+
for (int i = tracers.size() - 1; i >= 0; i--) {
157+
tracers.get(i).lroStartFailed(error);
158+
}
159+
}
160+
161+
@Override
162+
public void lroStartSucceeded() {
163+
for (int i = tracers.size() - 1; i >= 0; i--) {
164+
tracers.get(i).lroStartSucceeded();
165+
}
166+
}
167+
168+
@Override
169+
public void responseReceived() {
170+
for (int i = tracers.size() - 1; i >= 0; i--) {
171+
tracers.get(i).responseReceived();
172+
}
173+
}
174+
175+
@Override
176+
public void requestSent() {
177+
for (ApiTracer tracer : tracers) {
178+
tracer.requestSent();
179+
}
180+
}
181+
182+
@Override
183+
public void batchRequestSent(long elementCount, long requestSize) {
184+
for (ApiTracer tracer : tracers) {
185+
tracer.batchRequestSent(elementCount, requestSize);
186+
}
187+
}
188+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
31+
package com.google.api.gax.tracing;
32+
33+
import com.google.api.core.BetaApi;
34+
import com.google.api.core.InternalApi;
35+
import java.util.ArrayList;
36+
import java.util.Arrays;
37+
import java.util.List;
38+
39+
/**
40+
* A composite {@link ApiTracerFactory} that creates a {@link CompositeTracer} containing multiple
41+
* {@link ApiTracer} instances.
42+
*/
43+
@BetaApi
44+
@InternalApi
45+
public class CompositeTracerFactory implements ApiTracerFactory {
46+
private final List<ApiTracerFactory> factories;
47+
private final ApiTracerContext apiTracerContext;
48+
49+
public CompositeTracerFactory(ApiTracerFactory... factories) {
50+
this(Arrays.asList(factories), ApiTracerContext.empty());
51+
}
52+
53+
public CompositeTracerFactory(List<ApiTracerFactory> factories) {
54+
this(factories, ApiTracerContext.empty());
55+
}
56+
57+
private CompositeTracerFactory(
58+
List<ApiTracerFactory> factories, ApiTracerContext apiTracerContext) {
59+
this.factories = factories;
60+
this.apiTracerContext = apiTracerContext;
61+
}
62+
63+
@Override
64+
public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) {
65+
List<ApiTracer> tracers = new ArrayList<>(factories.size());
66+
for (int i = 0; i < factories.size(); i++) {
67+
ApiTracer subParent = getSubParent(parent, i);
68+
tracers.add(factories.get(i).newTracer(subParent, spanName, operationType));
69+
}
70+
return new CompositeTracer(tracers);
71+
}
72+
73+
@Override
74+
public ApiTracer newTracer(ApiTracer parent, ApiTracerContext context) {
75+
List<ApiTracer> tracers = new ArrayList<>(factories.size());
76+
for (int i = 0; i < factories.size(); i++) {
77+
ApiTracer subParent = getSubParent(parent, i);
78+
tracers.add(factories.get(i).newTracer(subParent, context));
79+
}
80+
return new CompositeTracer(tracers);
81+
}
82+
83+
private ApiTracer getSubParent(ApiTracer parent, int index) {
84+
if (parent instanceof CompositeTracer) {
85+
CompositeTracer compositeParent = (CompositeTracer) parent;
86+
if (index < compositeParent.getTracers().size()) {
87+
return compositeParent.getTracers().get(index);
88+
}
89+
}
90+
return parent;
91+
}
92+
93+
@Override
94+
public ApiTracerContext getApiTracerContext() {
95+
return apiTracerContext;
96+
}
97+
98+
@Override
99+
public ApiTracerFactory withContext(ApiTracerContext context) {
100+
List<ApiTracerFactory> updatedFactories = new ArrayList<>(factories.size());
101+
for (ApiTracerFactory factory : factories) {
102+
updatedFactories.add(factory.withContext(context));
103+
}
104+
return new CompositeTracerFactory(updatedFactories, apiTracerContext.merge(context));
105+
}
106+
}

gax-java/gax/src/test/java/com/google/api/gax/rpc/ClientContextTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1304,7 +1304,8 @@ void testCreate_withTracerFactoryReturningNullWithContext() throws IOException {
13041304
Mockito.doReturn(apiTracerFactory).when(settings).getTracerFactory();
13051305

13061306
ClientContext context = ClientContext.create(settings);
1307-
assertThat(context.getTracerFactory()).isSameInstanceAs(apiTracerFactory);
1307+
assertThat(context.getTracerFactory())
1308+
.isInstanceOf(com.google.api.gax.tracing.CompositeTracerFactory.class);
13081309
verify(apiTracerFactory, times(1)).withContext(Mockito.any());
13091310
}
13101311
}

gax-java/gax/src/test/java/com/google/api/gax/tracing/LoggingTracerFactoryTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
package com.google.api.gax.tracing;
3232

33+
import static org.junit.jupiter.api.Assertions.assertEquals;
3334
import static org.junit.jupiter.api.Assertions.assertNotNull;
3435
import static org.junit.jupiter.api.Assertions.assertTrue;
3536

gax-java/gax/src/test/java/com/google/api/gax/tracing/LoggingTracerTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030

3131
package com.google.api.gax.tracing;
3232

33-
3433
import com.google.api.gax.logging.TestLogger;
3534
import org.junit.jupiter.api.BeforeEach;
3635
import org.junit.jupiter.api.Test;

0 commit comments

Comments
 (0)