Traces & Spans
debt(d7/e3/b5/t5)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints.tools field is not specified. Missing span propagation, coarse spans, or absent attributes won't be flagged by compilers or default linters. Identifying these issues requires either careful code review or observing the resulting trace UI in a staging/production environment — you only discover that context isn't propagating when the trace tree is broken in the backend.
Closest to 'simple parameterised fix' (e3). The quick_fix notes that auto-instrumentation (open-telemetry/opentelemetry-auto-pdo) can be installed with zero code changes for PDO spans. However, common mistakes like missing trace context propagation across service boundaries or adding meaningful span attributes require touching multiple call sites (forwarding traceparent headers, annotating spans), making it slightly more than a single-line patch but still localised within one component or service boundary.
Closest to 'persistent productivity tax' (b5). Tracing infrastructure applies to both web and CLI contexts (per applies_to). Once adopted, every future developer must understand span naming conventions, attribute requirements, sampling strategy, and context propagation rules. This is a persistent tax across many work streams — new endpoints, new background jobs, new service integrations all require trace-awareness — but it doesn't fundamentally redefine the system's architecture.
Closest to 'notable trap — a documented gotcha most devs eventually learn' (t5). The misconception field explicitly states that developers wrongly believe tracing is only for microservices, missing its value in monoliths. Additionally, common mistakes around not propagating traceparent headers and sampling at 100% are well-documented gotchas that most developers encounter after initial adoption. These are notable but not catastrophic — the errors are correctable once discovered.
Also Known As
TL;DR
Explanation
A trace is a collection of spans that together describe how a single request moved through a distributed system. Each span records: operation name, start timestamp, duration, service name, status (success/error), and attributes (key-value metadata). Spans form a tree — the root span is the initial entry point (HTTP request, queue message, scheduled job); child spans represent downstream calls (database queries, external HTTP calls, cache lookups, sub-function calls). The parent–child relationship is maintained via a trace ID (shared by all spans in a trace) and a span ID (unique per span, referenced by children as their parent ID). In PHP, OpenTelemetry is the standard SDK for creating and exporting spans. Traces are exported to collectors like Jaeger, Zipkin, or Datadog, which visualise the span tree and highlight bottlenecks.
Common Misconception
Why It Matters
Common Mistakes
- Creating spans that are too coarse — a single span for 'process order' hides where time is spent; instrument individual database queries and external calls.
- Not propagating trace context across service boundaries — HTTP headers (traceparent) must be forwarded to downstream services for the trace tree to connect.
- Sampling 100% of requests in production — at high traffic, full sampling generates enormous data volume; use head-based sampling (1-10%) or tail-based sampling (keep traces with errors).
- Not adding attributes to spans — a span named 'db.query' with no attributes is useless; add the query, table name, and affected rows.
Code Examples
// No tracing — request slow, no idea why
public function processOrder(int $orderId): void {
$order = $this->db->query('SELECT * FROM orders WHERE id = ?', [$orderId]);
foreach ($order->items as $item) {
$product = $this->db->query('SELECT * FROM products WHERE id = ?', [$item->product_id]);
// N+1 — invisible in logs, obvious in traces
}
}
// Manual spans — each operation visible in trace UI
public function processOrder(int $orderId): void {
$span = $this->tracer->spanBuilder('process_order')
->setAttribute('order.id', $orderId)
->startSpan();
$scope = $span->activate();
try {
// PDO auto-instrumentation creates child spans automatically
$order = $this->db->query('SELECT * FROM orders WHERE id = ?', [$orderId]);
$span->setAttribute('order.item_count', count($order->items));
// ... rest of processing
$span->setStatus(StatusCode::STATUS_OK);
} catch (Throwable $e) {
$span->recordException($e)->setStatus(StatusCode::STATUS_ERROR);
throw $e;
} finally {
$scope->detach();
$span->end();
}
}