Anatomy of a span
A single DB query span
name: db.query
trace_id: 0af7651916cd43dd8448eb211c80319c
span_id: b7ad6b7169203331
parent_id: 0f9e8d7c6b5a4392
start_time: 2026-04-13T14:23:05.240Z
end_time: 2026-04-13T14:23:05.260Z
duration_ms: 20
attributes:
db.system: postgresql
db.statement: SELECT * FROM orders WHERE user_id = ? LIMIT 10
db.rows: 10
laravel.connection: pgsql
status:
code: OKThe span tells you everything you need to reconstruct that one operation's timing, inputs, and outcome — but nothing about the surrounding request. That's what the parent span is for.
Parent-child relationships
Spans form a tree via parent_id. The root span (the HTTP request) has no parent. Every other span points to its parent. The tracing backend reconstructs the tree from these pointers to render the waterfall.
Attributes
Spans carry structured key-value metadata — attributes. OpenTelemetry defines semantic conventions so every vendor uses the same attribute names for the same concepts:
http.method,http.status_code,http.route— HTTP spansdb.system,db.statement,db.operation— DB spansmessaging.system,messaging.destination— queue spansexception.type,exception.message,exception.stacktrace— error spans
Creating custom spans in Laravel
For an expensive internal operation that isn't captured by auto-instrumentation:
use OpenTelemetry\API\Globals;
$tracer = Globals::tracerProvider()->getTracer('app');
$span = $tracer->spanBuilder('image.resize')
->setAttribute('image.width', $width)
->setAttribute('image.height', $height)
->startSpan();
$scope = $span->activate();
try {
$resized = $this->resize($image, $width, $height);
$span->setStatus(StatusCode::STATUS_OK);
} catch (\Throwable $e) {
$span->recordException($e);
$span->setStatus(StatusCode::STATUS_ERROR);
throw $e;
} finally {
$scope->detach();
$span->end();
}Laravel-idiomatic alternative: wrap the operation in a macro or service method and have NightOwl's Nightwatch integration surface it via its own span abstraction.
Span events vs child spans
A span event is a timestamped annotation within a span — not a child span, just a marker. Use events for point-in-time signals (cache.hit, retry.attempted). Use child spans when the thing has its own duration worth tracking. Events are cheaper but less queryable.