OpenTelemetry Support
This page is under active development. Specifications are not final and subject to change.
"Proof is in the progress" - Drake
This document details Sentry's work in integrating and supporting OpenTelemetry, the open standard for metrics, traces and logs. In particular, it focuses on the integration between Sentry's performance monitoring product and OpenTelemetry's tracing spec.
Background
When Sentry performance monitoring was initially introduced, OpenTelemetry was in early stages. This lead to us adopt a slightly different model from OpenTelemetry, notably we have this concept of transactions that OpenTelemetry does not have. We've described this, and some more historical background, in our performance monitoring research document.
TODO: Add history about OpenTelemetry Sentry Exporter: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/sentryexporter
Approach
TODO: Talk about the approach we are using, based on Matt's hackweek project - https://github.com/getsentry/sentry-ruby/pull/1876
Transaction Protocol
TODO: Talk about generating transactions
Span Protocol
Below describe the transformations between an OpenTelemetry span and a Sentry Span. Related: the interface for a Sentry Span, the Relay spec for a Sentry Span and the spec for an OpenTelemetry span.
This is based on a mapping done as part of work on the OpenTelemetry Sentry Exporter.
OpenTelemetry Span | Sentry Span | Notes |
---|---|---|
Span.trace_id | Span.trace_id | |
Span.span_id | Span.span_id | |
Span.parent_span_id | Span.parent_span_id | If a span does not have a parent span ID, it is a root span. For a root span: |
Span.nameSpan.attributesSpan.kind | Span.description | The span description is decided using OpenTelemetry Semantic Conventions. Generally, the OpenTelemetrySpan.namemaps to a SentrySpan.description |
Span.nameSpan.attributesSpan.kind | Span.op | |
Span.attributesSpan.kindSpan.Status | Span.tags | The OpenTelemetry Span Status message and span kind are set as tags on the Sentry span. |
Span.attributesSpan.Status | Span.status | See Span Status for more details |
Span.start_time_unix_nano | Span.start_timestamp | |
Span.end_time_unix_nano | Span.timestamp |
Span Status
In OpenTelemetry, Span Status is an enum of 3 values, while Sentry's Span Status is an enum of 17 values that map to the GRPC status codes. Each of the Sentry Span Status codes also map to HTTP codes. Sentry adopted it's Span Status spec from OpenTelemetry, who used the GRPC status code spec, but later on changed to the current spec it uses today.
To map from OpenTelemetry Span Status to, you need to rely on both OpenTelemetry Span Status and Span attributes. This approach was adapted from a PR by GH user @anguisa to the OpenTelemetry Sentry Exporter.
// OpenTelemetry span status can be Unset, Ok, Error. HTTP and Grpc codes contained in tags can make it more detailed.
// canonicalCodesHTTPMap maps some HTTP codes to Sentry's span statuses. See possible mapping in https://develop.sentry.dev/sdk/event-payloads/span/
var canonicalCodesHTTPMap = map[string]sentry.SpanStatus{
"400": sentry.SpanStatusFailedPrecondition, // SpanStatusInvalidArgument, SpanStatusOutOfRange
"401": sentry.SpanStatusUnauthenticated,
"403": sentry.SpanStatusPermissionDenied,
"404": sentry.SpanStatusNotFound,
"409": sentry.SpanStatusAborted, // SpanStatusAlreadyExists
"429": sentry.SpanStatusResourceExhausted,
"499": sentry.SpanStatusCanceled,
"500": sentry.SpanStatusInternalError, // SpanStatusDataLoss, SpanStatusUnknown
"501": sentry.SpanStatusUnimplemented,
"503": sentry.SpanStatusUnavailable,
"504": sentry.SpanStatusDeadlineExceeded,
}
// canonicalCodesGrpcMap maps some GRPC codes to Sentry's span statuses. See description in grpc documentation.
var canonicalCodesGrpcMap = map[string]sentry.SpanStatus{
"1": sentry.SpanStatusCanceled,
"2": sentry.SpanStatusUnknown,
"3": sentry.SpanStatusInvalidArgument,
"4": sentry.SpanStatusDeadlineExceeded,
"5": sentry.SpanStatusNotFound,
"6": sentry.SpanStatusAlreadyExists,
"7": sentry.SpanStatusPermissionDenied,
"8": sentry.SpanStatusResourceExhausted,
"9": sentry.SpanStatusFailedPrecondition,
"10": sentry.SpanStatusAborted,
"11": sentry.SpanStatusOutOfRange,
"12": sentry.SpanStatusUnimplemented,
"13": sentry.SpanStatusInternalError,
"14": sentry.SpanStatusUnavailable,
"15": sentry.SpanStatusDataLoss,
"16": sentry.SpanStatusUnauthenticated,
}
code := spanStatus.Code()
if code < 0 || int(code) > 2 {
return sentry.SpanStatusUnknown, fmt.Sprintf("error code %d", code)
}
httpCode, foundHTTPCode := tags["http.status_code"]
grpcCode, foundGrpcCode := tags["rpc.grpc.status_code"]
var sentryStatus sentry.SpanStatus
switch {
case code == 1 || code == 0:
sentryStatus = sentry.SpanStatusOK
case foundHTTPCode:
httpStatus, foundHTTPStatus := canonicalCodesHTTPMap[httpCode]
switch {
case foundHTTPStatus:
sentryStatus = httpStatus
default:
sentryStatus = sentry.SpanStatusUnknown
}
case foundGrpcCode:
grpcStatus, foundGrpcStatus := canonicalCodesGrpcMap[grpcCode]
switch {
case foundGrpcStatus:
sentryStatus = grpcStatus
default:
sentryStatus = sentry.SpanStatusUnknown
}
default:
sentryStatus = sentry.SpanStatusUnknown
}
return sentryStatus
- TODO: Talk about generating stuff (breadcrumbs/error events) from Span Events: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/48ee3288c030ed32374125190a8abf7ced1a4238/exporter/sentryexporter/sentry_exporter.go#L167-L202
Semantic Conventions
Using the Span.Name, Span.Attributes, and Span.Kind of OpenTelemetry Spans, we can generate ops, descriptions, and tags. This exact mapping is described below.
TODO: Talk about what we do here: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/48ee3288c030ed32374125190a8abf7ced1a4238/exporter/sentryexporter/sentry_exporter.go#L226-L267
SDK Spec
- SpanProcessor: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#span-processor
- Propogator: https://opentelemetry.io/docs/reference/specification/context/api-propagators/