-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
docs(js): Add New Spans guide #17834
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
fac1de3
d67f9cd
4461c9c
29765bb
497dc78
c84d25e
93fed8f
6f89d79
2d06907
f0e25a6
9b703d3
4865de9
31c975c
2f89f55
04a40f8
1688a5a
653aa8f
e3d3699
034a6cb
0b511d5
b8d6819
0120dc6
bd5bdf0
95337d2
dddd077
61e9d71
e91978c
2a8fbf1
93d530f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,397 @@ | ||
| --- | ||
| title: New Spans | ||
| description: "Learn how to use stream mode to send spans to Sentry as they finish, removing the 1,000-span limit and making trace data visible sooner." | ||
|
Contributor
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. @cleptric - should we be calling out the 1k span limit as a benefit? Seems really valuable, but want to make sure we're not locking ourselves into something unnecessarily.
Member
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. Don't wanna answer for Michi (so feel free to overrule me) but removing the hard 1k limit is the key feature of span streaming. I think we can/should call it out. The 1k limit exists but isn't documented publicly a lot (besides this zendesk page). I don't think it's a secret though, so no harm in mentioning it. |
||
| sidebar_order: 35 | ||
| new: true | ||
| notSupported: | ||
| - javascript.cordova | ||
| --- | ||
|
|
||
| By default, the Sentry JavaScript SDKs collect all spans in memory and send them to Sentry as a single transaction once the root span ends. This is called transaction mode. | ||
| Stream mode changes this by sending spans to Sentry in batches as they finish. Service spans, which represent a service's entry point, replace transactions as the main grouping for each service. | ||
|
|
||
| <Expandable title="Why use stream mode?"> | ||
|
|
||
| - **No 1,000-span limit.** In transaction mode, transactions are capped at 1,000 spans. Stream mode has no upper limit since spans are sent in batches. | ||
| - **Lower memory usage.** Spans are flushed periodically and don't need to be held in memory until the root span ends. This is especially useful for long-running processes like queue consumers or cron jobs. | ||
| - **Faster visibility.** Span data arrives in Sentry as your application runs, instead of only after the entire operation completes. | ||
| - **No data loss from crashes.** If your process terminates unexpectedly, spans that were already flushed are preserved. In transaction mode, a crash before the root span ends means all span data is lost. | ||
|
|
||
| </Expandable> | ||
|
|
||
| You can find the following span types mentioned throughout this page: | ||
|
|
||
| - **Root span**: The topmost span in a trace. It has no parent span and is always a service span. | ||
| - **Service span**: A parent-level span at the entry of a service. In transaction mode, this is called a transaction. | ||
| - **Child span**: Any span nested under a parent span within the same trace. | ||
|
|
||
| This graph shows how these span types relate to each other within a trace: | ||
|
|
||
| ``` | ||
| Trace | ||
| │ | ||
| └── Root span [service A] | ||
| ├── Child span | ||
| │ └── Child span | ||
| └── Service span [service B] | ||
| ├── Child span | ||
| └── Child span | ||
| ``` | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| You need: | ||
|
|
||
| - <PlatformLink to="/tracing/#configure">Tracing configured</PlatformLink> in | ||
| your app | ||
| - Sentry SDK `>=10.53.1` | ||
|
|
||
| ## Migrate from Transaction Mode | ||
|
|
||
| For most users, switching to stream mode requires no code changes beyond the initial opt-in. If you use `beforeSendSpan` or `beforeSendTransaction`, follow these steps: | ||
|
|
||
| 1. [Enable stream mode](#enable-stream-mode) | ||
| 2. [Wrap `beforeSendSpan` with `Sentry.withStreamedSpan()` to filter spans](#filter-spans) | ||
| 3. [Replace `beforeSendTransaction` with `ignoreSpans` to drop spans](#drop-spans) | ||
| 4. [Verify the migration](#verify-your-setup) | ||
|
|
||
| ## Enable Stream Mode | ||
|
|
||
| <PlatformContent includePath="performance/enable-stream-mode" /> | ||
|
|
||
| <PlatformSection notSupported={["javascript.nextjs", "javascript.nuxt", "javascript.sveltekit", "javascript.astro", "javascript.solidstart", "javascript.capacitor", "javascript.tanstackstart-react", "javascript.remix", "javascript.react-router", "javascript.effect"]}> | ||
|
|
||
| <Alert level="success" title="Mixing tracing modes in distributed tracing"> | ||
|
|
||
| Tracing modes are scoped per SDK, which means you can use, for example, stream mode in your frontend, and transaction mode in your backend, or vice versa. | ||
|
|
||
| </Alert> | ||
|
|
||
| </PlatformSection> | ||
|
|
||
| <Expandable title="How does span flushing work?"> | ||
|
|
||
| When stream mode is enabled, the SDK maintains an internal buffer that groups spans by trace ID. | ||
|
|
||
| Spans are flushed: | ||
|
|
||
| - On a regular interval (every 5 seconds by default). | ||
| - When a trace's buffer reaches 1,000 spans. | ||
| - When you call `Sentry.flush()` or `Sentry.close()`. | ||
|
|
||
| Each flush sends only the spans accumulated since the last flush, grouped into envelopes by trace ID. | ||
|
|
||
| </Expandable> | ||
|
|
||
| ## Manual Instrumentation (Optional) | ||
|
|
||
| ### Start a Span | ||
|
|
||
| <SplitLayout> | ||
| <SplitSection> | ||
| <SplitSectionText> | ||
|
|
||
| Use `Sentry.startSpan()` to create a span that is automatically ended when the callback completes: | ||
|
|
||
| </SplitSectionText> | ||
| <SplitSectionCode> | ||
|
|
||
| ```javascript | ||
| const result = await Sentry.startSpan( | ||
| { name: "my-operation", attributes: { "my.attribute": "value" } }, | ||
| async () => { | ||
| // Your code here | ||
| return await doWork(); | ||
| } | ||
| ); | ||
| ``` | ||
|
|
||
| </SplitSectionCode> | ||
| </SplitSection> | ||
|
|
||
| <SplitSection> | ||
| <SplitSectionText> | ||
|
|
||
| Child spans created inside the callback are automatically associated with the parent: | ||
|
|
||
| </SplitSectionText> | ||
| <SplitSectionCode> | ||
|
|
||
| ```javascript | ||
| await Sentry.startSpan({ name: "parent-operation" }, async () => { | ||
| await Sentry.startSpan({ name: "child-step-1" }, async () => { | ||
| await stepOne(); | ||
| }); | ||
|
|
||
| await Sentry.startSpan({ name: "child-step-2" }, async () => { | ||
| await stepTwo(); | ||
| }); | ||
| }); | ||
| ``` | ||
|
|
||
| </SplitSectionCode> | ||
| </SplitSection> | ||
| </SplitLayout> | ||
|
|
||
| For more details on span creation APIs, such as `startSpan`, `startSpanManual`, or `startInactiveSpan`, see <PlatformLink to="/tracing/instrumentation/">Instrumentation</PlatformLink>. | ||
|
|
||
| ### Add Span Attributes | ||
|
|
||
| Attach structured metadata to spans using <PlatformLink to="/configuration/apis/#startSpan">`attributes`</PlatformLink>, which can be `string`, `number`, or `boolean`, as well as arrays of these types. | ||
|
|
||
| <SplitLayout> | ||
| <SplitSection> | ||
| <SplitSectionText> | ||
|
|
||
| You can set attributes when starting a span: | ||
|
|
||
| </SplitSectionText> | ||
| <SplitSectionCode> | ||
|
|
||
| ```javascript | ||
| Sentry.startSpan( | ||
| { | ||
| name: "process-order", | ||
| attributes: { | ||
| "sentry.op": "queue.process", | ||
| "order.id": "abc-123", | ||
| "order.item_count": 5, | ||
| "order.priority": true, | ||
| }, | ||
| }, | ||
| () => { | ||
| // Process the order | ||
| } | ||
| ); | ||
| ``` | ||
|
|
||
| </SplitSectionCode> | ||
| </SplitSection> | ||
|
|
||
| <SplitSection> | ||
| <SplitSectionText> | ||
|
|
||
| Or add them to an already running span: | ||
|
|
||
| </SplitSectionText> | ||
| <SplitSectionCode> | ||
|
|
||
| ```javascript | ||
| Sentry.startSpan({ name: "handle-request" }, (span) => { | ||
| // Set a single attribute | ||
| span.setAttribute("http.response.status_code", 200); | ||
|
|
||
| // Set multiple attributes at once | ||
| span.setAttributes({ | ||
| "http.route": "/api/users", | ||
| "user.id": "user-42", | ||
| }); | ||
| }); | ||
| ``` | ||
|
|
||
| </SplitSectionCode> | ||
| </SplitSection> | ||
|
|
||
| Find more examples in our <PlatformLink to="/tracing/span-metrics/">Sending Span Metrics</PlatformLink> documentation. | ||
|
|
||
| </SplitLayout> | ||
|
|
||
| ## Distributed Tracing (Optional) | ||
|
|
||
| Distributed tracing works out of the box when tracing is enabled and works the same way in stream mode. If you need to manually propagate trace context, for example, | ||
| when the SDK can't instrument automatically, see <PlatformLink to="/tracing/distributed-tracing/custom-instrumentation/">Custom Trace Propagation</PlatformLink>. | ||
|
|
||
| ## Extended Configuration (Optional) | ||
|
|
||
| ### Filter Spans | ||
|
Contributor
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. I think this may be the right place for it, but somewhere on this page I believe we should mention with a slight bit more detail in the description that you also need to modify
Collaborator
Author
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. Updated this section to mention these changes in the text accompanying the code snippet. And then I referred to the "migration" note, where I've added the table with all name changes. |
||
|
|
||
| <SplitLayout> | ||
| <SplitSection> | ||
| <SplitSectionText> | ||
|
|
||
| To modify or redact span data before it's sent, use <PlatformLink to="/configuration/options/#beforeSendSpan">`beforeSendSpan`</PlatformLink>. In stream mode, wrap it with `Sentry.withStreamedSpan()` so the SDK applies it to spans as they are flushed rather than only at transaction time. | ||
|
|
||
| <Alert> | ||
| `beforeSendSpan` can only modify span data, and you cannot use it to drop | ||
| spans. Use [`ignoreSpans`](#drop-spans) instead. | ||
| </Alert> | ||
|
|
||
| The `span` object also has different property names in stream mode. For example, `span.op` becomes `span.attributes?.["sentry.op"]` and `span.description` becomes `span.name`. See the migration note below for the full list. | ||
|
|
||
| </SplitSectionText> | ||
| <SplitSectionCode> | ||
|
|
||
| <PlatformCategorySection supported={["server", "serverless"]}> | ||
|
|
||
| ```JavaScript | ||
| Sentry.init({ | ||
| dsn: "___PUBLIC_DSN___", | ||
| tracesSampleRate: 1.0, | ||
| traceLifecycle: "stream", | ||
| beforeSendSpan: Sentry.withStreamedSpan((span) => { | ||
| // In stream mode, 'op' is accessed via attributes | ||
| if (span.attributes?.["sentry.op"] === "db.query") { | ||
| // In stream mode, 'description' is now renamed to 'name' | ||
| span.name = "[filtered]"; | ||
| } | ||
| return span; | ||
| }), | ||
| }); | ||
| ``` | ||
|
|
||
| </PlatformCategorySection> | ||
|
|
||
| <PlatformCategorySection supported={["browser"]}> | ||
|
|
||
| <PlatformSection notSupported={["javascript.nextjs", "javascript.nuxt", "javascript.sveltekit", "javascript.astro", "javascript.solidstart", "javascript.capacitor", "javascript.tanstackstart-react", "javascript.remix", "javascript.react-router", "javascript.effect"]}> | ||
|
|
||
| ```javascript | ||
| Sentry.init({ | ||
| dsn: "___PUBLIC_DSN___", | ||
| tracesSampleRate: 1.0, | ||
| integrations: [ | ||
| // other integrations | ||
| Sentry.spanStreamingIntegration(), | ||
| ], | ||
| beforeSendSpan: Sentry.withStreamedSpan((span) => { | ||
| // In stream mode, 'op' is accessed via attributes | ||
| if (span.attributes?.["sentry.op"] === "db.query") { | ||
| // In stream mode, 'description' is now renamed to 'name' | ||
| span.name = "[filtered]"; | ||
| } | ||
| return span; | ||
| }), | ||
| }); | ||
| ``` | ||
|
|
||
| </PlatformSection> | ||
| </PlatformCategorySection> | ||
|
|
||
| </SplitSectionCode> | ||
| </SplitSection> | ||
| </SplitLayout> | ||
|
|
||
| <Expandable level="warning" title="Migrating from transaction mode?"> | ||
|
|
||
| If you're using `beforeSendSpan`, wrap it with `Sentry.withStreamedSpan()` as shown above, otherwise the SDK falls back to transaction mode. | ||
|
|
||
| Note that the `span` object is `StreamedSpanJSON` instead of `SpanJSON` and has different property names: | ||
|
|
||
| | Transaction Mode (`SpanJSON`) | Stream Mode (`StreamedSpanJSON`) | | ||
| | ---------------------------------- | ----------------------------------- | | ||
| | `span.description` | `span.name` | | ||
| | `span.data` (processed attributes) | `span.attributes` (raw attributes) | | ||
| | `span.timestamp` (end time) | `span.end_timestamp` | | ||
| | `span.status` (optional string) | `span.status` (`'ok'` or `'error'`) | | ||
| | `span.op` | `span.attributes['sentry.op']` | | ||
|
|
||
| If you're using `beforeSendTransaction` to drop spans, use [`ignoreSpans`](#drop-spans) instead, since `beforeSendTransaction` is not available in stream mode. | ||
|
|
||
| </Expandable> | ||
|
|
||
| ### Drop Spans | ||
|
Contributor
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. @Lms24 - just wanting to verify that this behavior is true: In transaction mode, ignoreSpans is evaluated at transaction end (so all attributes accumulated during the span's lifetime are available). In stream mode, it's evaluated at span start (so only initial attributes are considered). If so, @inventarSarah, it may be useful to add that detail or heads up here.
Collaborator
Author
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. Yes, you can find this down below at line 339+
Collaborator
Author
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. I moved the stream mode-specific explanation under the section header and left the stream mode vs transaction mode explanations in the migration callout below - wdyt? |
||
|
|
||
| In stream mode, `ignoreSpans` is evaluated at span start, so only the span name and attributes available at that point are taken into account. Any name updates or additional attributes added while the span is active won't influence whether the span is dropped. | ||
|
|
||
| <SplitLayout> | ||
| <SplitSection> | ||
| <SplitSectionText> | ||
|
|
||
| To prevent specific spans from being created, use the <PlatformLink to="/configuration/options/#ignoreSpans">`ignoreSpans`</PlatformLink> option: | ||
|
|
||
| </SplitSectionText> | ||
| <SplitSectionCode> | ||
|
|
||
| <PlatformCategorySection supported={["server", "serverless"]}> | ||
|
|
||
| ```javascript | ||
| Sentry.init({ | ||
| dsn: "___PUBLIC_DSN___", | ||
| tracesSampleRate: 1.0, | ||
| traceLifecycle: "stream", | ||
| ignoreSpans: [ | ||
| // Drop spans whose name contains "healthcheck" | ||
| "healthcheck", | ||
| // Drop spans whose name matches a pattern | ||
| /^GET \/api\/v1\/internal/, | ||
| // Drop spans matching name and attribute conditions | ||
| { | ||
| name: /^GET \//, | ||
| attributes: { | ||
| "http.route": "/api/status", | ||
| }, | ||
| }, | ||
| ], | ||
| }); | ||
| ``` | ||
|
|
||
| </PlatformCategorySection> | ||
|
|
||
| <PlatformCategorySection supported={["browser"]}> | ||
|
|
||
| <PlatformSection notSupported={["javascript.nextjs", "javascript.nuxt", "javascript.sveltekit", "javascript.astro", "javascript.solidstart", "javascript.capacitor", "javascript.tanstackstart-react", "javascript.remix", "javascript.react-router", "javascript.effect"]}> | ||
|
|
||
| ```javascript | ||
| Sentry.init({ | ||
| dsn: "___PUBLIC_DSN___", | ||
| tracesSampleRate: 1.0, | ||
| integrations: [ | ||
| // other integrations | ||
| Sentry.spanStreamingIntegration(), | ||
| ], | ||
| ignoreSpans: [ | ||
| // Drop spans whose name contains "healthcheck" | ||
| "healthcheck", | ||
| // Drop spans whose name matches a pattern | ||
| /^GET \/api\/v1\/internal/, | ||
| // Drop spans matching name and attribute conditions | ||
| { | ||
| name: /^GET \//, | ||
| attributes: { | ||
| "http.route": "/api/status", | ||
| }, | ||
| }, | ||
| ], | ||
| }); | ||
| ``` | ||
|
|
||
| </PlatformSection> | ||
|
|
||
| </PlatformCategorySection> | ||
|
|
||
| </SplitSectionCode> | ||
| </SplitSection> | ||
| </SplitLayout> | ||
|
|
||
| If a matching span is a service span, all of its child spans are dropped as well. If a child span matches, only that span is dropped and its children are reparented to the nearest ancestor. | ||
|
|
||
| <Alert level="warning" title="Migrating from transaction mode?"> | ||
|
|
||
| In transaction mode, `ignoreSpans` is evaluated at transaction end rather than at span start. Review your existing rules to make sure the attributes and names you're matching on are passed when the span is created. | ||
|
|
||
| If you're auto-instrumenting and don't know what the initial name of a span is when it starts, enable SDK debug logging during development by setting <PlatformLink to="/configuration/options/#debug">`debug: true`</PlatformLink> when initializing the SDK. | ||
|
|
||
| </Alert> | ||
|
|
||
| ## Verify Your Setup | ||
|
|
||
| To make sure you've enabled stream mode successfully: | ||
|
|
||
| <PlatformCategorySection supported={["server", "serverless"]}> | ||
|
|
||
| <PlatformSection notSupported={["javascript.nextjs", "javascript.nuxt", "javascript.sveltekit", "javascript.astro", "javascript.solidstart", "javascript.capacitor", "javascript.tanstackstart-react", "javascript.remix", "javascript.react-router", "javascript.effect"]}> | ||
|
|
||
| - **Check the Sentry dashboard**: Spans should appear in the Traces view shortly after they complete. Traces look the same as in transaction mode, but without transactions. | ||
|
Member
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 is true but unless I missed something, there are very few visual indications that actually point out that a span is a (new) span and not a transaction 😬 The only thing that comes to mind is that it's not showing a JSON button anymore. I don't think there's harm in leaving in this sentence but users might get confused at what they should be looking in the product. This is of course not a primary docs issue but rather a product/UI issue. We might want to think about some kind of indicator (and, separate topic, please bring back JSON for streamed spans lol). That is assuming, I didn't miss any obvious indication 😅 Will ask product folks about this.
Collaborator
Author
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. In that case, I will update this to something like
Contributor
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. In the new experience, what do we call those attributes that we currently call
Collaborator
Author
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. @Lms24 Have you received a response from the product team yet?
Member
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. No response so far :( I suggest for now we go with the more generic sentence. If we decide to mark them specifically in the product, we can update this section. (I'm hacking on a small visual indication but no guarantees that this will get approved/merged).
Member
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. I opened getsentry/sentry#116386 which proposes a visual indication. Can't promise that this gets merged though yet. Will keep you posted! |
||
| - **Check for fallback warnings in your logs**: If the SDK logs warnings about falling back to transaction mode, your `beforeSendSpan` callback is likely missing the `Sentry.withStreamedSpan()` wrapper. | ||
|
|
||
| </PlatformSection> | ||
|
|
||
| </PlatformCategorySection> | ||
|
|
||
| <PlatformCategorySection supported={["browser"]}> | ||
|
|
||
| - **Check the Sentry dashboard**: Spans should appear in the Traces view shortly after they complete. Traces look similar to transaction mode, but contain only spans and no transactions. | ||
| - **Check for fallback warnings in your logs**: If the SDK logs warnings about falling back to transaction mode, your `beforeSendSpan` callback is likely missing the `Sentry.withStreamedSpan()` wrapper. | ||
| - **Check the network tab in your browser's DevTools**: Span envelopes should appear as individual requests with content type `application/vnd.sentry.items.span.v2+json` | ||
|
|
||
| </PlatformCategorySection> | ||
Uh oh!
There was an error while loading. Please reload this page.