Span
One timed operation inside a trace: a name, a duration, and a parent, drawn as a single bar in the request waterfall.
See it
What it is
A span is the atom of tracing. It carries a name ('GET /checkout', 'SELECT orders'), a start time, a duration, its own span ID, the trace ID it belongs to, and a parent span ID. Nest spans by parent and you get the familiar waterfall: the request bar contains service bars, which contain query bars.
What makes a span useful is not the timing, it is the attributes: key-value tags like http.route, db.system, plan tier, cache hit or miss, retry count. Attributes are how you ask 'show me spans over 500ms where cache_hit was false' instead of squinting at charts. Spans also carry events (timestamped notes inside the span, typically an exception) and a status of ok or error. OpenTelemetry's semantic conventions name the common attributes for you, so any backend can chart them without per-app config.
Gotchas: a span you start and never end shows up missing or absurdly long, which is exactly what happens when an error path skips the close, so use the SDK's wrapping API instead of manual start and end calls. And resist spraying high-cardinality values (raw user IDs, full SQL with inlined parameters, whole request bodies) into attributes: it inflates cost and pushes PII into a system with looser access rules than your database.
Ask AI for it
Add manual OpenTelemetry spans around the key operations in this code. Wrap each unit of work with tracer.startActiveSpan inside a try/finally so the span always ends even when the call throws. Name spans as 'verb object' ('charge payment', 'render invoice pdf'), and attach attributes for the inputs worth filtering on (operation, route, item count, retry count, cache hit, result size), using OTel semantic convention names where they exist. Call span.recordException and set status to ERROR on failure. Do not put user emails, tokens, or raw SQL parameter values in attributes.