Skip to content
Closed
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 @@ -85,6 +85,9 @@ public class ObservabilityAttributes {
/** The url template of the request (e.g. /v1/{name}:access). */
public static final String URL_TEMPLATE_ATTRIBUTE = "url.template";

/** 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 @@ -39,10 +39,20 @@

class ObservabilityUtils {

/** Function to extract the status of the error as a string */
/** Function to extract the status of the error as a string (defaults to gRPC canonical codes). */
static String extractStatus(@Nullable Throwable error) {
final String statusString;
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);
}

private static String extractGrpcStatus(@Nullable Throwable error) {
final String statusString;
if (error == null) {
return StatusCode.Code.OK.toString();
} else if (error instanceof CancellationException) {
Expand All @@ -52,10 +62,41 @@ static String extractStatus(@Nullable 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();
if (transportCode instanceof Integer) {
return ((Integer) transportCode).longValue();
} else {
return (long) ((ApiException) error).getStatusCode().getCode().getHttpStatusCode();
}
} else {
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));
}
}

static Attributes toOtelAttributes(Map<String, Object> attributes) {
AttributesBuilder attributesBuilder = Attributes.builder();
if (attributes == null) {
Expand All @@ -69,6 +110,8 @@ static Attributes toOtelAttributes(Map<String, Object> attributes) {
attributesBuilder.put(k, (Long) v);
} else if (v instanceof Integer) {
attributesBuilder.put(k, (long) (Integer) v);
} else if (v instanceof Long) {
attributesBuilder.put(k, (Long) v);
}
});
return attributesBuilder.build();
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 @@ -131,31 +132,44 @@ public void attemptStarted(Object request, int attemptNumber) {

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

@Override
public void attemptCancelled() {
endAttempt();
endAttempt(new CancellationException());
}

@Override
public void attemptFailedDuration(Throwable error, java.time.Duration delay) {
endAttempt();
public void attemptFailedRetriesExhausted(Throwable error) {
endAttempt(error);
}

@Override
public void attemptFailedRetriesExhausted(Throwable error) {
endAttempt();
public void attemptPermanentFailure(Throwable error) {
endAttempt(error);
}

@Override
public void attemptPermanentFailure(Throwable error) {
endAttempt();
public void attemptFailedDuration(Throwable error, java.time.Duration delay) {
endAttempt(error);
}

private void endAttempt() {
@Override
public void attemptFailed(Throwable error, org.threeten.bp.Duration delay) {
endAttempt(error);
}

private void endAttempt(Throwable error) {
if (attemptSpan != null) {
Map<String, Object> endAttributes = new HashMap<>();
ObservabilityUtils.populateStatusAttributes(
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 @@ -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