Skip to content

Commit 7b1ffa0

Browse files
committed
Simplify examples to single-span patterns (no nested spans). Emphasize auto-instrumentation; keep attributes minimal and business-focused for easier customization.
1 parent 992cf98 commit 7b1ffa0

File tree

1 file changed

+44
-69
lines changed
  • docs/platforms/javascript/common/tracing/span-metrics

1 file changed

+44
-69
lines changed

docs/platforms/javascript/common/tracing/span-metrics/examples.mdx

Lines changed: 44 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -65,41 +65,28 @@ Where to put this in your app:
6565
- In the `onClick` for the checkout CTA, or inside the submit handler of your checkout form/container component.
6666
- Auto-instrumentation will add client `fetch` spans; keep the explicit UI span for business context.
6767
68-
**Backend — instrument each business step:**
68+
**Backend — single span per request; rely on auto-instrumentation:**
6969
7070
```javascript
7171
// Example: Node/Express
7272
app.post("/api/checkout", async (req, res) => {
73-
await Sentry.startSpan({ name: "Process order", op: "business.logic" }, async (orderSpan) => {
73+
await Sentry.startSpan({ name: "Checkout (server)", op: "http.server" }, async (span) => {
7474
try {
75-
await Sentry.startSpan({ name: "Validate cart", op: "validate" }, async () => {
76-
// validate item availability, pricing, coupon
77-
});
78-
79-
const payment = await Sentry.startSpan({ name: "Authorize payment", op: "payment" }, async (span) => {
80-
span.setAttribute("payment.provider", "stripe");
81-
// call payment provider
82-
return { id: "pi_123", status: "authorized" };
83-
});
84-
orderSpan.setAttribute("payment.status", payment.status);
85-
86-
await Sentry.startSpan({ name: "Reserve inventory", op: "inventory" }, async () => {
87-
// reserve stock in DB or external OMS
88-
});
89-
90-
const orderId = await Sentry.startSpan({ name: "Create order", op: "db.write" }, async () => {
91-
// insert order; return internal id
92-
return "ord_abc123";
93-
});
94-
orderSpan.setAttribute("order.id", orderId);
95-
96-
await Sentry.startSpan({ name: "Send confirmation", op: "email" }, async () => {
97-
// enqueue email or call provider
75+
// validate, authorize payment, reserve inventory, create order, send email
76+
const orderId = await createOrder(req.body);
77+
78+
// Keep attributes low-cardinality and business-focused
79+
span.setAttributes({
80+
"order.id": orderId,
81+
"payment.provider": "stripe",
82+
"payment.status": "authorized",
83+
"inventory.reserved": true,
84+
"email.enqueued": true,
9885
});
9986

10087
res.json({ orderId, paymentProvider: "stripe" });
10188
} catch (e) {
102-
orderSpan.setStatus?.("error");
89+
span.setStatus?.("error");
10390
res.status(500).json({ error: "Checkout failed" });
10491
}
10592
});
@@ -108,7 +95,7 @@ app.post("/api/checkout", async (req, res) => {
10895
10996
**How the trace works together:**
11097
- UI span starts on click → fetch carries trace headers → backend continues the trace.
111-
- Child spans highlight where time is spent (validation, payment, inventory, DB, email).
98+
- Rely on your SDK's auto-instrumentation for HTTP/DB/external calls; keep this example span simple.
11299
- Span metrics let you track latency percentiles and failure rates by attributes like `payment.provider`, `cart.item_count`.
113100
114101
What to monitor with span metrics:
@@ -122,7 +109,7 @@ What to monitor with span metrics:
122109
123110
**Solution:** Start a client span when the upload begins; on the backend, create spans for signed-URL issuance, enqueue a job, and instrument worker phases. Propagate trace context via job metadata to stitch the traces.
124111
125-
**Frontend (React) — instrument upload begin and progress:**
112+
**Frontend (React) — instrument upload begin and completion:**
126113
127114
```javascript
128115
Sentry.startSpan(
@@ -132,27 +119,28 @@ Sentry.startSpan(
132119
attributes: {
133120
"file.size_bytes": file.size,
134121
"file.mime_type": file.type,
135-
"upload.chunked": true,
136122
},
137123
},
138124
async (span) => {
125+
const t0 = performance.now();
139126
try {
140127
const urlRes = await fetch("/api/uploads/signed-url", { method: "POST" });
141128
const { uploadUrl, objectKey } = await urlRes.json();
142129

143-
// Use XHR for progress; fetch is illustrative here
144-
await uploadWithProgress(uploadUrl, file, (progress) => {
145-
span.setAttribute("upload.bytes_transferred", progress.bytes);
146-
span.setAttribute("upload.percent_complete", progress.percent);
147-
});
130+
// Upload file to storage (signed URL)
131+
await fetch(uploadUrl, { method: "PUT", body: file });
148132

133+
// Tell backend to start async processing
149134
await fetch("/api/uploads/start-processing", {
150135
method: "POST",
151136
headers: { "content-type": "application/json" },
152137
body: JSON.stringify({ key: objectKey }),
153138
});
154139

155-
span.setAttribute("upload.success", true);
140+
span.setAttributes({
141+
"upload.success": true,
142+
"upload.duration_ms": Math.round(performance.now() - t0),
143+
});
156144
} catch (e) {
157145
span.setStatus?.("error");
158146
Sentry.captureException(e);
@@ -165,15 +153,13 @@ Sentry.startSpan(
165153
166154
Place this where the user triggers an upload (dropzone onDrop, file input onChange, or explicit Upload button).
167155
168-
**Backend — signed URL, enqueue job, and worker phases:**
156+
**Backend — single span per handler; single span in worker:**
169157
170158
```javascript
171159
// Issue signed URL
172160
app.post("/api/uploads/signed-url", async (req, res) => {
173-
await Sentry.startSpan({ name: "Get signed URL", op: "storage.sign" }, async (span) => {
174-
span.setAttribute("storage.provider", "s3");
175-
span.setAttribute("storage.bucket", "media");
176-
// generate URL
161+
await Sentry.startSpan({ name: "Signed URL", op: "storage.sign" }, async (span) => {
162+
span.setAttributes({ "storage.provider": "s3", "storage.bucket": "media" });
177163
res.json({ uploadUrl: "https://s3...", objectKey: "uploads/abc.jpg" });
178164
});
179165
});
@@ -191,14 +177,13 @@ app.post("/api/uploads/start-processing", async (req, res) => {
191177
// Worker
192178
worker.on("message", async (msg) => {
193179
const parentContext = msg.trace; // restore trace/parent if available
194-
await Sentry.startSpan({ name: "Process media", op: "worker.job", parentContext }, async (jobSpan) => {
195-
await Sentry.startSpan({ name: "Virus scan", op: "security.scan" }, async (span) => {
196-
span.setAttribute("scan.engine", "clamav");
180+
await Sentry.startSpan({ name: "Process media", op: "worker.job", parentContext }, async (span) => {
181+
// Do work (scan, transcode, thumbnail) — rely on auto-instrumentation for sub-operations
182+
span.setAttributes({
183+
"scan.engine": "clamav",
184+
"transcode.preset": "720p",
185+
"thumbnail.created": true,
197186
});
198-
await Sentry.startSpan({ name: "Transcode", op: "media.transcode" }, async (span) => {
199-
span.setAttribute("transcode.preset", "720p");
200-
});
201-
await Sentry.startSpan({ name: "Thumbnail", op: "media.thumbnail" }, async () => {});
202187
});
203188
});
204189
```
@@ -269,31 +254,21 @@ async function runSearch(query, debounceMs = 150) {
269254
270255
Place this in your search input hook/component after applying a debounce.
271256
272-
**Backend — cache, search engine, rank:**
257+
**Backend — single span with useful attributes:**
273258
274259
```javascript
275260
app.get("/api/search", async (req, res) => {
276261
const q = String(req.query.q || "");
277-
278-
await Sentry.startSpan({ name: "Search", op: "search" }, async (root) => {
279-
const cacheHit = await Sentry.startSpan({ name: "Cache lookup", op: "cache" }, async (span) => {
280-
const hit = await cache.get(q);
281-
span.setAttribute("cache.hit", Boolean(hit));
282-
if (hit) res.json(hit);
283-
return Boolean(hit);
284-
});
285-
if (cacheHit) return;
286-
287-
const results = await Sentry.startSpan({ name: "Query engine", op: "external.search" }, async (span) => {
288-
span.setAttribute("search.engine", "elasticsearch");
289-
span.setAttribute("search.index", "products_v2");
290-
span.setAttribute("search.mode", q.length < 3 ? "prefix" : "fuzzy");
291-
return searchEngine.query(q);
292-
});
293-
294-
await Sentry.startSpan({ name: "Rank results", op: "compute.rank" }, async (span) => {
295-
span.setAttribute("rank.model", "bm25");
296-
span.setAttribute("rank.version", "2.1");
262+
await Sentry.startSpan({ name: "Search", op: "search" }, async (span) => {
263+
const hit = await cache.get(q);
264+
span.setAttribute("cache.hit", Boolean(hit));
265+
if (hit) return res.json(hit);
266+
267+
const results = await searchEngine.query(q);
268+
span.setAttributes({
269+
"search.engine": "elasticsearch",
270+
"search.mode": q.length < 3 ? "prefix" : "fuzzy",
271+
"results.count": results.length,
297272
});
298273

299274
res.json({ results });
@@ -303,7 +278,7 @@ app.get("/api/search", async (req, res) => {
303278
304279
**How the trace works together:**
305280
- Each debounced request becomes a client span; aborted ones are marked and short.
306-
- Server spans show cache effectiveness, engine latency, and ranking time.
281+
- The server span shows cache effectiveness and search mode; auto-instrumentation will cover network/DB latency.
307282
308283
What to monitor with span metrics:
309284
- p95 latency of `op:http.client` by `query.length` bucket.

0 commit comments

Comments
 (0)