This repository was archived by the owner on Aug 14, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 223
feat(sdk): Describe trace contexts #407
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
--- | ||
title: "Trace Contexts (Experimental)" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This name is very unfortunate because SDKs already have a "Trace Context", an object that is sent in events (both errors and transactions) as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also for @RaduW -- suggestions on how we name the new trace context, instead? How about "trace state" borrowing from W3C? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trace state works for me |
||
--- | ||
|
||
<Alert level="warning"> | ||
|
||
This feature is under development and not required for all SDKs supporting Performance Monitoring, yet. Please consider the <Link to="/sdk/performance#header-sentry-trace">Performance Guidelines</Link> as reference documentation. Anything that contradicts it is a mistake (or an out of date detail) in this document. | ||
|
||
</Alert> | ||
|
||
In order to sample traces we need to pass along the call chain a trace id together with the necessary information for making a sampling decision, the so-called "trace context". | ||
|
||
## Protocol | ||
|
||
Trace information is passed between SDKs as an encoded `tracestate` header, which SDKs are expected to intercept and propagate. | ||
|
||
For event submission to sentry, the trace context is sent as JSON object embedded in an Envelope header with the key `trace`. | ||
|
||
### Trace Context | ||
|
||
Regardless of the transport mechanism, the trace context is a JSON object with the following fields: | ||
|
||
- `trace_id` (string, required) - UUID V4 encoded as a hexadecimal sequence with no dashes (e.g. `771a43a4192642f0b136d5159a501700`) that is a sequence of 32 hexadecimal digits. This must match the trace id of the submitted transaction item. | ||
- `public_key` (string, required) - Public key from the DSN used by the SDK | ||
- `release` (string, optional) - The release name as specified in client options, usually: `[email protected]+build`. *This should match the `release` attribute of the transaction event payload*.* | ||
- `environment` - The environment name as specified in client options, for example `staging`. *This should match the `environment` attribute of the transaction event payload*.* | ||
- `user` (object, optional) - A subset of the scope's user context containing the following fields: | ||
- `id` (string, optional) - The `id` attribute of the user context. | ||
- `segment` (string, optional) - The value of a `segment` attribute in the user's data bag, if it exists. In the future, this field may be promoted to a proper attribute of the user context. | ||
- `transaction` (string, optional) - The transaction name set on the scope. *This should match the `transaction` attribute of the transaction event payload*.* | ||
|
||
** See "Freezing the Context" for more information on consistency between the trace context and fields in the event payload.* | ||
|
||
**Example:** | ||
|
||
```json | ||
{ | ||
"trace_id": "771a43a4192642f0b136d5159a501700", | ||
"public_key": "49d0f7386ad645858ae85020e393bef3", | ||
"release": "[email protected]", | ||
"environment": "production", | ||
"user": { | ||
"id": "7efa4978da177713df088f846f8c484d", | ||
"segment": "vip" | ||
}, | ||
"transaction": "/api/0/project_details", | ||
} | ||
``` | ||
|
||
### Envelope Headers | ||
|
||
When sending transaction events to Sentry via Envelopes, the trace information must be set in the envelope headers under the `trace` field. | ||
|
||
Here's an example of a minimal envelope header containing the trace context (Although the header does not contain newlines, in the example below newlines were added for readability): | ||
|
||
```json | ||
{ | ||
"event_id":"12c2d058d58442709aa2eca08bf20986", | ||
"trace": { | ||
"trace_id": "771a43a4192642f0b136d5159a501700", | ||
"public_key": "49d0f7386ad645858ae85020e393bef3" | ||
// other trace attributes | ||
} | ||
} | ||
``` | ||
|
||
### Tracestate Headers | ||
|
||
When propagating trace contexts to other SDKs, Sentry uses the [W3C `tracestate` header](https://www.w3.org/TR/trace-context/#trace-context-http-headers-format). See "Trace Propagation" for more information on how to propagate these headers to other SDKs. | ||
|
||
Tracestate headers contain several vendor-specific opaque data. As per HTTP spec, these multiple header values can be given in two ways, usually supported by HTTP libraries and framework out-of-the-box: | ||
|
||
- Joined by comma: | ||
``` | ||
tracestate: sentry=<data>, other=<data> | ||
``` | ||
- Repetition: | ||
``` | ||
tracestate: sentry=<data> | ||
tracestate: other=<data> | ||
``` | ||
|
||
To create contents of the tracestate header: | ||
|
||
1. Serialize the full trace context to JSON, including the trace_id. | ||
2. Encode the resulting JSON string as UTF-8, if strings are represented differently on the platform. | ||
3. Encode the UTF-8 string with base64. | ||
4. Strip trailing padding characters (`=`), since this is a reserved character. | ||
5. Prepend with `"sentry="`, resulting in `"sentry=<base64>"`. | ||
6. Join into the header as described above. | ||
|
||
<Alert level="info"> | ||
|
||
By stripping trailing padding, default base64 parsers may detect an incomplete payload. Choose a parsing mode that either allows for missing `=` or allows truncated payloads. | ||
|
||
</Alert> | ||
|
||
The following is an example : | ||
|
||
```json | ||
{ | ||
"trace_id": "771a43a4192642f0b136d5159a501700", | ||
"public_key": "49d0f7386ad645858ae85020e393bef3", | ||
"release": "1.1.22", | ||
"environment": "dev", | ||
"user": { | ||
"segment": "vip", | ||
"id": "7efa4978da177713df088f846f8c484d" | ||
} | ||
} | ||
``` | ||
|
||
Would encode as: | ||
|
||
``` | ||
ewogICJ0cmFjZV9pZCI6ICI3NzFhNDNhNDE5MjY0MmYwYjEzNmQ1MTU5YTUwMTcwMCIsCiAgInB1YmxpY19rZXkiOiAiNDlkMGY3Mzg2YWQ2NDU4NThhZTg1MDIwZTM5M2JlZjMiLAogICJyZWxlYXNlIjogIjEuMS4yMiIsCiAgImVudmlyb25tZW50IjogImRldiIsCiAgInVzZXIiOiB7CiAgICAic2VnbWVudCI6ICJ2aXAiLAogICAgImlkIjogIjdlZmE0OTc4ZGExNzc3MTNkZjA4OGY4NDZmOGM0ODRkIgogIH0KfQ | ||
``` | ||
|
||
Resulting in the header: | ||
|
||
``` | ||
tracestate: other=[omitted],sentry=ewogICJ0cmFjZV9pZCI6ICI3NzFhNDNhNDE5MjY0MmYwYjEzNmQ1MTU5YTUwMTcwMCIsCiAgInB1YmxpY19rZXkiOiAiNDlkMGY3Mzg2YWQ2NDU4NThhZTg1MDIwZTM5M2JlZjMiLAogICJyZWxlYXNlIjogIjEuMS4yMiIsCiAgImVudmlyb25tZW50IjogImRldiIsCiAgInVzZXIiOiB7CiAgICAic2VnbWVudCI6ICJ2aXAiLAogICAgImlkIjogIjdlZmE0OTc4ZGExNzc3MTNkZjA4OGY4NDZmOGM0ODRkIgogIH0KfQ | ||
``` | ||
|
||
## Implementation Guidelines | ||
|
||
An SDK supporting this header must: | ||
|
||
- Create a new trace context using scope information | ||
- Intercept `tracestate` headers from incoming HTTP requests where applicable and apply them to the local trace context | ||
- Add the contents of trace context as `trace` header to Envelopes containing transaction events | ||
- Add `tracestate` headers to outgoing HTTP requests for propagation | ||
|
||
### Backgrounds | ||
|
||
This is an extension of trace ID propagation covered by <Link to="/sdk/performance#header-sentry-trace">Performance Guidelines</Link>. According to the Unified API tracing spec, Sentry SDKs add an HTTP header `sentry-trace` to outgoing requests via integrations. Most importantly, this header contains the trace ID, which must match the trace id of the transaction event and also of the trace context below. | ||
|
||
The trace context shall be propagated in an additional `tracestate` header defined in [W3C traceparent header](https://www.w3.org/TR/trace-context/#trace-context-http-headers-format). Note that we must keep compatibility with the W3C spec as opposed to the proprietary `sentry-trace` header. The `tracestate` header also contains vendor-specific opaque data in addition to the contents placed by the Sentry SDK. | ||
|
||
### Freezing the Context | ||
|
||
To ensure fully consistent trace contexts for all transactions in a trace, the trace context cannot be changed once it is sent over the wire, even if scope or options change afterwards. That is, once computed the trace context is no longer updated. Even if the app calls `setRelease`, the old release remains in the context. | ||
|
||
To compensate for lazy calls to functions like `setTransaction` and `setUser`, the trace context can be thought to be in two states: *NEW* and *SENT* . Initially, the context is in the *NEW* state and it is modifiable. Once sent for first time, it becomes *SENT* and can no longer change. | ||
|
||
We recommend the trace context should be computed on-the-fly the first time it is needed in any of: | ||
|
||
- Creating an Envelope | ||
- Propagation to an outgoing HTTP request | ||
|
||
The trace context must be retained until the user starts a new trace, at which point a new trace context must be computed by the SDK. | ||
|
||
<Alert level="info"> | ||
|
||
It is recommended that SDKs log modifications of attributes that would result in trace context changes like `user.id` when the trace context is frozen, in order to simplify debugging of common dynamic sampling pitfalls. | ||
|
||
</Alert> | ||
|
||
### Incoming Contexts | ||
|
||
Same as for intercepting trace IDs from inbound HTTP requests, SDKs should read `tracestate` headers and assume the Sentry trace context, if specified. Such a context is immediately frozen in the *SENT* state and should no longer allow for modifications. | ||
|
||
## Platform Specifics | ||
|
||
### Encoding in JavaScript | ||
|
||
As mentioned, we need to encode the JSON trace context using UTF-8 strings. JavaScript internally uses UTF16 so we need to work a bit to do the transformation. | ||
|
||
The basic idea is presented in [this](https://attacomsian.com/blog/javascript-base64-encode-decode) article (and in [other](https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings) places). | ||
|
||
In short here's the function that converts a context into a base64 string that can be saved in `tracestate`. In the end we went with a simpler implementation, but the idea is the same: | ||
|
||
```jsx | ||
// Compact form | ||
function objToB64(obj) { | ||
const utf16Json = JSON.stringify(obj) | ||
const b64 = btoa(encodeURIComponent(utf16Json).replace(/%([0-9A-F]{2})/g, | ||
function toSolidBytes(match, p1) { | ||
return String.fromCharCode('0x' + p1); | ||
})) | ||
const len = b64.length | ||
if (b64[len - 2] === '=') | ||
return b64.substr(0, len - 2) | ||
else if (b64[len - 1] === '=') | ||
return b64.substr(0, len - 1) | ||
return b64 | ||
} | ||
|
||
// Commented | ||
function objToB64(obj) { | ||
// object to JSON string | ||
const utf16Json = JSON.stringify(obj) | ||
// still utf16 string but with non ASCI escaped as UTF-8 numbers) | ||
const encodedUtf8 =encodeURIComponent(utf16Json); | ||
|
||
// replace the escaped code points with utf16 | ||
// in the first 256 code points (the most wierd part) | ||
const b64 = btoa(endcodedUtf8.replace(/%([0-9A-F]{2})/g, | ||
function toSolidBytes(match, p1) { | ||
return String.fromCharCode('0x' + p1); | ||
})) | ||
|
||
// drop the '=' or '==' padding from base64 | ||
const len = b64.length | ||
if (b64[len - 2] === '=') | ||
return b64.substr(0, len - 2) | ||
else if (b64[len - 1] === '=') | ||
return b64.substr(0, len - 1) | ||
return b64 | ||
} | ||
// const test = {"x":"a-🙂-读写汉字 - 学中文"} | ||
// objToB64(test) | ||
// "eyJ4IjoiYS3wn5mCLeivu+WGmeaxieWtlyAtIOWtpuS4reaWhyJ9" | ||
``` | ||
|
||
And here's the function that accepts a base64 string (with or without '=' padding) and returns an object | ||
|
||
```jsx | ||
function b64ToObj(b64) { | ||
utf16 = decodeURIComponent(atob(b64).split('').map(function(c) { | ||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); | ||
}).join('')); | ||
return JSON.parse(utf16) | ||
} | ||
|
||
// b64ToObj("eyJ4IjoiYS3wn5mCLeivu+WGmeaxieWtlyAtIOWtpuS4reaWhyJ9") | ||
// {"x":"a-🙂-读写汉字 - 学中文"} | ||
``` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to have the URL path
/sdk/trace-context/
instead of nesting under/sdk/performance/trace-context/
? The navigation is not working as expected, once you click the link the new page Trace Contexts has no corresponding item in the sidebar visible.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't care much about the URL and didn't want to create folders, to be honest. We can move it if you like.