Skip to content
This repository has been archived by the owner on Aug 14, 2024. It is now read-only.

Commit

Permalink
W3C traceparent (#1136)
Browse files Browse the repository at this point in the history
Co-authored-by: Lukas Stracke <lukas.stracke@sentry.io>
  • Loading branch information
cleptric and Lms24 authored Apr 9, 2024
1 parent 05b139f commit 1a841fd
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 18 deletions.
18 changes: 10 additions & 8 deletions src/docs/sdk/distributed-tracing/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@
title: "Distributed Tracing"
---

This document describes how a SDK should propagate information between different services to connect all telemetry (errors, profiles, replays, transaction) from those services into one trace.
This document describes how a SDK should propagate information between different services to connect all telemetry (errors, profiles, replays, transaction, check-ins, ...) from those services into one trace.

For an overview see [Distributed Tracing](https://docs.sentry.io/product/performance/distributed-tracing/) in the product docs.

Sentry uses two containers to hold trace information [`sentry-trace`](/sdk/performance/#header-sentry-trace) and [`baggage`](/sdk/performance/dynamic-sampling-context/#baggage).
Sentry uses three containers to hold trace information [`sentry-trace`](/sdk/performance/#header-sentry-trace), [`traceparent`](/sdk/performance/#header-traceparent) and [`baggage`](/sdk/performance/dynamic-sampling-context/#baggage).

With these containers you can propagate a trace to a down-stream service. By either
- adding `sentry-trace` and `baggage` HTTP headers (when doing outgoing HTTP requests),
- adding `sentry-trace` and `baggage` as meta data (when putting tasks into a queue, details are specific to the queue you want to support), or
- setting environment variables (when calling another process). In this case the env variables should be called `SENTRY_TRACE` and `SENTRY_BAGGAGE`.
- adding `sentry-trace`, `traceparent` and `baggage` HTTP headers (when doing outgoing HTTP requests),
- adding `sentry-trace`, `traceparent` and `baggage` as meta data (when putting tasks into a queue, details are specific to the queue you want to support), or
- setting environment variables (when calling another process). In this case the env variables should be called `SENTRY_TRACE`, `SENTRY_TRACEPARENT` and `SENTRY_BAGGAGE`.

The SDK running in the receiving service needs to make sure to pick up incoming trace information by
- reading `sentry-trace` and `baggage` headers for each incoming HTTP request,
- reading `sentry-trace` and `baggage` meta data when retrieving an item from a queue, or
- reading the environment variables `SENTRY_TRACE` and `SENTRY_BAGGAGE` on start up.
- reading `sentry-trace`, `traceparent` and `baggage` headers for each incoming HTTP request,
- reading `sentry-trace`, `traceparent` and `baggage` meta data when retrieving an item from a queue, or
- reading the environment variables `SENTRY_TRACE`, `SENTRY_TRACEPARENT` and `SENTRY_BAGGAGE` on start up.

In case both `sentry-trace` and `traceparent` are present, `sentry-trace` takes precedence.

This trace information should be stored in the "propagation context" of the current scope. This makes sure that all telemetry that is emmited from the receiving service to Sentry will include the correct trace information.
1 change: 1 addition & 0 deletions src/docs/sdk/features.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ If Performance Monitoring is both supported by the SDK and enabled in the client
- operation: `http.client`
- description: `$METHOD $url` (uppercase HTTP method), e.g. `GET https://sentry.io`
- HTTP requests must be enhanced with a [`sentry-trace` HTTP header](/sdk/performance/#header-sentry-trace) to support [distributed tracing](https://docs.sentry.io/product/sentry-basics/tracing/distributed-tracing)
- HTTP requests must be enhanced with a [`traceparent` HTTP header](/sdk/performance/#header-traceparent) to support [distributed tracing](https://docs.sentry.io/product/sentry-basics/tracing/distributed-tracing)
- HTTP requests must be enhanced with a [`baggage` HTTP header](/sdk/performance/dynamic-sampling-context/#baggage) to support [dynamic sampling](/sdk/performance/dynamic-sampling-context/)
- span status must match HTTP response status code ([see Span status to HTTP status code mapping](/sdk/event-payloads/span/))
- when network error occurs, span status must be set to `internal_error`
Expand Down
14 changes: 7 additions & 7 deletions src/docs/sdk/performance/dynamic-sampling-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Baggage is a standard HTTP header with URI encoded key-value pairs.
For the propagation of DSC, SDKs first read the DSC from the baggage header of incoming requests/messages.
To propagate DSC to downstream SDKs/services, we create a baggage header (or modify an existing one) through HTTP request instrumentation.

The `baggage` header should only be attached to an outgoing request if the request's URL matches at least one entry of the [`tracePropagationTargets`](/sdk/performance/#tracepropagationtargets) SDK option.
The `baggage` header should only be attached to an outgoing request if the request's URL matches at least one entry of the [`tracePropagationTargets`](/sdk/performance/#tracepropagationtargets) SDK option or this option is set to `null` or not set.

<Alert level="info">

Expand Down Expand Up @@ -108,7 +108,7 @@ Any values on the DSC should be propagated "as is" - this includes values like "

SDKs should recognize incoming requests as "instrumented" or "traced" when at least one of the following applies:

- The incoming request has a `sentry-trace` header
- The incoming request has a `sentry-trace` and/or `traceparent` header
- The incoming request has a `baggage` header containing one or more keys starting with "`sentry-`"

After the DSC of a particular trace has been frozen, API calls like `set_user` should have no effect on the DSC.
Expand Down Expand Up @@ -197,7 +197,7 @@ def has_sentry_value_in_baggage_header(request):
# header of `request`, for which the key starts with "sentry-". Otherwise, it returns False.

def on_incoming_request(request):
if request.has_header("sentry-trace") and (not request.has_header("baggage") or not has_sentry_value_in_baggage_header(request)):
if (request.has_header("sentry-trace") or request.has_header("traceparent")) and (not request.has_header("baggage") or not has_sentry_value_in_baggage_header(request)):
# Request comes from an old SDK which doesn't support Dynamic Sampling Context yet
# --> we don't propagate baggage for this trace
current_transaction.dynamic_sampling_context_frozen = True
Expand Down Expand Up @@ -245,12 +245,12 @@ Here's an example of that flow:

- Page starts loading
- Sentry initializes and starts `pageload` transaction
- Page makes HTTP request to user service to get user (propogates sentry-trace/baggage to user service)
- Page makes HTTP request to user service to get user (propogates sentry-trace/traceparent/baggage to user service)
- user service continues trace by automatically creating sampling transaction
- user service pings database service (propogates sentry-trace/baggage to database service)
- user service pings database service (propogates sentry-trace/traceparent/baggage to database service)
- database service continues trace by automatically creating sampling transaction
- Page gets data from user service, calls `Sentry.setUser` and sets `user_segment`
- Page makes HTTP requests to service A, service B, and service C (propogates sentry-trace/baggage to services A, B and C)
- Page makes HTTP requests to service A, service B, and service C (propogates sentry-trace/traceparent/baggage to services A, B and C)
- DSC is propogated with baggage to service A, service B, and service C, so 3 child transactions
- Page finishes loading, finishing `pageload` transaction, which is sent to Sentry

Expand All @@ -266,7 +266,7 @@ To illustrate this, let's look at another example:

- Page starts loading
- Sentry initializes and starts `pageload` transaction (with transaction name `/teams/123/user/456` - based on window URL)
- Page makes HTTP request to service A (propogates sentry-trace/baggage to user service)
- Page makes HTTP request to service A (propogates sentry-trace/traceparent/baggage to user service)
- Page renders with react router, triggering paramaterization of transaction name (`/teams/123/user/456` -> `/teams/:id/user/:id`).
- Page finishes loading, finishing pageload transaction, which is sent to Sentry

Expand Down
20 changes: 17 additions & 3 deletions src/docs/sdk/performance/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ This option takes an array of strings and/or regular expressions. SDKs should on

SDKs may choose a default value which makes sense for their use case. Most SDKs default to the regex `.*` (meaning they attach headers to all outgoing requests), but deviation is allowed if necessary. For example, because of CORS, browser-based SDKs default to only adding headers to domain-internal requests.

See [`sentry-trace`](#header-sentry-trace) and [`baggage`](/sdk/performance/dynamic-sampling-context/#baggage) for more details on the individual headers which are attached to outgoing requests.
See [`sentry-trace`](#header-sentry-trace), [`traceparent`](#header-traceparent) and [`baggage`](/sdk/performance/dynamic-sampling-context/#baggage) for more details on the individual headers which are attached to outgoing requests.

#### Example

Expand Down Expand Up @@ -117,7 +117,8 @@ tree as well as the unit of reporting to Sentry.
- `Span` should have a method `startChild` which creates a new span with the current span's id as the new span's `parentSpanId` and the current span's `sampled` value copied over to the new span's `sampled` property
- The `startChild` method should respect the `maxSpans` limit, and once the limit is reached the SDK should not create new child spans for the given transaction.
- `Span` should have a method called `toSentryTrace` which returns a string that could be sent as a header called `sentry-trace`.
- `Span` should have a method called `iterHeaders` (adapt to platform's naming conventions) that returns an iterable or map of header names and values. This is a thin wrapper containing `return {"sentry-trace": toSentryTrace()}` right now. See `continueFromHeaders` as to why this exists and should be preferred when writing integrations.
- `Span` should have a method called `toW3CTrace` which returns a string that could be sent as a header called `traceparent`.
- `Span` should have a method called `iterHeaders` (adapt to platform's naming conventions) that returns an iterable or map of header names and values. This is a thin wrapper containing `return {"sentry-trace": toSentryTrace(), "traceparent": toW3CTrace()}` right now. See `continueFromHeaders` as to why this exists and should be preferred when writing integrations.

- `Transaction` Interface

Expand All @@ -127,6 +128,7 @@ tree as well as the unit of reporting to Sentry.
- Since a `Transaction` inherits a `Span` it has all functions available and can be interacted with like it was a `Span`
- A transaction is either sampled (`sampled = true`) or unsampled (`sampled = false`), a decision which is either inherited or set once during the transaction's lifetime, and in either case is propagated to all children. Unsampled transactions should not be sent to Sentry.
- `TransactionContext` should have a static/ctor method called `fromSentryTrace` which prefills a `TransactionContext` with data received from a `sentry-trace` header value
- `TransactionContext` should have a static/ctor method called `fromW3CTrace` which prefills a `TransactionContext` with data received from a `traceparent` header value
- `TransactionContext` should have a static/ctor method called `continueFromHeaders(headerMap)` which is really just a thin wrapper around `fromSentryTrace(headerMap.get("sentry-trace"))` right now. This should be preferred by integration/framework-sdk authors over `fromSentryTrace` as it hides the exact header names used deeper in the core sdk, and leaves opportunity for using additional headers (from the W3C) in the future without changing all integrations.

- `Span.finish()`
Expand Down Expand Up @@ -198,7 +200,7 @@ To offer a minimal compatibility with the [W3C `traceparent` header](https://www
To avoid confusion with the W3C `traceparent` header (to which our header is similar but not identical), we call it simply `sentry-trace`.
No version is being defined in the header.

The `sentry-trace` header should only be attached to an outgoing request if the request's URL matches at least one entry of the [`tracePropagationTargets`](#tracepropagationtargets) SDK option.
The `sentry-trace` header should only be attached to an outgoing request if the request's URL matches at least one entry of the [`tracePropagationTargets`](#tracepropagationtargets) SDK option or this options is set to `null`.

### The `sampled` Value

Expand All @@ -220,6 +222,18 @@ Besides the [usual reasons to use \*defer](https://github.com/apache/incubator-z

Which in reality is useful for proxies to set it to `0` and opt out of tracing.

## Header `traceparent`

The header is used for trace propagation. SDKs use the header to continue traces from upstream services (e.g. incoming HTTP requests), and to propagate tracing information to downstream services (e.g. outgoing HTTP requests).

`traceparent = version-traceid-spanid-traceflags`

We can assume a version of `00`, as well as traceflags being either `-00` or `-01`.
A deferred sampling decision is not part of the specfication.
See [W3C `traceparent` header](https://www.w3.org/TR/trace-context/#traceparent-header) for more information.

The `traceparent` header should only be attached to an outgoing request if the request's URL matches at least one entry of the [`tracePropagationTargets`](#tracepropagationtargets) SDK option or this option is set to `null` or not set.

## Static API Changes

The `Sentry.startTransaction` function should take two arguments - the `transactionContext` passed to the `Transaction` constructor and an optional `customSamplingContext` object containing data to be passed to `tracesSampler` (if defined).
Expand Down

0 comments on commit 1a841fd

Please # to comment.