Skip to content

Commit 571a297

Browse files
committed
Merge remote-tracking branch 'origin/main' into observability/tracing-attr/status-code
2 parents 3c815d5 + e8b6d50 commit 571a297

6 files changed

Lines changed: 281 additions & 4 deletions

File tree

gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,10 @@ public class ObservabilityAttributes {
8787

8888
/** The HTTP status code of the request (e.g., 200, 404). */
8989
public static final String HTTP_RESPONSE_STATUS_ATTRIBUTE = "http.response.status_code";
90+
91+
/** The resend count of the request. Only used in HTTP transport. */
92+
public static final String HTTP_RESEND_COUNT_ATTRIBUTE = "http.request.resend_count";
93+
94+
/** The resend count of the request. Only used in gRPC transport. */
95+
public static final String GRPC_RESEND_COUNT_ATTRIBUTE = "gcp.grpc.resend_count";
9096
}

gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ static Attributes toOtelAttributes(Map<String, Object> attributes) {
106106
(k, v) -> {
107107
if (v instanceof String) {
108108
attributesBuilder.put(k, (String) v);
109+
} else if (v instanceof Long) {
110+
attributesBuilder.put(k, (Long) v);
109111
} else if (v instanceof Integer) {
110112
attributesBuilder.put(k, (long) (Integer) v);
111113
} else if (v instanceof Long) {

gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,26 @@ private void buildAttributes() {
106106

107107
@Override
108108
public void attemptStarted(Object request, int attemptNumber) {
109+
Map<String, Object> currentAttemptAttributes = new HashMap<>(this.attemptAttributes);
110+
111+
if (attemptNumber > 0) {
112+
ApiTracerContext.Transport transport = apiTracerContext.transport();
113+
if (transport == ApiTracerContext.Transport.GRPC) {
114+
currentAttemptAttributes.put(
115+
ObservabilityAttributes.GRPC_RESEND_COUNT_ATTRIBUTE, (long) attemptNumber);
116+
} else if (transport == ApiTracerContext.Transport.HTTP) {
117+
currentAttemptAttributes.put(
118+
ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE, (long) attemptNumber);
119+
}
120+
}
121+
109122
SpanBuilder spanBuilder = tracer.spanBuilder(attemptSpanName);
110123

111124
// Attempt spans are of the CLIENT kind
112125
spanBuilder.setSpanKind(SpanKind.CLIENT);
113126

114-
spanBuilder.setAllAttributes(ObservabilityUtils.toOtelAttributes(this.attemptAttributes));
127+
// Pass the combined attributes to the new SpanBuilder method
128+
spanBuilder.setAllAttributes(ObservabilityUtils.toOtelAttributes(currentAttemptAttributes));
115129

116130
this.attemptSpan = spanBuilder.startSpan();
117131
}

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ void testPopulateStatusAttributes_http_success() {
146146
Map<String, Object> attributes = new java.util.HashMap<>();
147147
ObservabilityUtils.populateStatusAttributes(attributes, null, ApiTracerContext.Transport.HTTP);
148148
assertThat(attributes)
149-
.containsEntry(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE, StatusCode.Code.OK.getHttpStatusCode());
149+
.containsEntry(
150+
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE,
151+
(long) StatusCode.Code.OK.getHttpStatusCode());
150152
}
151153

152154
@Test
@@ -170,8 +172,9 @@ public Object getTransportCode() {
170172
false);
171173
ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.HTTP);
172174
assertThat(attributes)
173-
.containsEntry(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE,
174-
StatusCode.Code.NOT_FOUND.getHttpStatusCode());
175+
.containsEntry(
176+
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE,
177+
(long) StatusCode.Code.NOT_FOUND.getHttpStatusCode());
175178
}
176179

177180
@Test

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

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,29 @@ void testAttemptSucceeded_http() {
133133
200L);
134134
}
135135

136+
@Test
137+
void testAttemptStarted_noRetryAttributes_grpc() {
138+
ApiTracerContext grpcContext =
139+
ApiTracerContext.newBuilder()
140+
.setLibraryMetadata(com.google.api.gax.rpc.LibraryMetadata.empty())
141+
.setTransport(ApiTracerContext.Transport.GRPC)
142+
.build();
143+
SpanTracer grpcTracer = new SpanTracer(tracer, grpcContext, ATTEMPT_SPAN_NAME);
144+
145+
// Initial attempt, attemptNumber is 0
146+
grpcTracer.attemptStarted(new Object(), 0);
147+
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
148+
verify(spanBuilder).setAllAttributes(attributesCaptor.capture());
149+
assertThat(attributesCaptor.getValue().asMap())
150+
.doesNotContainKey(
151+
io.opentelemetry.api.common.AttributeKey.longKey(
152+
ObservabilityAttributes.GRPC_RESEND_COUNT_ATTRIBUTE));
153+
assertThat(attributesCaptor.getValue().asMap())
154+
.doesNotContainKey(
155+
io.opentelemetry.api.common.AttributeKey.longKey(
156+
ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE));
157+
}
158+
136159
@Test
137160
void testAttemptFailed_grpc() {
138161
ApiTracerContext context =
@@ -173,6 +196,32 @@ public Object getTransportCode() {
173196
"NOT_FOUND");
174197
}
175198

199+
@Test
200+
void testAttemptStarted_retryAttributes_grpc() {
201+
ApiTracerContext grpcContext =
202+
ApiTracerContext.newBuilder()
203+
.setLibraryMetadata(com.google.api.gax.rpc.LibraryMetadata.empty())
204+
.setTransport(ApiTracerContext.Transport.GRPC)
205+
.build();
206+
SpanTracer grpcTracer = new SpanTracer(tracer, grpcContext, ATTEMPT_SPAN_NAME);
207+
208+
// N-th retry, attemptNumber is 5
209+
grpcTracer.attemptStarted(new Object(), 5);
210+
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
211+
verify(spanBuilder).setAllAttributes(attributesCaptor.capture());
212+
java.util.Map<io.opentelemetry.api.common.AttributeKey<?>, Object> capturedAttributes =
213+
attributesCaptor.getValue().asMap();
214+
assertThat(capturedAttributes)
215+
.containsEntry(
216+
io.opentelemetry.api.common.AttributeKey.longKey(
217+
ObservabilityAttributes.GRPC_RESEND_COUNT_ATTRIBUTE),
218+
5L);
219+
assertThat(capturedAttributes)
220+
.doesNotContainKey(
221+
io.opentelemetry.api.common.AttributeKey.longKey(
222+
ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE));
223+
}
224+
176225
@Test
177226
void testAttemptFailed_http() {
178227
ApiTracerContext context =
@@ -212,4 +261,55 @@ public Object getTransportCode() {
212261
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE),
213262
404L);
214263
}
264+
265+
@Test
266+
void testAttemptStarted_noRetryAttributes_http() {
267+
ApiTracerContext httpContext =
268+
ApiTracerContext.newBuilder()
269+
.setLibraryMetadata(com.google.api.gax.rpc.LibraryMetadata.empty())
270+
.setTransport(ApiTracerContext.Transport.HTTP)
271+
.build();
272+
SpanTracer httpTracer = new SpanTracer(tracer, httpContext, ATTEMPT_SPAN_NAME);
273+
274+
// Initial attempt, attemptNumber is 0
275+
httpTracer.attemptStarted(new Object(), 0);
276+
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
277+
verify(spanBuilder).setAllAttributes(attributesCaptor.capture());
278+
java.util.Map<io.opentelemetry.api.common.AttributeKey<?>, Object> capturedAttributes =
279+
attributesCaptor.getValue().asMap();
280+
assertThat(capturedAttributes)
281+
.doesNotContainKey(
282+
io.opentelemetry.api.common.AttributeKey.longKey(
283+
ObservabilityAttributes.GRPC_RESEND_COUNT_ATTRIBUTE));
284+
assertThat(capturedAttributes)
285+
.doesNotContainKey(
286+
io.opentelemetry.api.common.AttributeKey.longKey(
287+
ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE));
288+
}
289+
290+
@Test
291+
void testAttemptStarted_retryAttributes_http() {
292+
ApiTracerContext httpContext =
293+
ApiTracerContext.newBuilder()
294+
.setLibraryMetadata(com.google.api.gax.rpc.LibraryMetadata.empty())
295+
.setTransport(ApiTracerContext.Transport.HTTP)
296+
.build();
297+
SpanTracer httpTracer = new SpanTracer(tracer, httpContext, ATTEMPT_SPAN_NAME);
298+
299+
// N-th retry, attemptNumber is 5
300+
httpTracer.attemptStarted(new Object(), 5);
301+
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
302+
verify(spanBuilder).setAllAttributes(attributesCaptor.capture());
303+
java.util.Map<io.opentelemetry.api.common.AttributeKey<?>, Object> capturedAttributes =
304+
attributesCaptor.getValue().asMap();
305+
assertThat(capturedAttributes)
306+
.doesNotContainKey(
307+
io.opentelemetry.api.common.AttributeKey.longKey(
308+
ObservabilityAttributes.GRPC_RESEND_COUNT_ATTRIBUTE));
309+
assertThat(capturedAttributes)
310+
.containsEntry(
311+
io.opentelemetry.api.common.AttributeKey.longKey(
312+
ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE),
313+
5L);
314+
}
215315
}

