@@ -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
7272app .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
114101What 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
128115Sentry .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
166154Place 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
172160app .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
192178worker .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
270255Place 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
275260app .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
308283What to monitor with span metrics:
309284- p95 latency of ` op: http .client ` by ` query .length ` bucket.
0 commit comments