Metrics tell you what changed. Logs tell you why something happened. Traces tell you where time was spent and how a request moved across your system.
At the heart of distributed tracing in OpenTelemetry are two core concepts:
Trace : The full journey of one request / transaction across services.
: The full journey of one request / transaction across services. Span: A timed unit of work inside that journey (function call, DB query, external API call, queue processing, etc.).
This guide walks through how traces and spans are structured, how context is propagated, how to model work properly, and how to implement everything in Node.js / TypeScript with practical patterns you can copy into production.
Table of Contents
Core Concepts Anatomy of a Span Visual Mental Model Setting Up Tracing in Node.js Manual Instrumentation Basics Express / Fastify Middleware Example Database + External Call Spans Error Recording & Status Attributes, Events & Links Context Propagation (Sync + Async) Sampling (Head & Tail) Span Naming Best Practices Common Anti-Patterns Putting It All Together Next Steps (Correlating with Metrics & Logs)
1. Core Concepts
Concept Description (Plain Language) Trace The full story of a single request. Made of many spans. Has a trace_id so all pieces stay linked. Span One timed step in that story (e.g., DB query, HTTP call, function). Starts, ends, and carries metadata. Root Span The first span (no parent). Usually the inbound HTTP request, queue message, or scheduled job trigger. Child Span A smaller step inside a bigger one. Lets you break work into clear pieces. Context The “current trace + active span” that rides along your async calls and network hops so new spans attach correctly. Sampler The rule that decides: keep (record/export) this trace or drop it to save cost/noise. Exporter The piece that ships finished spans to your backend (OneUptime, Jaeger, Tempo, etc.).
Quick analogy (HTTP request version): A trace is the entire lifecycle of one HTTP request from accept to response. The root span is the server receiving the request. Child spans are each stage: auth middleware, controller handler, database query, cache lookup, external API call, response serialization. Context is the request-scoped metadata that flows through each layer so new spans attach correctly. The sampler is the decision point that says “do we keep detailed timing for this request or drop it?” The exporter is the component that ships the finished request timeline to your observability backend (OneUptime, Jaeger, Tempo, etc.).
... continue reading