Span Interface
The Span Interface specifies a series of timed application events that have a start and end time.
A Transaction may contain zero or more Spans in an array attribute named spans
. Spans in the list don't have to be ordered, they will be ordered by start / end time on the Server.
While Span attributes will be normalized on the server, a Span is most useful when it includes at least an op
and description
.
Attributes
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 |
---|---|---|
trace_id | trace_id | |
span_id | span_id | |
parent_span_id | parent_span_id | If a span does not have a parent span ID, it is a root span. For a root span: |
name,attributes,kind | description | The span description is decided using OpenTelemetry Semantic Conventions. Generally, the OpenTelemetrynamemaps to a Sentrydescription |
name,attributes,kind | op | |
attributes,kind,status | tags | The OpenTelemetry Span Status message and span kind are set as tags on the Sentry span. |
attributes,status | status | See Span Status for more details |
start_time_unix_nano | start_timestamp | |
end_time_unix_nano | timestamp | |
event | See Span Events for more details |
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
Span Events
OpenTelemetry, has the concept of Span Events. As per the spec:
An event is a human-readable message on a span that represents “something happening” during it’s lifetime
In Sentry, we have two options for how to treat span events. First, we can add them as breadcrumbs to the transaction the span belongs to. Second, we can create an artificial "point-in-time" span (a span with 0 duration), and add it to the span tree. TODO on what approach we take here.
In the special case that the span event is an exception span, where the name
of the span event is exception
, we also have the possibility of generating a Sentry error from an exception. In this case, we can create this exception based on the attributes of an event, which include the error message and stacktrace. This exception can also inherit all other attributes of the span event + span as tags on the event.
In the OpenTelemetry Sentry exporter, we've used this strategy to generate Sentry errors.
tags
- Optional. A map or list of tags for this event. Each tag must be less than 200 characters.
{
"tags": {
"ios_version": "4.0",
"context": "production"
}
}
trace_id
:- Required. Determines which
trace
the Span belongs to. The value should be 16 random bytes encoded as a hex string (32 characters long).
{
"trace_id": "1e57b752bc6e4544bbaa246cd1d05dee"
}
op
- Recommended. Short code identifying the type of operation the span is measuring.
For more details, see Sentry's conventions around span operations.
{
"op": "db.query"
}
description
- Optional. Longer description of the span's operation, which uniquely identifies the span but is consistent across instances of the span.
{
"description": "SELECT * FROM users WHERE last_active < DATE_SUB(CURRENT_DATE, INTERVAL 1 YEAR)`"
}
start_timestamp
- Required. A timestamp representing when the measuring started. The
format is either a string as defined in RFC
3339 or a numeric (integer or float)
value representing the number of seconds that have elapsed since the Unix
epoch. The
start_timestamp
value must be less than or equal to thetimestamp
value, otherwise the Span is discarded as invalid.
{
"start_timestamp": "2011-05-02T17:41:36.242Z"
}
or:
{
"start_timestamp": 1304358096.242
}
timestamp
- Required. A timestamp representing when the measuring finished. The format is either a string as defined in RFC 3339 or a numeric (integer or float) value representing the number of seconds that have elapsed since the Unix epoch.
{
"timestamp": "2011-05-02T17:41:36.955Z"
}
or:
{
"timestamp": 1304358096.955
}
status
- Optional. Describes the
status
of the Span/Transaction.
State | Description | HTTP status code equivalent |
---|---|---|
ok | Not an error, returned on success | 200 and 2XX HTTP statuses |
cancelled | The operation was cancelled, typically by the caller | 499 |
unknown or unknown_error | An unknown error raised by APIs that don't return enough error information | 500 |
invalid_argument | The client specified an invalid argument | 400 |
deadline_exceeded | The deadline expired before the operation could succeed | 504 |
not_found | Content was not found or request was denied for an entire class of users | 404 |
already_exists | The entity attempted to be created already exists | 409 |
permission_denied | The caller doesn't have permission to execute the specified operation | 403 |
resource_exhausted | The resource has been exhausted e.g. per-user quota exhausted, file system out of space | 429 |
failed_precondition | The client shouldn't retry until the system state has been explicitly handled | 400 |
aborted | The operation was aborted | 409 |
out_of_range | The operation was attempted past the valid range e.g. seeking past the end of a file | 400 |
unimplemented | The operation is not implemented or is not supported/enabled for this operation | 501 |
internal_error | Some invariants expected by the underlying system have been broken. This code is reserved for serious errors | 500 |
unavailable | The service is currently available e.g. as a transient condition | 503 |
data_loss | Unrecoverable data loss or corruption | 500 |
unauthenticated | The requester doesn't have valid authentication credentials for the operation | 401 |
{
"status": "ok"
}
tags
- Optional. A map or list of tags for this event. Each tag must be less than 200 characters.
{
"tags": {
"ios_version": "4.0",
"context": "production"
}
}
data
- Optional. Arbitrary data associated with this Span.
{
"data": {
"url": "http://localhost:8080/sockjs-node/info?t=1588601703755",
"status_code": 200,
"type": "xhr",
"method": "GET"
}
}
Examples
The following example illustrates the Span as part of the Transaction and omits other attributes for simplicity.
{
"spans": [
{
"trace_id": "1e57b752bc6e4544bbaa246cd1d05dee",
"span_id": "b01b9f6349558cd1",
"parent_span_id": "b0e6f15b45c36b12",
"op": "http",
"description": "GET /sockjs-node/info",
"status": "ok",
"start_timestamp": 1588601261.481961,
"timestamp": 1588601261.488901,
"tags": {
"http.status_code": "200"
},
"data": {
"url": "http://localhost:8080/sockjs-node/info?t=1588601703755",
"status_code": 200,
"type": "xhr",
"method": "GET"
}
},
{
"trace_id": "1e57b752bc6e4544bbaa246cd1d05dee",
"span_id": "b980d4dec78d7344",
"parent_span_id": "9312d0d18bf51736",
"op": "update",
"description": "Vue <App>",
"start_timestamp": 1588601261.535386,
"timestamp": 1588601261.544196
}
]
}