-
Notifications
You must be signed in to change notification settings - Fork 1.1k
impl(o11y): introduce http.response.status_code
#12312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
a49d06b
c3ed5e8
11868c1
1d87cd7
074ab67
12143fd
3a52abc
c2895af
8c8fdbd
50352e6
eaf0262
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,7 +43,17 @@ | |
|
|
||
| final class ObservabilityUtils { | ||
|
|
||
| private ObservabilityUtils() {} | ||
| /** Function to extract the status of the error as a string (defaults to gRPC canonical codes). */ | ||
| static String extractStatus(@Nullable Throwable error) { | ||
| return (String) extractStatus(error, ApiTracerContext.Transport.GRPC); | ||
| } | ||
|
|
||
| static Object extractStatus(@Nullable Throwable error, ApiTracerContext.Transport transport) { | ||
| if (transport == ApiTracerContext.Transport.HTTP) { | ||
| return extractHttpStatus(error); | ||
| } | ||
| return extractGrpcStatus(error); | ||
| } | ||
|
|
||
| /** Constant for redacted values. */ | ||
| private static final String REDACTED_VALUE = "REDACTED"; | ||
|
|
@@ -150,15 +160,8 @@ private static String redactSensitiveQueryValues(final String rawQuery) { | |
| return Joiner.on('&').join(redactedParams); | ||
| } | ||
|
|
||
| /** | ||
| * Function to extract the status of the error as a string. | ||
| * | ||
| * @param error the thrown throwable error | ||
| * @return the extracted status string | ||
| */ | ||
| static String extractStatus(@Nullable final Throwable error) { | ||
| private static String extractGrpcStatus(@Nullable Throwable error) { | ||
| final String statusString; | ||
|
|
||
| if (error == null) { | ||
| return StatusCode.Code.OK.toString(); | ||
| } else if (error instanceof CancellationException) { | ||
|
|
@@ -168,10 +171,45 @@ static String extractStatus(@Nullable final Throwable error) { | |
| } else { | ||
| statusString = StatusCode.Code.UNKNOWN.toString(); | ||
| } | ||
|
|
||
| return statusString; | ||
| } | ||
|
|
||
| private static Long extractHttpStatus(@Nullable Throwable error) { | ||
| if (error == null) { | ||
| return 200L; | ||
| } else if (error instanceof ApiException) { | ||
| Object transportCode = ((ApiException) error).getStatusCode().getTransportCode(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a getHttpStatusCode method. Can we use it directly? For example, let
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SG, done. |
||
| // HttpJsonStatusCode.getTransportCode() returns an Integer (HTTP status code). | ||
| // GrpcStatusCode returns a Status.Code enum, and FakeStatusCode (in tests) | ||
| // returns | ||
| // a StatusCode.Code enum. If it's not an Integer, we fall back to the mapped | ||
| // HTTP status code of the canonical code. | ||
| if (transportCode instanceof Integer) { | ||
| return ((Integer) transportCode).longValue(); | ||
| } else { | ||
| return (long) ((ApiException) error).getStatusCode().getCode().getHttpStatusCode(); | ||
| } | ||
| } | ||
| StatusCode.Code code = StatusCode.Code.UNKNOWN; | ||
| if (error instanceof CancellationException) { | ||
| code = StatusCode.Code.CANCELLED; | ||
| } | ||
| return (long) code.getHttpStatusCode(); | ||
| } | ||
|
|
||
| static void populateStatusAttributes( | ||
| Map<String, Object> attributes, | ||
| @Nullable Throwable error, | ||
| ApiTracerContext.Transport transport) { | ||
| if (transport == ApiTracerContext.Transport.GRPC) { | ||
| attributes.put( | ||
| ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, extractStatus(error, transport)); | ||
| } else if (transport == ApiTracerContext.Transport.HTTP) { | ||
| attributes.put( | ||
| ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE, extractStatus(error, transport)); | ||
| } | ||
| } | ||
|
|
||
| /** Function to extract the ErrorInfo payload from the error, if available */ | ||
| @Nullable | ||
| static ErrorInfo extractErrorInfo(@Nullable Throwable error) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,7 @@ | |
| import io.opentelemetry.api.trace.Tracer; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import java.util.concurrent.CancellationException; | ||
|
|
||
| /** An implementation of {@link ApiTracer} that uses OpenTelemetry to record traces. */ | ||
| @BetaApi | ||
|
|
@@ -133,7 +134,7 @@ public void attemptStarted(Object request, int attemptNumber) { | |
|
|
||
| @Override | ||
| public void attemptSucceeded() { | ||
| endAttempt(); | ||
| endAttempt(null); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -180,26 +181,34 @@ private long extractContentLength(java.util.Map<String, Object> headers) { | |
|
|
||
| @Override | ||
| public void attemptCancelled() { | ||
| endAttempt(); | ||
| endAttempt(new CancellationException()); | ||
| } | ||
|
|
||
| @Override | ||
| public void attemptFailedDuration(Throwable error, java.time.Duration delay) { | ||
| endAttempt(); | ||
| endAttempt(error); | ||
| } | ||
|
|
||
| @Override | ||
| public void attemptFailedRetriesExhausted(Throwable error) { | ||
| endAttempt(); | ||
| endAttempt(error); | ||
| } | ||
|
|
||
| @Override | ||
| public void attemptPermanentFailure(Throwable error) { | ||
| endAttempt(); | ||
| endAttempt(error); | ||
| } | ||
|
|
||
| private void endAttempt() { | ||
| private void endAttempt(Throwable error) { | ||
| if (attemptSpan != null) { | ||
| Map<String, Object> endAttributes = new HashMap<>(); | ||
| ObservabilityUtils.populateStatusAttributes( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we plan to reuse
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| endAttributes, error, this.apiTracerContext.transport()); | ||
|
|
||
| if (!endAttributes.isEmpty()) { | ||
| attemptSpan.setAllAttributes(ObservabilityUtils.toOtelAttributes(endAttributes)); | ||
| } | ||
|
|
||
| attemptSpan.end(); | ||
| attemptSpan = null; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would avoid type casting. Either use different methods for extracting grpc and http statuses, or return a StatusCode.Code and get different status with different getters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. In this case we were already defaulting to the gRPC status code, so I'm using
extractStatus(...).toString()