Spans & Traces
debt(d9/e5/b5/t5)
Closest to 'silent in production until users hit it' (d9). The detection_hints note automated=no and the only tool listed is opentelemetry itself (the instrumentation framework, not a linter/analyzer). Missing spans, wrong granularity, or missing ERROR status produce no warnings — traces simply look incomplete or healthy while bottlenecks go undetected until users complain.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix requires adding spans across DB queries, HTTP calls, cache operations, and business steps, plus setting ERROR status on exceptions and adding attributes. This isn't a single-line swap — it requires instrumenting multiple call sites across the codebase, though it doesn't necessarily require architectural rework.
Closest to 'persistent productivity tax' (b5). The term applies_to web, cli, and queue-worker contexts, meaning span granularity decisions affect all major execution paths. Poor instrumentation decisions (too coarse or too fine) persistently degrade the value of observability across the codebase and require ongoing maintenance as new features are added.
Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception is that spans must represent function calls, when they can represent any logical operation. The common_mistakes confirm this: developers default to one span per request or per line of code, and critically miss setting span status=ERROR on exceptions — making traces appear successful when they are not. These are well-documented gotchas that most developers encounter.
TL;DR
Explanation
Trace: collection of spans sharing a trace ID. Root span: first span in a request. Child spans: nested operations (DB query, HTTP call, cache lookup). Span attributes: key-value data (db.statement, http.url, user.id). Span events: timestamped events within a span (cache.miss). Span status: OK, ERROR, UNSET. Span kind: SERVER, CLIENT, PRODUCER, CONSUMER, INTERNAL. W3C Trace Context: traceparent header carries (version, trace-id, parent-id, flags). Sampling: recording every span is expensive — sample 1-10% in production or use head/tail-based sampling.
Common Misconception
Why It Matters
Common Mistakes
- One span per request — no internal visibility.
- Span per line of code — noise and cost.
- Missing DB spans — usually the most important.
- Not setting span status=ERROR on exception — traces look successful.
Code Examples
// Single span for entire request — no internal visibility:
$span = $tracer->spanBuilder('handleRequest')->startSpan();
try { handleRequest(); } finally { $span->end(); }
// Nested spans:
$requestSpan = $tracer->spanBuilder('POST /orders')->setSpanKind(SpanKind::SERVER)->startSpan();
$dbSpan = $tracer->spanBuilder('SELECT orders')->setSpanKind(SpanKind::CLIENT)->startSpan();
$dbSpan->setAttribute('db.statement', 'SELECT * FROM orders WHERE id = ?');
try { $result = $pdo->query(...); }
catch (Exception $e) { $dbSpan->setStatus(StatusCode::ERROR, $e->getMessage()); throw $e; }
finally { $dbSpan->end(); }