java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,23 @@
3131
package com.google.showcase.v1beta1.it;
3232

3333
import static com.google.common.truth.Truth.assertThat;
34+
import static org.junit.Assert.assertThrows;
3435

36+
import com.google.api.client.http.javanet.NetHttpTransport;
37+
import com.google.api.gax.core.NoCredentialsProvider;
38+
import com.google.api.gax.retrying.RetrySettings;
39+
import com.google.api.gax.rpc.StatusCode;
40+
import com.google.api.gax.rpc.UnavailableException;
3541
import com.google.api.gax.tracing.ObservabilityAttributes;
3642
import com.google.api.gax.tracing.SpanTracer;
3743
import com.google.api.gax.tracing.SpanTracerFactory;
44+
import com.google.rpc.Status;
3845
import com.google.showcase.v1beta1.EchoClient;
3946
import com.google.showcase.v1beta1.EchoRequest;
47+
import com.google.showcase.v1beta1.EchoSettings;
4048
import com.google.showcase.v1beta1.it.util.TestClientInitializer;
49+
import com.google.showcase.v1beta1.stub.EchoStub;
50+
import com.google.showcase.v1beta1.stub.EchoStubSettings;
4151
import io.opentelemetry.api.GlobalOpenTelemetry;
4252
import io.opentelemetry.api.common.AttributeKey;
4353
import io.opentelemetry.api.trace.SpanKind;
@@ -213,4 +223,146 @@ void testTracing_successfulEcho_httpjson() throws Exception {
213223
.isEqualTo(200L);
214224
}
215225
}
226+
227+
@Test
228+
void testTracing_retry_grpc() throws Exception {
229+
final int attempts = 5;
230+
final StatusCode.Code statusCode = StatusCode.Code.UNAVAILABLE;
231+
// A custom EchoClient is used in this test because retries have jitter, and we cannot
232+
// predict the number of attempts that are scheduled for an RPC invocation otherwise.
233+
// The custom retrySettings limit to a set number of attempts before the call gives up.
234+
RetrySettings retrySettings =
235+
RetrySettings.newBuilder()
236+
.setTotalTimeout(org.threeten.bp.Duration.ofMillis(5000L))
237+
.setMaxAttempts(attempts)
238+
.build();
239+
240+
EchoStubSettings.Builder grpcEchoSettingsBuilder = EchoStubSettings.newBuilder();
241+
grpcEchoSettingsBuilder
242+
.echoSettings()
243+
.setRetrySettings(retrySettings)
244+
.setRetryableCodes(statusCode);
245+
EchoSettings grpcEchoSettings = EchoSettings.create(grpcEchoSettingsBuilder.build());
246+
grpcEchoSettings =
247+
grpcEchoSettings.toBuilder()
248+
.setCredentialsProvider(NoCredentialsProvider.create())
249+
.setTransportChannelProvider(EchoSettings.defaultGrpcTransportProviderBuilder().build())
250+
.setEndpoint("localhost:7469")
251+
.build();
252+
253+
SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk);
254+
255+
EchoStubSettings echoStubSettings =
256+
(EchoStubSettings)
257+
grpcEchoSettings.getStubSettings().toBuilder().setTracerFactory(tracingFactory).build();
258+
EchoStub stub = echoStubSettings.createStub();
259+
EchoClient grpcClient = EchoClient.create(stub);
260+
261+
EchoRequest echoRequest =
262+
EchoRequest.newBuilder()
263+
.setError(Status.newBuilder().setCode(statusCode.ordinal()).build())
264+
.build();
265+
266+
assertThrows(UnavailableException.class, () -> grpcClient.echo(echoRequest));
267+
268+
List<SpanData> spans = spanExporter.getFinishedSpanItems();
269+
assertThat(spans).hasSize(attempts); // Expect exactly one span for the successful retry
270+
271+
// This single span represents the successful retry, which has resend_count=1
272+
// The first attempt has no resend_count. The subsequent retries will have a resend_count,
273+
// starting from 1.
274+
List<Long> resendCounts =
275+
spans.stream()
276+
.map(
277+
span ->
278+
(Long)
279+
span.getAttributes()
280+
.asMap()
281+
.get(
282+
AttributeKey.longKey(
283+
ObservabilityAttributes.GRPC_RESEND_COUNT_ATTRIBUTE)))
284+
.filter(java.util.Objects::nonNull)
285+
.sorted()
286+
.collect(java.util.stream.Collectors.toList());
287+
288+
List<Long> expectedCounts =
289+
java.util.stream.LongStream.range(1, attempts)
290+
.boxed()
291+
.collect(java.util.stream.Collectors.toList());
292+
assertThat(resendCounts).containsExactlyElementsIn(expectedCounts).inOrder();
293+
}
294+
295+
@Test
296+
void testTracing_retry_httpjson() throws Exception {
297+
final int attempts = 5;
298+
final StatusCode.Code statusCode = StatusCode.Code.UNAVAILABLE;
299+
// A custom EchoClient is used in this test because retries have jitter, and we cannot
300+
// predict the number of attempts that are scheduled for an RPC invocation otherwise.
301+
// The custom retrySettings limit to a set number of attempts before the call gives up.
302+
RetrySettings retrySettings =
303+
RetrySettings.newBuilder()
304+
.setTotalTimeout(org.threeten.bp.Duration.ofMillis(5000L))
305+
.setMaxAttempts(attempts)
306+
.build();
307+
308+
EchoStubSettings.Builder httpJsonEchoSettingsBuilder = EchoStubSettings.newHttpJsonBuilder();
309+
httpJsonEchoSettingsBuilder
310+
.echoSettings()
311+
.setRetrySettings(retrySettings)
312+
.setRetryableCodes(statusCode);
313+
EchoSettings httpJsonEchoSettings = EchoSettings.create(httpJsonEchoSettingsBuilder.build());
314+
httpJsonEchoSettings =
315+
httpJsonEchoSettings.toBuilder()
316+
.setCredentialsProvider(NoCredentialsProvider.create())
317+
.setTransportChannelProvider(
318+
EchoSettings.defaultHttpJsonTransportProviderBuilder()
319+
.setHttpTransport(
320+
new NetHttpTransport.Builder().doNotValidateCertificate().build())
321+
.setEndpoint("http://localhost:7469")
322+
.build())
323+
.build();
324+
325+
SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk);
326+
327+
EchoStubSettings echoStubSettings =
328+
(EchoStubSettings)
329+
httpJsonEchoSettings.getStubSettings().toBuilder()
330+
.setTracerFactory(tracingFactory)
331+
.build();
332+
EchoStub stub = echoStubSettings.createStub();
333+
EchoClient httpClient = EchoClient.create(stub);
334+
335+
EchoRequest echoRequest =
336+
EchoRequest.newBuilder()
337+
.setError(Status.newBuilder().setCode(statusCode.ordinal()).build())
338+
.build();
339+
340+
assertThrows(UnavailableException.class, () -> httpClient.echo(echoRequest));
341+
342+
List<SpanData> spans = spanExporter.getFinishedSpanItems();
343+
assertThat(spans).hasSize(attempts); // Expect exactly one span for the successful retry
344+
345+
// This single span represents the successful retry, which has resend_count=1
346+
// The first attempt has no resend_count. The subsequent retries will have a resend_count,
347+
// starting from 1.
348+
List<Long> resendCounts =
349+
spans.stream()
350+
.map(
351+
span ->
352+
(Long)
353+
span.getAttributes()
354+
.asMap()
355+
.get(
356+
AttributeKey.longKey(
357+
ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE)))
358+
.filter(java.util.Objects::nonNull)
359+
.sorted()
360+
.collect(java.util.stream.Collectors.toList());
361+
362+
List<Long> expectedCounts =
363+
java.util.stream.LongStream.range(1, attempts)
364+
.boxed()
365+
.collect(java.util.stream.Collectors.toList());
366+
assertThat(resendCounts).containsExactlyElementsIn(expectedCounts).inOrder();
367+
}
216368
}

0 commit comments

Comments
 (0)