From fac1de3edf1b22fdcac666af83233a5b1424b349 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Tue, 5 May 2026 16:20:02 +0200 Subject: [PATCH] docs(js): add Node stream spans guide --- .../node/tracing/stream-spans/index.mdx | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 docs/platforms/javascript/guides/node/tracing/stream-spans/index.mdx diff --git a/docs/platforms/javascript/guides/node/tracing/stream-spans/index.mdx b/docs/platforms/javascript/guides/node/tracing/stream-spans/index.mdx new file mode 100644 index 00000000000000..3e4c257ef8cf50 --- /dev/null +++ b/docs/platforms/javascript/guides/node/tracing/stream-spans/index.mdx @@ -0,0 +1,174 @@ +--- +title: Stream Spans +description: "Learn how to use streamed spans to remove the 1000 span limit, reduce memory usage, and get faster visibility into your traces." +sidebar_order: 50 +new: true +--- + +By default, the Sentry Node.js SDK collects all spans in memory and sends them as a single transaction once the root span ends. Streamed spans change this model: spans are sent incrementally in batches as they finish, rather than waiting for the entire transaction to complete. + +## Why Use Streamed Spans + +- **No 1000 span limit.** Transactions are capped at 1000 spans. With streamed spans, there is 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. With transactions, a crash before the root span ends means all span data is lost. + +## Enable Streamed Spans + +Opt in by setting the `traceLifecycle` option to `'stream'` when initializing the SDK: + +```javascript {filename:instrument.js} +const Sentry = require("@sentry/node"); + +Sentry.init({ + dsn: "___PUBLIC_DSN___", + tracesSampleRate: 1.0, + traceLifecycle: "stream", +}); +``` + +When `traceLifecycle` is set to `'static'` (the default), traces are sent as transactions. Setting it to `'stream'` enables span streaming. + +## Start a Span + +Starting spans works the same way as with transactions. Use `Sentry.startSpan()` to create a span that is automatically ended when the callback completes: + +```javascript +const result = await Sentry.startSpan( + { name: "my-operation", attributes: { "my.attribute": "value" } }, + async () => { + // Your code here + return await doWork(); + } +); +``` + +Child spans created inside the callback are automatically associated with the parent: + +```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(); + }); +}); +``` + +With streaming enabled, each span is flushed to Sentry shortly after it ends instead of being held until the parent completes. + +For more details on span creation APIs (`startSpan`, `startSpanManual`, `startInactiveSpan`), see Instrumentation. + +## Attach Attributes + +Attributes let you attach structured metadata to spans. You can set them when starting a span: + +```javascript +Sentry.startSpan( + { + name: "process-order", + op: "queue.process", + attributes: { + "order.id": "abc-123", + "order.item_count": 5, + "order.priority": true, + }, + }, + () => { + // Process the order + } +); +``` + +Or add them to an already running span: + +```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", + }); +}); +``` + +Attribute values can be `string`, `number`, or `boolean`, as well as arrays of these types. + +## Continue a Trace + +When you receive a request from an upstream service that includes Sentry trace headers, use `Sentry.continueTrace()` to connect your spans to the existing distributed trace: + +```javascript {filename:server.js} +const http = require("http"); +const Sentry = require("@sentry/node"); + +http.createServer((req, res) => { + Sentry.continueTrace( + { + sentryTrace: req.headers["sentry-trace"], + baggage: req.headers["baggage"], + }, + () => { + Sentry.startSpan({ name: `${req.method} ${req.url}` }, () => { + // Handle the request + res.end("OK"); + }); + } + ); +}); +``` + +To propagate the trace to downstream services, inject the trace headers into your outgoing requests: + +```javascript +await Sentry.startSpan({ name: "call-downstream" }, async () => { + const traceData = Sentry.getTraceData(); + + await fetch("https://downstream.example.com/api", { + headers: { + "sentry-trace": traceData["sentry-trace"], + baggage: traceData["baggage"], + }, + }); +}); +``` + +## Start a New Trace + +If you need to start a completely new trace that is not connected to the current one, use `Sentry.startNewTrace()`. This is useful for background jobs or scheduled tasks where you want a clean trace boundary: + +```javascript {filename:worker.js} +const Sentry = require("@sentry/node"); + +function processJob(job) { + Sentry.startNewTrace(() => { + Sentry.startSpan( + { + name: "process-job", + attributes: { "job.id": job.id, "job.type": job.type }, + }, + async () => { + await job.execute(); + } + ); + }); +} +``` + +Any spans created inside the `startNewTrace` callback belong to a fresh trace with a new trace ID. Once the callback ends, the SDK continues the previous trace (if one was active). + +## How Span Flushing Works + +When span streaming 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 1000 spans. +- When you call `Sentry.flush()` or `Sentry.close()`. + +Each flush sends only the spans that have accumulated since the last flush, grouped into envelopes by trace ID.