Transaction Payloads

Transactions are used to send tracing events to Sentry.

Transactions must be wrapped in an Envelope and therefore also be sent to the Envelope endpoint.

Anatomy

A Transaction is basically a Span combined with an Event. When using tracing with our SDKs you usually create a Span tree, the root node and therefore the whole tree is considered to be the Transaction. So technically a Transaction is just a Span. A Transaction must also have a contexts.trace (which contains some data of the Span) and some other properties that will be covered in the next section.

Transactions are Events enriched with Span data. We are only going to list here what is important for a Transaction.

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 SpanSentry SpanNotes
trace_idtrace_id
span_idspan_id
parent_span_idparent_span_idIf a span does not have a parent span ID, it is a root span. For a root span:
  • If there is an active Sentry transaction, add it to the transaction
  • If there is no active Sentry transaction, construct a new transaction from that span
  • namedescription
    name,attributes,kindop
    attributes,kind,statustagsThe OpenTelemetry Span Status message and span kind are set as tags on the Sentry span.
    attributes,statusstatusSee Span Status for more details
    start_time_unix_nanostart_timestamp
    end_time_unix_nanotimestamp
    eventSee Span Events for more details

    Currently there is no spec for how Span.link in OpenTelemetry should appear in Sentry.

    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.

    Copied
    // 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.

    Copied
    {
      "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).

    Copied
    {
      "trace_id": "1e57b752bc6e4544bbaa246cd1d05dee"
    }

    contexts.trace

    A Transaction has to have a specific contexts.trace entry that contains data from the Span.

    op
    Recommended. Short code identifying the type of operation the span is measuring.

    For more details, see Sentry's conventions around span operations.

    Copied
    {
      "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.

    Copied
    {
      "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 the timestamp value, otherwise the Span is discarded as invalid.

    Copied
    {
      "start_timestamp": "2011-05-02T17:41:36.242Z"
    }

    or:

    Copied
    {
      "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.

    Copied
    {
      "timestamp": "2011-05-02T17:41:36.955Z"
    }

    or:

    Copied
    {
      "timestamp": 1304358096.955
    }

    status
    Optional. Describes the status of the Span/Transaction.

    StateDescriptionHTTP status code equivalent
    okNot an error, returned on success200 and 2XX HTTP statuses
    cancelledThe operation was cancelled, typically by the caller499
    unknown or unknown_errorAn unknown error raised by APIs that don't return enough error information500
    invalid_argumentThe client specified an invalid argument400
    deadline_exceededThe deadline expired before the operation could succeed504
    not_foundContent was not found or request was denied for an entire class of users404
    already_existsThe entity attempted to be created already exists409
    permission_deniedThe caller doesn't have permission to execute the specified operation403
    resource_exhaustedThe resource has been exhausted e.g. per-user quota exhausted, file system out of space429
    failed_preconditionThe client shouldn't retry until the system state has been explicitly handled400
    abortedThe operation was aborted409
    out_of_rangeThe operation was attempted past the valid range e.g. seeking past the end of a file400
    unimplementedThe operation is not implemented or is not supported/enabled for this operation501
    internal_errorSome invariants expected by the underlying system have been broken. This code is reserved for serious errors500
    unavailableThe service is currently available e.g. as a transient condition503
    data_lossUnrecoverable data loss or corruption500
    unauthenticatedThe requester doesn't have valid authentication credentials for the operation401
    Copied
    {
      "status": "ok"
    }

    tags
    Optional. A map or list of tags for this event. Each tag must be less than 200 characters.

    Copied
    {
      "tags": {
        "ios_version": "4.0",
        "context": "production"
      }
    }

    Examples

    Copied
    {
      "contexts": {
        "trace": {
          "op": "navigation",
          "description": "User clicked on <Link />",
          "trace_id": "743ad8bbfdd84e99bc38b4729e2864de",
          "span_id": "a0cfbde2bdff3adc",
          "status": "ok",
          "parent_span_id": "99659d76b7cdae94"
        }
      }
    }

    spans
    Recommended. A list of Spans.

    Copied
    {
      "spans": [
        {
          "start_timestamp": 1588601261.481961,
          "description": "GET /sockjs-node/info",
          "tags": {
            "http.status_code": "200"
          },
          "timestamp": 1588601261.488901,
          "parent_span_id": "b0e6f15b45c36b12",
          "trace_id": "1e57b752bc6e4544bbaa246cd1d05dee",
          "op": "http",
          "data": {
            "url": "http://localhost:8080/sockjs-node/info?t=1588601703755",
            "status_code": 200,
            "type": "xhr",
            "method": "GET"
          },
          "span_id": "b01b9f6349558cd1"
        },
        {
          "start_timestamp": 1588601261.535386,
          "description": "Vue <App>",
          "timestamp": 1588601261.544196,
          "parent_span_id": "9312d0d18bf51736",
          "trace_id": "1e57b752bc6e4544bbaa246cd1d05dee",
          "op": "update",
          "span_id": "b980d4dec78d7344"
        }
      ]
    }

    measurements
    Optional. An object containing standard/custom measurements with keys signifying the name of the measurement.

    Standard measurement keys currently supported are from the following list taken from here.

    Copied
    [
      // web
      "fp",
      "fcp",
      "lcp",
      "fid",
      "cls",
      "ttfb",
      "ttfb.requesttime",
      // mobile
      "app_start_cold",
      "app_start_warm",
      "frames_total",
      "frames_slow",
      "frames_frozen",
      // react native
      "stall_count",
      "stall_total_time",
      "stall_longest_time"
    ]

    For the well-known measurements listed above, Sentry automatically infers units. Custom measurements need units to be specified, defaulting to "none" if missing. The full list of supported units is specified on Relay's MetricUnit. Sentry's event ingestion supports arbitrary custom units, but many SDKs will not expose a generic user-defined unit interface.

    Copied
    {
      "measurements": {
        "lcp": { "value": 100 },
        "fp": { "value": 123 },
        "my.custom.metric": { "value": 456, "unit": "millisecond" }
      }
    }

    Transaction Annotations

    transaction_info
    Recommended. Additional information about the name of the transaction.

    Copied
    {
      "transaction_info": {
        "source": "url"
      }
    }

    transaction_info.source
    Required. This information is required by dynamic sampling. Contains information about how the name of the transaction was determined. This will be used by the server to decide whether or not to scrub identifiers from the transaction name, or replace the entire name with a placeholder. The source should only be set by integrations and not by developers directly.

    SourceDescription
    Examples
    customUser-defined name, see setTransactionName()my_transaction
    urlRaw URL, potentially containing identifiers./auth/login/john123/
    GET /auth/login/john123/
    routeParametrized URL / route/auth/login/:userId/
    GET /auth/login/{user}/
    viewName of the view handling the request.UserListView
    componentNamed after a software component, such as a function or class name.AuthLogin.login
    LoginActivity.login_button
    taskName of a background task (e.g. a Celery task)sentry.tasks.do_something
    unknownValue set by Relay for legacy SDKs. SDKs must not set this value explicitly.
    You can edit this page on GitHub.