plunkettscott/laravel-otel
OpenTelemetry integration for Laravel that instruments HTTP requests, database queries, queues, and console commands to emit traces, metrics, and logs through OTLP exporters, with middleware, service provider wiring, and publishable config.
plunkettscott/laravel-otel wires Laravel into the OpenTelemetry PHP SDK, giving you traces, metrics, and log correlation with minimal code changes.
composer require plunkettscott/laravel-otel
php artisan vendor:publish --tag=otel-config
.env:
OTEL_SERVICE_NAME=my-laravel-app
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_TRACES_EXPORTER=otlp
config/otel.php — service name, exporters, sampler, and the list of enabled watchers.otel-lgtm or otel/opentelemetry-collector + Jaeger is fastest), hit a route that does a DB query and dispatches a job, and confirm the request span, child query spans, and queue span all appear under one trace. That single smoke test validates endpoint, protocol, propagation, and flushing at once.Lean on auto-instrumentation first. Most of the value is in the watchers you enable in config — HTTP requests, DB queries, queue jobs, cache/Redis, and scheduled commands. Turn on one watcher at a time and inspect the span tree in your backend before writing any manual code.
Manual spans for domain work. Wrap business operations so they show up as named children rather than anonymous time inside a request:
$tracer = app(\OpenTelemetry\API\Trace\TracerInterface::class);
$span = $tracer->spanBuilder('invoice.generate')->startSpan();
try {
$span->setAttribute('invoice.id', $invoice->id);
// ...
} finally {
$span->end();
}
Prefer a small action/middleware wrapper over sprinkling try/finally through controllers.
Propagate context into queued work. Queue spans are only useful if the trace continues across the job boundary. Verify a dispatched job's span shares the trace_id of the dispatching request; if it doesn't, the queue watcher isn't registering the job event or your queue driver bypasses the normal dispatch path.
Scheduled tasks. The ScheduleWatcherOptions object configures how scheduled commands are traced — span naming and the attributes attached to each run. The v0.4.2 release (Fix ScheduleWatcherOptions) corrects how those options are applied. If you set naming or attribute options on this object and saw them silently ignored, that fix is your reason to upgrade; if you don't trace the scheduler, the patch is inert for you.
Log correlation. Once traces are active, push trace_id/span_id into the log context (a Monolog processor or a logging channel tap) so you can pivot from a log line in your aggregator straight to the trace in Tempo/Jaeger.
Sampling by environment. Sample everything locally, and use a ratio or parent-based sampler in production. Keep the sampler decision in config driven by env, not hardcoded.
Testing. Disable the exporter in phpunit.xml (OTEL_SDK_DISABLED=true or a null exporter) so the suite doesn't block on a network endpoint, and assert on behaviour rather than emitted spans — span assertions are brittle against SDK upgrades.
4317 is gRPC, 4318 is HTTP. Setting http/protobuf against :4317 (or vice versa) typically fails quietly — no spans, no exception. If your backend is empty, check this before anything else.config:cache shadows env(). If your config/otel.php reads env vars, changes to .env won't apply until php artisan config:clear. Always re-cache after editing OTel settings in deploy scripts.ScheduleWatcherOptions behaviour changed in 0.4.2. This is a bug fix, not a breaking change, but the effective configuration of your scheduler spans may differ from what you observed on 0.4.1. If you tuned scheduler options against the buggy behaviour, re-check your span names and attributes after upgrading.$span->recordException($e) and set the status explicitly, or your custom spans will look healthy while the request 500s.0.4.x the config keys and watcher APIs can shift between minor versions. Pin the version in composer.json, read the changelog before each bump, and keep manual instrumentation behind small wrappers so SDK churn is a one-file change.How can I help you explore Laravel packages today?