Skip to content

Commit 320f92d

Browse files
impl(o11y): introduce http.response.status_code (#12312)
1 parent ac1f0f4 commit 320f92d

File tree

9 files changed

+362
-37
lines changed

9 files changed

+362
-37
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,14 @@ class GoldenSignalsMetricsTracer implements ApiTracer {
4949
private final Stopwatch clientRequestTimer;
5050
private final GoldenSignalsMetricsRecorder metricsRecorder;
5151
private final Map<String, Object> attributes;
52+
private final ApiTracerContext.Transport transport;
5253

5354
GoldenSignalsMetricsTracer(
5455
GoldenSignalsMetricsRecorder metricsRecorder, ApiTracerContext apiTracerContext) {
5556
this.clientRequestTimer = Stopwatch.createStarted();
5657
this.metricsRecorder = metricsRecorder;
5758
this.attributes = apiTracerContext.getMetricsAttributes();
59+
this.transport = apiTracerContext.transport();
5860
}
5961

6062
@VisibleForTesting
@@ -65,6 +67,7 @@ class GoldenSignalsMetricsTracer implements ApiTracer {
6567
this.clientRequestTimer = Stopwatch.createStarted(ticker);
6668
this.metricsRecorder = metricsRecorder;
6769
this.attributes = new HashMap<>(apiTracerContext.getMetricsAttributes());
70+
this.transport = apiTracerContext.transport();
6871
}
6972

7073
/**
@@ -88,7 +91,7 @@ public void operationCancelled() {
8891

8992
@Override
9093
public void operationFailed(Throwable error) {
91-
attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error));
94+
ObservabilityUtils.populateStatusAttributes(attributes, error, transport);
9295
metricsRecorder.recordOperationLatency(
9396
clientRequestTimer.elapsed(TimeUnit.NANOSECONDS) / 1_000_000_000.0, attributes);
9497
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ void recordActionableError(Throwable error) {
8080

8181
logContext.put(
8282
ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE,
83-
ObservabilityUtils.extractStatus(error));
83+
ObservabilityUtils.extractStatus(error).toString());
8484

8585
ErrorInfo errorInfo = ObservabilityUtils.extractErrorInfo(error);
8686
if (errorInfo != null) {

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ public void operationFailed(Throwable error) {
120120
if (operationFinished.getAndSet(true)) {
121121
throw new IllegalStateException(OPERATION_FINISHED_STATUS_MESSAGE);
122122
}
123-
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error));
123+
// Uses the GRPC status code representation.
124+
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error).toString());
124125
metricsRecorder.recordOperationLatency(
125126
operationTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
126127
metricsRecorder.recordOperationCount(1, attributes);
@@ -172,7 +173,7 @@ public void attemptCancelled() {
172173
*/
173174
@Override
174175
public void attemptFailedDuration(Throwable error, java.time.Duration delay) {
175-
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error));
176+
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error).toString());
176177
metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
177178
metricsRecorder.recordAttemptCount(1, attributes);
178179
}
@@ -196,7 +197,7 @@ public void attemptFailed(Throwable error, org.threeten.bp.Duration delay) {
196197
*/
197198
@Override
198199
public void attemptFailedRetriesExhausted(Throwable error) {
199-
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error));
200+
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error).toString());
200201
metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
201202
metricsRecorder.recordAttemptCount(1, attributes);
202203
}
@@ -210,7 +211,7 @@ public void attemptFailedRetriesExhausted(Throwable error) {
210211
*/
211212
@Override
212213
public void attemptPermanentFailure(Throwable error) {
213-
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error));
214+
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error).toString());
214215
metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
215216
metricsRecorder.recordAttemptCount(1, attributes);
216217
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ public class ObservabilityAttributes {
8888
/** Size of the response body in bytes. */
8989
public static final String HTTP_RESPONSE_BODY_SIZE = "http.response.body.size";
9090

91+
/** The HTTP status code of the request (e.g., 200, 404). */
92+
public static final String HTTP_RESPONSE_STATUS_ATTRIBUTE = "http.response.status_code";
93+
9194
/** The resend count of the request. Only used in HTTP transport. */
9295
public static final String HTTP_RESEND_COUNT_ATTRIBUTE = "http.request.resend_count";
9396

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

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,18 @@
4343

4444
final class ObservabilityUtils {
4545

46-
private ObservabilityUtils() {}
46+
/** Function to extract the status of the error as a canonical code. */
47+
static StatusCode.Code extractStatus(@Nullable Throwable error) {
48+
if (error == null) {
49+
return StatusCode.Code.OK;
50+
} else if (error instanceof CancellationException) {
51+
return StatusCode.Code.CANCELLED;
52+
} else if (error instanceof ApiException) {
53+
return ((ApiException) error).getStatusCode().getCode();
54+
} else {
55+
return StatusCode.Code.UNKNOWN;
56+
}
57+
}
4758

4859
/** Constant for redacted values. */
4960
private static final String REDACTED_VALUE = "REDACTED";
@@ -150,26 +161,17 @@ private static String redactSensitiveQueryValues(final String rawQuery) {
150161
return Joiner.on('&').join(redactedParams);
151162
}
152163

153-
/**
154-
* Function to extract the status of the error as a string.
155-
*
156-
* @param error the thrown throwable error
157-
* @return the extracted status string
158-
*/
159-
static String extractStatus(@Nullable final Throwable error) {
160-
final String statusString;
161-
162-
if (error == null) {
163-
return StatusCode.Code.OK.toString();
164-
} else if (error instanceof CancellationException) {
165-
statusString = StatusCode.Code.CANCELLED.toString();
166-
} else if (error instanceof ApiException) {
167-
statusString = ((ApiException) error).getStatusCode().getCode().toString();
164+
static void populateStatusAttributes(
165+
Map<String, Object> attributes,
166+
@Nullable Throwable error,
167+
ApiTracerContext.Transport transport) {
168+
StatusCode.Code code = extractStatus(error);
169+
if (transport == ApiTracerContext.Transport.HTTP) {
170+
attributes.put(
171+
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE, (long) code.getHttpStatusCode());
168172
} else {
169-
statusString = StatusCode.Code.UNKNOWN.toString();
173+
attributes.put(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, code.toString());
170174
}
171-
172-
return statusString;
173175
}
174176

175177
/** Function to extract the ErrorInfo payload from the error, if available */

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import io.opentelemetry.api.trace.Tracer;
3939
import java.util.HashMap;
4040
import java.util.Map;
41+
import java.util.concurrent.CancellationException;
4142

4243
/** An implementation of {@link ApiTracer} that uses OpenTelemetry to record traces. */
4344
@BetaApi
@@ -133,7 +134,7 @@ public void attemptStarted(Object request, int attemptNumber) {
133134

134135
@Override
135136
public void attemptSucceeded() {
136-
endAttempt();
137+
endAttempt(null);
137138
}
138139

139140
@Override
@@ -180,26 +181,34 @@ private long extractContentLength(java.util.Map<String, Object> headers) {
180181

181182
@Override
182183
public void attemptCancelled() {
183-
endAttempt();
184+
endAttempt(new CancellationException());
184185
}
185186

186187
@Override
187188
public void attemptFailedDuration(Throwable error, java.time.Duration delay) {
188-
endAttempt();
189+
endAttempt(error);
189190
}
190191

191192
@Override
192193
public void attemptFailedRetriesExhausted(Throwable error) {
193-
endAttempt();
194+
endAttempt(error);
194195
}
195196

196197
@Override
197198
public void attemptPermanentFailure(Throwable error) {
198-
endAttempt();
199+
endAttempt(error);
199200
}
200201

201-
private void endAttempt() {
202+
private void endAttempt(Throwable error) {
202203
if (attemptSpan != null) {
204+
Map<String, Object> endAttributes = new HashMap<>();
205+
ObservabilityUtils.populateStatusAttributes(
206+
endAttributes, error, this.apiTracerContext.transport());
207+
208+
if (!endAttributes.isEmpty()) {
209+
attemptSpan.setAllAttributes(ObservabilityUtils.toOtelAttributes(endAttributes));
210+
}
211+
203212
attemptSpan.end();
204213
attemptSpan = null;
205214
}

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

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,23 @@ void testExtractStatus_errorConversion_apiExceptions() {
4747
ApiException error =
4848
new ApiException(
4949
"fake_error", null, new FakeStatusCode(StatusCode.Code.INVALID_ARGUMENT), false);
50-
String errorCode = ObservabilityUtils.extractStatus(error);
51-
assertThat(errorCode).isEqualTo(StatusCode.Code.INVALID_ARGUMENT.toString());
50+
StatusCode.Code errorCode = ObservabilityUtils.extractStatus(error);
51+
assertThat(errorCode).isEqualTo(StatusCode.Code.INVALID_ARGUMENT);
5252
}
5353

5454
@Test
5555
void testExtractStatus_errorConversion_noError() {
5656
// test "OK", which corresponds to a "null" error.
57-
String successCode = ObservabilityUtils.extractStatus(null);
58-
assertThat(successCode).isEqualTo(StatusCode.Code.OK.toString());
57+
StatusCode.Code successCode = ObservabilityUtils.extractStatus(null);
58+
assertThat(successCode).isEqualTo(StatusCode.Code.OK);
5959
}
6060

6161
@Test
6262
void testExtractStatus_errorConversion_unknownException() {
6363
// test "UNKNOWN"
6464
Throwable unknownException = new RuntimeException();
65-
String errorCode2 = ObservabilityUtils.extractStatus(unknownException);
66-
assertThat(errorCode2).isEqualTo(StatusCode.Code.UNKNOWN.toString());
65+
StatusCode.Code errorCode2 = ObservabilityUtils.extractStatus(unknownException);
66+
assertThat(errorCode2).isEqualTo(StatusCode.Code.UNKNOWN);
6767
}
6868

6969
@Test
@@ -114,6 +114,106 @@ void testToOtelAttributes_shouldMapIntAttributes() {
114114
.isEqualTo((long) attribute2Value);
115115
}
116116

117+
@Test
118+
void testPopulateStatusAttributes_grpc_success() {
119+
Map<String, Object> attributes = new java.util.HashMap<>();
120+
ObservabilityUtils.populateStatusAttributes(attributes, null, ApiTracerContext.Transport.GRPC);
121+
assertThat(attributes)
122+
.containsEntry(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, "OK");
123+
}
124+
125+
@Test
126+
void testPopulateStatusAttributes_grpc_apiException() {
127+
Map<String, Object> attributes = new java.util.HashMap<>();
128+
ApiException error =
129+
new ApiException("fake_error", null, new FakeStatusCode(StatusCode.Code.NOT_FOUND), false);
130+
ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.GRPC);
131+
assertThat(attributes)
132+
.containsEntry(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, "NOT_FOUND");
133+
}
134+
135+
@Test
136+
void testPopulateStatusAttributes_grpc_cancellationException() {
137+
Map<String, Object> attributes = new java.util.HashMap<>();
138+
Throwable error = new java.util.concurrent.CancellationException();
139+
ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.GRPC);
140+
assertThat(attributes)
141+
.containsEntry(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, "CANCELLED");
142+
}
143+
144+
@Test
145+
void testPopulateStatusAttributes_http_success() {
146+
Map<String, Object> attributes = new java.util.HashMap<>();
147+
ObservabilityUtils.populateStatusAttributes(attributes, null, ApiTracerContext.Transport.HTTP);
148+
assertThat(attributes)
149+
.containsEntry(
150+
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE,
151+
(long) StatusCode.Code.OK.getHttpStatusCode());
152+
}
153+
154+
@Test
155+
void testPopulateStatusAttributes_http_apiExceptionWithIntegerTransportCode() {
156+
Map<String, Object> attributes = new java.util.HashMap<>();
157+
ApiException error =
158+
new ApiException(
159+
"fake_error",
160+
null,
161+
new com.google.api.gax.rpc.StatusCode() {
162+
@Override
163+
public Code getCode() {
164+
return Code.NOT_FOUND;
165+
}
166+
167+
@Override
168+
public Object getTransportCode() {
169+
return StatusCode.Code.NOT_FOUND.getHttpStatusCode();
170+
}
171+
},
172+
false);
173+
ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.HTTP);
174+
assertThat(attributes)
175+
.containsEntry(
176+
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE,
177+
(long) StatusCode.Code.NOT_FOUND.getHttpStatusCode());
178+
}
179+
180+
@Test
181+
void testPopulateStatusAttributes_http_apiExceptionWithNonIntegerTransportCode() {
182+
Map<String, Object> attributes = new java.util.HashMap<>();
183+
ApiException error =
184+
new ApiException(
185+
"fake_error",
186+
null,
187+
new com.google.api.gax.rpc.StatusCode() {
188+
@Override
189+
public Code getCode() {
190+
return Code.NOT_FOUND;
191+
}
192+
193+
@Override
194+
public Object getTransportCode() {
195+
return "Not Found";
196+
}
197+
},
198+
false);
199+
ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.HTTP);
200+
assertThat(attributes)
201+
.containsEntry(
202+
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE,
203+
(long) StatusCode.Code.NOT_FOUND.getHttpStatusCode());
204+
}
205+
206+
@Test
207+
void testPopulateStatusAttributes_http_cancellationException() {
208+
Map<String, Object> attributes = new java.util.HashMap<>();
209+
Throwable error = new java.util.concurrent.CancellationException();
210+
ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.HTTP);
211+
assertThat(attributes)
212+
.containsEntry(
213+
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE,
214+
(long) StatusCode.Code.CANCELLED.getHttpStatusCode());
215+
}
216+
117217
@Test
118218
void testToOtelAttributes_shouldReturnEmptyAttributes_nullInput() {
119219
assertThat(ObservabilityUtils.toOtelAttributes(null)).isEqualTo(Attributes.empty());

0 commit comments

Comments
 (0)