Instrumenting an app · 5 min read · Updated 2026-09-22
Which call to make
The chapter you keep open while you write. A failure, an intent, a duration, or context — pick one call, or pick none.
Part 2 of 8 · Next: Errors in application code
Engineers say “should I log this?” and mean five different jobs. They mean “record that it failed,” “record that someone did it,” “record how long it took,” “leave a trail in case it fails later,” or “I am debugging locally and I want stdout.” Talaria has a call for the first four. The fifth belongs in your process logs.
The table below is a checkout — storefront plus API, SKU TR-900. The same rule applies if your core action is signup, upgrade, or publish.
Which call?
Checkout
What just happened in the code?
A failure?
captureException
Becomes an issue
Someone did a thing?
analytics.track
Intent, not breakage
Work took time?
startSpan
Shows up on the waterfall
Context before a failure?
addBreadcrumb
Cheap trail, no meter
If none of the four fit, leave it in stdout. Do not invent a fifth Talaria call.
The four calls
captureException— a throwable you want grouped into an issue. Default levelerror. This is the one that pages people.analytics.track(orpage/identify) — a named thing a person did, including successes. Most analytics events are not failures.startTransaction/startSpan— timed work. The root is what Talaria meters; children ride along. Auto instrumentation already covers the HTTP request and many DB / fetch calls.addBreadcrumb— a cheap trail (nav, click, “opened checkout”) attached to thenextevent. Breadcrumbs are not a meter. They are also not a product analytics stream.
captureMessage and logger helpers such as Talaria::info() exist. They create events, they share the event meter with crashes, and they are the wrong default. Use them when something is operationally important and is not an exception — a webhook signature that failed closed, a job you dead-lettered on purpose. Do not use them as printf.
Call sites on a checkout
These are the lines that actually appear in a checkout. Read the “Use” column as the primary call. You can attach more than one signal to the same moment — a failed charge is an exception and a checkout_failed track — but you should never substitute one for another.
| In the code | Use | Do not |
|---|---|---|
| Stripe throws on charge | captureException | track('Error') or a span-only catch |
| Charge succeeds | analytics.track('order_completed') on the server | A success captureMessage |
| Customer clicks Add to cart | analytics.track('add_to_cart') | captureMessage('added to cart') |
| Checkout form mounts | addBreadcrumb and track('checkout_started') | An info event for every mount |
| User signs in | identify(userId) + setUser | Putting the email in a property named email |
| Landing page loads | page() + the pageload transaction | A custom span named homepage |
| PDO runs INSERT orders | Auto DB span (or a short manual span around a batch) | track of the SQL |
| N queries in a loop | Fix the query; the N+1 view will show it | A span per row with the primary key in the name |
| Outbound fetch to the API | Auto HTTP span + W3C traceparent | Wrapping Talaria’s own ingest client |
| Payment HTTP to Stripe | startSpan if the client is not auto-wrapped | Card numbers in span attributes |
| Feature flag checked | Tag or breadcrumb if it will explain a failure | An event per flag per request |
| 404 on a missing SKU | Usually nothing — or a breadcrumb | An error issue for every bot |
| Expected validation (“size required”) | track with a reason, or a breadcrumb | captureException on a user mistake |
| Debug dump of the cart object | stdout / your log product | Talaria extra, or a 16 KB analytics property map |
| Job retried, then dead-lettered | captureMessage at error, once | An info event on every retry |
Fifteen moments on a checkout. The “Do not” column is how a dashboard fills with noise.
Anti-patterns
Tracking exceptions
analytics.track('checkout_error', { message: String(e) }) does not create an issue. It does not fingerprint. It does not get a stack. It will not reopen when the same crash comes back. The catch block calls captureException. If you also want funnel drop-off for “people who failed to pay,” fire checkout_failed as well — a named intent that the charge did not complete, not a dump of the throwable.
A message on every request
Talaria::info('Checkout opened') at the top of the controller is how you discover the event meter. Ten thousand successful checkouts become ten thousand events, grouped badly, sitting next to real crashes. The request is already a transaction once tracing is on. The “opened” moment is a breadcrumb or checkout_started.
Spans for business facts
A span named user.upgraded_plan with no meaningful duration is an analytics event wearing a stopwatch. Spans exist so you can ask “where did the time go?” If the answer is “there was no time, only a fact,” use track.
Stacks and carts in properties
Analytics properties are a flat map: 64 keys, 1024-character strings, 16 KB for the whole object or the map is dropped. Extra on an error event is for cart_id, not the serialized cart. Card numbers, emails, and tokens are stripped or should never have been passed — see privacy and consent.
IDs in the name
POST /checkout/cart_01H… as a transaction name, or product_TR900_viewed as an event name, explodes the group-by. Name the route and the intent. Put the id in an attribute or a property.
The rule you can memorize
- It threw and you did not expect it —
captureException. - A person finished (or abandoned) a step you care about —
track. - A unit of work has a start and an end you might need to explain — span.
- It will only matter if something fails later — breadcrumb.
- You are staring at a local bug — stdout.
Related guides
Instrumenting an app · 5 min
Instrumenting an appFour signals, one checkout, and a rule for every line of application code: capture, span, track, breadcrumb — or leave it alone.
Instrumenting an app · 5 min
Errors in application codeTalaria events are exceptions and the rare message that should become an issue. Everything else is a breadcrumb, a span, or stdout.
Instrumenting an app · 4 min
Tracing in application codeA transaction is the request. A span is a timed child. You name the route, you opt in, and you let auto instrumentation do the dull work.
Instrumenting an app · 4 min
Analytics in application codeName the intent, fire it once, identify the person, and put the charge on the server — the browser never saw the card succeed.