Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@ class GoldenSignalsMetricsTracer implements ApiTracer {
private final Stopwatch clientRequestTimer;
private final GoldenSignalsMetricsRecorder metricsRecorder;
private final Map<String, Object> attributes;
private final ApiTracerContext.Transport transport;

GoldenSignalsMetricsTracer(
GoldenSignalsMetricsRecorder metricsRecorder, ApiTracerContext apiTracerContext) {
this.clientRequestTimer = Stopwatch.createStarted();
this.metricsRecorder = metricsRecorder;
this.attributes = apiTracerContext.getMetricsAttributes();
this.transport = apiTracerContext.transport();
}

@VisibleForTesting
Expand All @@ -65,6 +67,7 @@ class GoldenSignalsMetricsTracer implements ApiTracer {
this.clientRequestTimer = Stopwatch.createStarted(ticker);
this.metricsRecorder = metricsRecorder;
this.attributes = new HashMap<>(apiTracerContext.getMetricsAttributes());
this.transport = apiTracerContext.transport();
}

/**
Expand All @@ -88,7 +91,7 @@ public void operationCancelled() {

@Override
public void operationFailed(Throwable error) {
attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error));
ObservabilityUtils.populateStatusAttributes(attributes, error, transport);
metricsRecorder.recordOperationLatency(
clientRequestTimer.elapsed(TimeUnit.NANOSECONDS) / 1_000_000_000.0, attributes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ void recordActionableError(Throwable error) {

logContext.put(
ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE,
ObservabilityUtils.extractStatus(error));
ObservabilityUtils.extractStatus(error).toString());

ErrorInfo errorInfo = ObservabilityUtils.extractErrorInfo(error);
if (errorInfo != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ public void operationFailed(Throwable error) {
if (operationFinished.getAndSet(true)) {
throw new IllegalStateException(OPERATION_FINISHED_STATUS_MESSAGE);
}
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error));
// Uses the GRPC status code representation.
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error).toString());
metricsRecorder.recordOperationLatency(
operationTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
metricsRecorder.recordOperationCount(1, attributes);
Expand Down Expand Up @@ -172,7 +173,7 @@ public void attemptCancelled() {
*/
@Override
public void attemptFailedDuration(Throwable error, java.time.Duration delay) {
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error));
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error).toString());
metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
metricsRecorder.recordAttemptCount(1, attributes);
}
Expand All @@ -196,7 +197,7 @@ public void attemptFailed(Throwable error, org.threeten.bp.Duration delay) {
*/
@Override
public void attemptFailedRetriesExhausted(Throwable error) {
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error));
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error).toString());
metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
metricsRecorder.recordAttemptCount(1, attributes);
}
Expand All @@ -210,7 +211,7 @@ public void attemptFailedRetriesExhausted(Throwable error) {
*/
@Override
public void attemptPermanentFailure(Throwable error) {
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error));
attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error).toString());
metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes);
metricsRecorder.recordAttemptCount(1, attributes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ public class ObservabilityAttributes {
/** Size of the response body in bytes. */
public static final String HTTP_RESPONSE_BODY_SIZE = "http.response.body.size";

/** The HTTP status code of the request (e.g., 200, 404). */
public static final String HTTP_RESPONSE_STATUS_ATTRIBUTE = "http.response.status_code";

/** The resend count of the request. Only used in HTTP transport. */
public static final String HTTP_RESEND_COUNT_ATTRIBUTE = "http.request.resend_count";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,18 @@

final class ObservabilityUtils {

private ObservabilityUtils() {}
/** Function to extract the status of the error as a canonical code. */
static StatusCode.Code extractStatus(@Nullable Throwable error) {
if (error == null) {
return StatusCode.Code.OK;
} else if (error instanceof CancellationException) {
return StatusCode.Code.CANCELLED;
} else if (error instanceof ApiException) {
return ((ApiException) error).getStatusCode().getCode();
} else {
return StatusCode.Code.UNKNOWN;
}
}

/** Constant for redacted values. */
private static final String REDACTED_VALUE = "REDACTED";
Expand Down Expand Up @@ -150,26 +161,17 @@ 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) {
final String statusString;

if (error == null) {
return StatusCode.Code.OK.toString();
} else if (error instanceof CancellationException) {
statusString = StatusCode.Code.CANCELLED.toString();
} else if (error instanceof ApiException) {
statusString = ((ApiException) error).getStatusCode().getCode().toString();
static void populateStatusAttributes(
Map<String, Object> attributes,
@Nullable Throwable error,
ApiTracerContext.Transport transport) {
StatusCode.Code code = extractStatus(error);
if (transport == ApiTracerContext.Transport.HTTP) {
attributes.put(
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE, (long) code.getHttpStatusCode());
} else {
statusString = StatusCode.Code.UNKNOWN.toString();
attributes.put(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, code.toString());
}

return statusString;
}

/** Function to extract the ErrorInfo payload from the error, if available */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -133,7 +134,7 @@ public void attemptStarted(Object request, int attemptNumber) {

@Override
public void attemptSucceeded() {
endAttempt();
endAttempt(null);
}

@Override
Expand Down Expand Up @@ -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(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we plan to reuse populateStatusAttributes in GoldenSignalsMetricsTracer? Otherwise it seems a little redundant to have another layer. We can extract status and set it to the Span directly here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

populateStatusAttributes does the trick here too. Updated

endAttributes, error, this.apiTracerContext.transport());

if (!endAttributes.isEmpty()) {
attemptSpan.setAllAttributes(ObservabilityUtils.toOtelAttributes(endAttributes));
}

attemptSpan.end();
attemptSpan = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,23 @@ void testExtractStatus_errorConversion_apiExceptions() {
ApiException error =
new ApiException(
"fake_error", null, new FakeStatusCode(StatusCode.Code.INVALID_ARGUMENT), false);
String errorCode = ObservabilityUtils.extractStatus(error);
assertThat(errorCode).isEqualTo(StatusCode.Code.INVALID_ARGUMENT.toString());
StatusCode.Code errorCode = ObservabilityUtils.extractStatus(error);
assertThat(errorCode).isEqualTo(StatusCode.Code.INVALID_ARGUMENT);
}

@Test
void testExtractStatus_errorConversion_noError() {
// test "OK", which corresponds to a "null" error.
String successCode = ObservabilityUtils.extractStatus(null);
assertThat(successCode).isEqualTo(StatusCode.Code.OK.toString());
StatusCode.Code successCode = ObservabilityUtils.extractStatus(null);
assertThat(successCode).isEqualTo(StatusCode.Code.OK);
}

@Test
void testExtractStatus_errorConversion_unknownException() {
// test "UNKNOWN"
Throwable unknownException = new RuntimeException();
String errorCode2 = ObservabilityUtils.extractStatus(unknownException);
assertThat(errorCode2).isEqualTo(StatusCode.Code.UNKNOWN.toString());
StatusCode.Code errorCode2 = ObservabilityUtils.extractStatus(unknownException);
assertThat(errorCode2).isEqualTo(StatusCode.Code.UNKNOWN);
}

@Test
Expand Down Expand Up @@ -114,6 +114,106 @@ void testToOtelAttributes_shouldMapIntAttributes() {
.isEqualTo((long) attribute2Value);
}

@Test
void testPopulateStatusAttributes_grpc_success() {
Map<String, Object> attributes = new java.util.HashMap<>();
ObservabilityUtils.populateStatusAttributes(attributes, null, ApiTracerContext.Transport.GRPC);
assertThat(attributes)
.containsEntry(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, "OK");
}

@Test
void testPopulateStatusAttributes_grpc_apiException() {
Map<String, Object> attributes = new java.util.HashMap<>();
ApiException error =
new ApiException("fake_error", null, new FakeStatusCode(StatusCode.Code.NOT_FOUND), false);
ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.GRPC);
assertThat(attributes)
.containsEntry(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, "NOT_FOUND");
}

@Test
void testPopulateStatusAttributes_grpc_cancellationException() {
Map<String, Object> attributes = new java.util.HashMap<>();
Throwable error = new java.util.concurrent.CancellationException();
ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.GRPC);
assertThat(attributes)
.containsEntry(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, "CANCELLED");
}

@Test
void testPopulateStatusAttributes_http_success() {
Map<String, Object> attributes = new java.util.HashMap<>();
ObservabilityUtils.populateStatusAttributes(attributes, null, ApiTracerContext.Transport.HTTP);
assertThat(attributes)
.containsEntry(
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE,
(long) StatusCode.Code.OK.getHttpStatusCode());
}

@Test
void testPopulateStatusAttributes_http_apiExceptionWithIntegerTransportCode() {
Map<String, Object> attributes = new java.util.HashMap<>();
ApiException error =
new ApiException(
"fake_error",
null,
new com.google.api.gax.rpc.StatusCode() {
@Override
public Code getCode() {
return Code.NOT_FOUND;
}

@Override
public Object getTransportCode() {
return StatusCode.Code.NOT_FOUND.getHttpStatusCode();
}
},
false);
ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.HTTP);
assertThat(attributes)
.containsEntry(
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE,
(long) StatusCode.Code.NOT_FOUND.getHttpStatusCode());
}

@Test
void testPopulateStatusAttributes_http_apiExceptionWithNonIntegerTransportCode() {
Map<String, Object> attributes = new java.util.HashMap<>();
ApiException error =
new ApiException(
"fake_error",
null,
new com.google.api.gax.rpc.StatusCode() {
@Override
public Code getCode() {
return Code.NOT_FOUND;
}

@Override
public Object getTransportCode() {
return "Not Found";
}
},
false);
ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.HTTP);
assertThat(attributes)
.containsEntry(
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE,
(long) StatusCode.Code.NOT_FOUND.getHttpStatusCode());
}

@Test
void testPopulateStatusAttributes_http_cancellationException() {
Map<String, Object> attributes = new java.util.HashMap<>();
Throwable error = new java.util.concurrent.CancellationException();
ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.HTTP);
assertThat(attributes)
.containsEntry(
ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE,
(long) StatusCode.Code.CANCELLED.getHttpStatusCode());
}

@Test
void testToOtelAttributes_shouldReturnEmptyAttributes_nullInput() {
assertThat(ObservabilityUtils.toOtelAttributes(null)).isEqualTo(Attributes.empty());
Expand Down
Loading
Loading