Skip to content

Commit b52eca8

Browse files
Lms24andreiborzas1gr1dnicohrubec
authored
feat(develop/sdks): Add section with JS SDK-specific information (getsentry#10965)
Add JavaScript SDK specific documentation to the develop docs. The idea of these docs is that everyone at Sentry, as well as external community contributors can read up on certain aspects of the JS SDKs that are good to know. --------- Co-authored-by: Andrei <[email protected]> Co-authored-by: Sigrid Huemer <[email protected]> Co-authored-by: Nicolas Hrubec <[email protected]>
1 parent e145a2a commit b52eca8

File tree

6 files changed

+245
-2
lines changed

6 files changed

+245
-2
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
---
2+
title: Tracing in the Browser
3+
description: Interesting aspects of tracing and performance instrumentation in the browser SDKs.
4+
sidebar_order: 10
5+
---
6+
7+
The tracing behavior in our browser SDKs is somewhat unique and significantly differs from tracing in the backend. This page collects the most important aspects.
8+
9+
<Note>
10+
The aspects described in this document apply to all browser SDKs. This
11+
includes `@sentry/browser` and all SDKs building on top of it like
12+
`@sentry/react`, `@sentry/angular` or `@sentry/vue` but also the client-side
13+
parts of meta frameworks like `@sentry/nextjs`.
14+
</Note>
15+
16+
Please note that any kind of automatic tracing instrumentation in the browser requires the `browserTracingIntegration()` to be added to the SDK configuration.
17+
The default configuration of the SDK does not include any performance or tracing-related instrumentation to save on [bundle size](../bundle-size) for errors-only users.
18+
19+
## Pageload and Navigation Spans
20+
21+
Browser SDKs automatically create spans for the initial pageload as well as for subsequent [soft navigations](#navigation-spans).
22+
23+
These spans however don't behave like conventional spans. We call them **idle spans** because they are started at some point (more on that later) but are not explicitly ended.
24+
Instead, we use a couple of heuristics and parameters to debounce and end the spans automatically.
25+
For example, we end an idle span after a certain time of inactivity, meaning no new spans are added to the span tree.
26+
Also, there are maximum timeout parameters.
27+
This idling mechanism is necessary because neither in pageload nor navigation scenarios we have a clear end point or event to explicitly end the span.
28+
29+
It's important to understand the idle span ending behavior to interpret its duration in a sensible way:
30+
31+
- When ending the idle span, we shorten its duration to the last ended child span.
32+
- Sometimes, child spans that you'd intuitively not associate to the initial pageload might be started close enough to the expected end of the idle span to debounce ending it.
33+
- In some cases, the idle span end can be debounced so often, that the maximum duration of the idle span is reached.
34+
At this point it is "forcefully" ended.
35+
36+
Therefore, the duration of the idle span does not reliably represent the actual time a pageload or navigation took.
37+
It's important that we're also aware of this limitation in the product UI to avoid deriving performance scores or alerts based on the idle span duration.
38+
39+
Both, pageload and navigation spans are **always started as root spans**.
40+
41+
### Pageload Spans
42+
43+
The pageload span is started when the SDK initializes, more specifically, when the `browserTracingIntegration`'s `afterAllSetup` hook is called. This ensures that we start the
44+
pageload span as early as possible. However, this still means that the span is only started once the SDK code is shipped to the browser. In reality though, the actual page load
45+
starts with the browser making the request for the URL to the server. To account for this, we retroactively backdate the start time of the pageload span to the browser request
46+
start time. We are able to backdate the start time because the browser provides this timing information.
47+
48+
The pageload span is **earliest** ended, when the browser emits `'interactive'` or `'complete'` in `WINDOW.document.readyState`. It might be very well prolonged by our instrumentation adding additional child spans to the pageload root span.
49+
50+
![Pageload Span](pageload-span.png)
51+
52+
A pageload span:
53+
54+
- Always has the `sentry.op: 'pageload'` attribute
55+
- Always is a root span
56+
- [Continues a trace](#trace-continuation) picked up from `<meta>` tags in the initial HTML response or, if no tags are detected, starts a new trace.
57+
- Contains various spans with specific ops that we retroactively create from browser Performance API entries (see image for example spans):
58+
1. `browser` - information about the pageload request lifecycle (entry type `navigation`).
59+
2. `resource.*` - resource timing information containing the duration of how long images, CSS, JS files etc. took to load.
60+
3. `measure` - custom time measurements. Some of them are set by our SDK, some by SDK users or 3rd parties, some by the browser.
61+
4. `mark` - custom time markers. Some of them are set by our SDK, some SDK users or 3rd parties, some by the browser.
62+
5. `paint` - paint timing information.
63+
6. `ui.long-animation-frame` - a span showing the duration of a long animation frame event detected by the browser.
64+
- Contains [web vital](#web-vitals) as measurements.
65+
- May contain child spans created from other instrumentations (e.g. `http.client`) spans or manually created spans.
66+
67+
Since the pageload span is automatically started at SDK initialization, **there's only one pageload span** started during the SDK lifecycle.
68+
69+
### Navigation Spans
70+
71+
Navigation spans are started when a "soft navigation" occurs.
72+
A "soft navigation" means that the URL or history state of the page changes **without** a full page reload.
73+
A typical example for a soft navigation is a single-page application (SPA) using a client-side router (for example React Router or Angular's Router).
74+
While navigations in SPAs can appear like a normal page navigation to users, they do not trigger a full page load request.
75+
Instead, they typically update the browser's history state, the URL and the content of the page dynamically.
76+
77+
By default, in `@sentry/browser` the SDK listens to the browser's `History` API to detect such navigations.
78+
[Framework-specific SDKs and router instrumentations](#router-instrumentations-and-route-parameterization) might use other mechanisms to detect them.
79+
80+
Like a pageload span, navigation spans are idle spans and end themselves after a certain time of inactivity or after a maximum duration.
81+
82+
In contrast to pageload spans, navigation spans do not contain web vital measurements. Read more about the reason [here](#web-vitals).
83+
84+
### Router Instrumentations and Route Parameterization
85+
86+
Some frameworks like React, Angular or Vue have their own in-browser routing solutions (e.g. Angular Router).
87+
Most of our framework SDKs therefore provide their own `browserTracingIntegration` that instruments this router to improve the quality of the pageload and navigation spans.
88+
89+
Router instrumentation allows us to:
90+
91+
1. Directly hook into the router to detect navigations and start navigation spans. Depending on the framework, this is more versatile than listening to the history API.
92+
2. Extract parameterized route names, meaning, we can show and group spans by the actual route instead of individual raw urls (e.g. `/users/[id]` instead of `/users/12345`).
93+
3. Depending on the instrumentation, extract route parameters or redirects and add them in a controlled manner to the span data.
94+
95+
## Web Vitals
96+
97+
The browser SDKs automatically capture Web Vitals during the initial pageload of the web application.
98+
This document will not explain what Web Vitals are, but you can find more information on the [Web Vitals website](https://web.dev/vitals/).
99+
100+
Our goal is to capture web vitals in the same way that e.g. Google Analytics, Google search engine indexers, Lighthouse or the web vitals chrome extension do.
101+
There are sometimes discrepancies between the values we capture and the values that other tools capture because these tools are not tied to the restrictions around defining an end of a page load like us.
102+
103+
Generally speaking, most web vitals are accumulated while the pageload span is active and captured when we _end_ the pageload span. This works well for vitals that stabilize themselves very early in the lifecycle but
104+
it leads to discrepancies for vitals that are still changing after the pageload span ends. This is the case for `LCP` and `CLS` in particular.
105+
106+
<Note>
107+
At the time of writing, we're transitioning to a [more sophisticated `LCP` and
108+
`CLS` capturing
109+
technique](https://github.com/getsentry/sentry-javascript/issues/12714) that
110+
should improve the accuracy of these metrics. It will essentially work like
111+
`INP` collection works today.
112+
</Note>
113+
114+
### Interaction To Next paint (INP)
115+
116+
The Interaction To Next Paint (INP) web vital is treated a bit differently.
117+
It's not attached to the pageload span.
118+
Instead, we listen to when the browser emits INP events (that is on page hide, for example when switching tabs) and capture them as standalone spans.
119+
120+
## Tracing Model
121+
122+
With the release of version 8 of the JS SDKs, the browser SDKs adopted a new trace lifetime model.
123+
With this new model, traces last for the entire duration of one page or route.
124+
This means that a trace id is created at the initial pageload and is used for all subsequent events until the user navigates to a new page or reloads the page.
125+
For single-page applications (SPAs) using some kind of routing, the trace id is used for the entire duration of one route and changes on a soft navigation.
126+
127+
![Trace Lifetime](page-based-traces.png)
128+
129+
This tracing model has a couple of consequences that are important to understand:
130+
131+
- **Increased Context**: Let's start with the good stuff: Since the trace id remains constant for all events on a page or route, all events within one page or route belong to the same trace.
132+
This makes it easier to understand what happened prior to the error or span event users are looking at.
133+
- **Multiple Root Spans**: A trace can have multiple root spans or events. For instance, if users enabled interaction spans or manually create spans, they will be associated with the same trace id of the prior pageload or navigation.
134+
- **Quota Management**: Since the trace state does not change throughout a single page or route, the sampling decision of the initial span is carried over for all subsequent events. This means that the sampling decision is only made once per page or route and is not reevaluated for each event. Furthermore, subsequent http (`fetch` or `XmlHttpRequest`) also propagate this sampling decision to potential downstream services. This can lead to a higher number of events being sent to Sentry than before.
135+
- **Long-lived Traces**: Not every web application features navigations (hard or soft). For pages where users simply remain on one page without making navigations (e.g. a chat application), the trace id will remain constant for the entire duration of the user journey. This can lead to very long-lived traces that can potentially grow very large.
136+
137+
<Note>
138+
139+
As outlined above, some of the consequences have negative product or UX implications.
140+
This is a known issue and we're working on an update to the trace lifetime model to address these issues.
141+
However, this is a complex topic and we're still in the process of evaluating the best solution.
142+
143+
</Note>
144+
145+
## Trace Propagation
146+
147+
The browser JS SDKs propagate traces like other SDKs by attaching headers to outgoing requests made with the `fetch` or `XmlHttpRequest` APIs.
148+
However, we can't by default add the headers to all outgoing requests as downstream services might send error responses if their CORS policies don't allow arbitrary headers.
149+
Therefore, the browser SDKs only attach headers to same-origin requests by default. Users have to opt into cross-origin tracing by setting the `tracePropagationTargets` option in the SDK configuration.
150+
151+
## Trace Continuation
152+
153+
The browser SDKs are able to continue a trace that was started on the server when the initial HTML page response is served.
154+
For this to work, the server-side SDK has to render `<meta>` tags containing the `sentry-trace` and `baggage` tracing data into the HTML response.
155+
These meta tags are picked up by the browser SDKs when it initializes (`Sentry.init`).
156+
157+
## Tracing Without performance
158+
159+
The browser SDKs support tracing without performance (TwP).
160+
TwP means that the SDK attaches tracing headers to outgoing requests but does not start or send any spans.
161+
This feature enables connecting frontend to backend errors in the UI without depleting users' span quota.
162+
163+
While in other SDKs, TwP is activated by default when neither `tracesSampleRate` nor `tracesSampler` are set, in browser, users still have to add `browserTracingIntegration`.
164+
The reason for this is that our fetch and XHR instrumentation is not included in the default SDK bundle to save on [bundle size](../bundle-size).
165+
So the only configuration to enable TwP is to register `browserTracingIntegration` but omit the sampling options completely (or set them to `undefined`).
166+
167+
TwP mode uses the same [tracing model](#tracing-model) as SDKs configured for regular tracing.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
title: Bundle Size
3+
description: Why we think twice before adding a new line of code to the SDK
4+
sidebar_order: 11
5+
---
6+
7+
In contrast to most other SDKs, we really have to be careful how much code we add to the browser SDKs and _in what way_ we do it.
8+
The reason is that browser SDK code (with the exception of our Loader) is loaded synchronously, either in its own JS bundle or in the main JS bundle of the frontend web application.
9+
10+
Hence, our SDK directly contributes to the bundle size of the page and can have an impact on the performance of the page.
11+
12+
## What do we do against it?
13+
14+
Well, we can't just stop developing new features or fixes. But we can be smart about it:
15+
16+
- We run bundle size checks on each PR to measure potential size increases (or decreases).
17+
- Our CI setup fails if the bundle size surpasses a certain threshold for specific bundling combinations.
18+
- We have lint rules against certain syntax that increases bundle size more than necessary.
19+
- When reviewing PRs, we keep an eye on the bundle size impact of the changes and try to identifies opportunities for optimization.
20+
21+
Most importantly, whenever we add a new feature, we try to write it in a tree-shakeable way.
22+
23+
### Writing Tree-Shakeable Code
24+
25+
If you want to contribute a new feature to the SDK, please keep in mind to build the feature in an opt-in, tree-shakeable way.
26+
In most cases, this means building the feature as an `Integration` or - helpful for smaller features - as a package export that users can import.
27+
28+
Specifically for building integrations, be aware that writing an integration does not mean it has to be opt-in for users by default.
29+
If we deem it valuable enough, we can add an integration by default, which, yes, will increase the bundle size for many users.
30+
However users can still set up [advanced tree-shaking](https://docs.sentry.io/platforms/javascript/configuration/tree-shaking/) to remove the integrations they don't need.
31+
The important part is that building integrations enables tree-shaking.
32+
33+
Keeping in mind the above, opting users into a feature that should remain tree-shakeable means that we can't enabled the feature with a simple flag in the SDK.
34+
Instead, we need to let users import the feature explicitly and register it:
35+
36+
```js
37+
// Not tree-shakeable
38+
Sentry.init({
39+
enableReplay: true, // internally: if (enableReplay) { ...replay code }
40+
});
41+
42+
// tree-shakeable
43+
Sentry.init({
44+
integrations: [replayIntegration()],
45+
});
46+
```
47+
48+
## What we're not doing
49+
50+
As with all things, there are exceptions to these rule and we have added size-increasing features in the past.
51+
52+
- While we are cautious about bundle size, it does not mean we don't add new things to the SDK.
53+
- If there's enough value for the change to be added by default, we will increase the bundle size.
54+
- If fixing incorrect behavior requires more code, we will increase the bundle size.
55+
- We don't sacrifice code readability entirely for bundle size. We try to find a balance.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
title: JavaScript SDKs
3+
sidebar_order: 31
4+
---
5+
6+
This Section contains JavaScript SDK-specific information that's useful for developing new or extending existing JS SDKs as well as for ingest and product teams to understanding how the JS SDKs works and what data it sends.
7+
8+
If you're looking to contribute new features to the SDK or want to understand what data you can consume from the JS SDK, this is the right place to start.
9+
The JS SDK team encourages community or cross-team contributions to the SDK and is happy to help you get started.
10+
11+
Please reach out to the SDK team (on [Github](https://github.com/getsentry/sentry-javascript) or internally in Slack `#discuss-javascript`) if you have questions, concerns or plan on building a larger feature.
12+
13+
<Alert level="warning">
14+
15+
This document **does not** replace general SDK specifications like the Unified API or the SDK Development Guide.
16+
It also doesn't explain implementations in detail or link to SDK code.
17+
It's a supplement to other documents and contains information that's specific to the JavaScript SDKs.
18+
19+
</Alert>
20+
21+
<PageGrid></PageGrid>
950 KB
Loading
215 KB
Loading

develop-docs/sdk/signal-handlers.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Signal Handlers
3-
sidebar_order: 30
3+
sidebar_order: 40
44
---
55

66
Native Sentry SDKs like sentry-native and sentry-cocoa have to work with signal handlers
@@ -70,4 +70,4 @@ thread as the signal handler.
7070

7171
For writing files the `fwrite` family of functions must not be used as they
7272
are not async safe. Instead, the underlying `write` and `open` functions
73-
should be used.
73+
should be used.

0 commit comments

Comments
 (0)