
August 20, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
Your Laravel app returns 200, but users still complain about slow checkout or stuck document uploads. Logs show errors; they do not show where time disappears across HTTP, queues, Redis, and MySQL. That gap is exactly why teams search for opentelemetry php laravel guidance. OpenTelemetry gives you one standard way to capture traces, propagate context across services, and correlate telemetry with business events. I outlined this alongside Laravel architecture best practices because observability now belongs in the foundation, not as a post-launch patch.
open-telemetry/opentelemetry-instrumentation-laravel with an OTLP exporter, set OTEL_SERVICE_NAME and your collector endpoint in .env, enable auto-instrumentation for HTTP and Eloquent, then add manual spans only for slow or business-critical code paths.How do you set up OpenTelemetry auto-instrumentation in Laravel?
Auto-instrumentation is your baseline. It records incoming HTTP requests, outgoing HTTP calls, Eloquent queries, cache hits, and queue jobs without touching controllers. The PHP OpenTelemetry ecosystem is stable enough for Laravel 12 and Laravel 13 on PHP 8.3 or higher in 2026.
Install the required Composer packages
You need the Laravel instrumentation package, an OTLP exporter, and a transport layer. Most teams target Jaeger, Grafana Tempo, Honeycomb, or Datadog through an OpenTelemetry Collector.
composer require open-telemetry/opentelemetry-instrumentation-laravel \
open-telemetry/exporter-otlp \
open-telemetry/transport-grpc \
guzzlehttp/guzzle:^7.0 The gRPC transport needs the PHP gRPC extension. Shared hosting often blocks that. On those servers, swap in open-telemetry/transport-http and send OTLP over HTTP/protobuf instead. Guzzle alone is enough.
Configure environment variables
The SDK reads the standard OpenTelemetry environment variables. Add these to your .env and mirror them in staging and production secrets:
OTEL_SERVICE_NAME=legal-portal-prod
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.example.com:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_TRACES_SAMPLER=parentbased_always_on
OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_INSTRUMENTATION_LARAVEL_ENABLED=true I enable OTEL_PHP_AUTOLOAD_ENABLED=true so the SDK hooks Composer autoload early. That catches spans during framework boot. Manual bootstrap in a service provider is easy to get wrong and often misses the first middleware pass.
Verify spans appear in your backend
Create a test route that runs one Eloquent query and returns JSON. Hit it twice, then open your trace UI. You should see a root HTTP span with child spans for SQL and middleware. If nothing appears, confirm the collector port, firewall rules, and that OTEL_SERVICE_NAME matches your dashboard filters. The official OpenTelemetry PHP documentation lists every supported environment variable.
For deeper context on how traces fit beside metrics and logs, read observability vs monitoring: logs, metrics, and traces. Pair traces with Laravel Horizon queue monitoring when jobs fail silently after the HTTP response returns.
When should you add manual spans in a Laravel application?
Auto-instrumentation shows infrastructure time. It rarely explains business steps. On a legal-tech portal I built, slow traces pointed to SQL. The real delay was synchronous PDF generation inside a controller. Manual spans expose that layer.
Create spans with the injected tracer
Resolve TracerInterface from the container. Build spans around meaningful operations, not every private helper.
use OpenTelemetry\API\Trace\TracerInterface;
use OpenTelemetry\API\Trace\SpanKind;
class MarriageApplicationService
{
public function submit(array $data): Application
{
$tracer = app(TracerInterface::class);
$span = $tracer->spanBuilder('marriage.application.submit')
->setSpanKind(SpanKind::KIND_INTERNAL)
->setAttribute('application.type', $data['type'])
->setAttribute('applicant.district', $data['district'])
->startSpan();
try {
$application = Application::create($data);
$this->generatePdf($application);
$this->notifyLawyer($application);
$span->setStatus(\OpenTelemetry\API\Trace\StatusCode::STATUS_OK);
return $application;
} catch (\Throwable $e) {
$span->recordException($e);
$span->setStatus(\OpenTelemetry\API\Trace\StatusCode::STATUS_ERROR, $e->getMessage());
throw $e;
} finally {
$span->end();
}
}
} Always end spans in a finally block. Leaked spans corrupt duration math and confuse downstream analysis.
Choose attributes that survive an incident
Span names alone rarely help at 2 a.m. Tag business dimensions you will filter on: payment gateway, order ID, document type, queue name. Avoid high-cardinality values like raw email addresses in every span. They inflate storage cost without improving search.
On eCommerce projects with Khalti or eSewa, I tag payment.gateway, order.id, and transaction.status. Those three fields answer most payment callback investigations. See our Court Marriage In Nepal portfolio case for a real document workflow where trace attributes mattered during a production slowdown.
What production settings keep OpenTelemetry PHP overhead under control?
Unconfigured telemetry can inflate p99 latency and memory use during traffic spikes. Treat sampling, batching, and export timeouts as production requirements, not tuning afterthoughts.
Pick a sampling strategy
Do not sample 100% of traces on high-traffic sites unless volume is tiny. Parent-based sampling respects upstream decisions from your load balancer or API gateway.
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1 During Dashain or Tihar sales on eCommerce stores, I often drop to 5% sampling. You still spot slow endpoints and error clusters. Unsampled spans never leave the server, so export cost stays bounded.
Tune batch export settings
OTEL_BSP_SCHEDULE_DELAY=5000
OTEL_BSP_EXPORT_TIMEOUT=30000
OTEL_BSP_MAX_QUEUE_SIZE=2048
OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512 When the collector is slow or down, the queue fills. After MAX_QUEUE_SIZE, new spans drop silently. Watch exporter failure metrics or collector reject rates. On one deployment, oversized batches hit payload limits and we lost hours of traces during an outage.
Propagate trace context on outbound calls
Auto-instrumentation injects W3C Trace Context headers on standard HTTP clients. Custom cURL or bare Guzzle instances may skip that. Inject manually when needed:
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
$headers = [];
TraceContextPropagator::getInstance()->inject($headers);
$response = Http::withHeaders($headers)->get('https://api.example.com/v1/status'); The W3C Trace Context specification defines the traceparent header format. Without propagation, microservice calls produce orphan traces that cannot be tied back to the original user request. Our guide on distributed tracing with OpenTelemetry and Jaeger walks through end-to-end correlation.
| Setting | Development | Production (low traffic) | Production (high traffic) |
|---|---|---|---|
| Sampler | always_on | parentbased_always_on | traceidratio (0.05–0.1) |
| Export protocol | http/protobuf | http/protobuf | grpc when extension exists |
| Batch delay | 1000 ms | 5000 ms | 5000–10000 ms |
| Max queue size | 512 | 2048 | 4096+ |
| Attribute cap | Default | Default | 128 per span |
| Exception recording | All | All | 5xx and critical paths only |
Combine traces with Prometheus and Grafana monitoring for RED metrics. Traces explain individual slow requests; metrics show whether the problem is systemic. For queue-heavy apps, align trace IDs with Redis queue worker setup logs.
How do you validate OpenTelemetry instrumentation before production?
Broken telemetry is worse than none. It looks like coverage while blind spots remain. Validate in three stages: local, staging, then production with sampling.
Local: console exporter
OTEL_EXPORTER_OTLP_ENDPOINT=""
OTEL_TRACES_EXPORTER=console
OTEL_LOG_LEVEL=debug Spans print as JSON on stdout. Confirm names follow HTTP route patterns, attributes hold expected values, and parent-child links form trees. Paste sample span JSON into a JSON formatter when diffing before and after config changes.
Staging: assert expected spans
Deploy to staging with the real OTLP endpoint. Run feature tests for checkout, login, or document upload flows. Query the trace backend and assert spans like marriage.application.submit exist with the right attributes. Catch missing instrumentation before users do.
Benchmark overhead with load tests
Run k6 or Apache Bench against a representative endpoint with telemetry on and off. Compare p50, p95, and p99.
k6 run --iterations=1000 --vus=50 bench.js On a recent Laravel API best practices engagement, overhead dropped from 18% to 4% after switching from a broken gRPC polyfill to HTTP/protobuf. Also review PHP-FPM tuning for high traffic because trace export adds concurrent network I/O under load.
What mistakes break OpenTelemetry in PHP Laravel projects?
Teams adopt tracing enthusiastically, then wonder why dashboards stay empty or noisy. These patterns show up repeatedly.
- Over-instrumenting helpers: Spans on every 2 ms function create noise. Reserve manual spans for steps over ~50 ms or clear business meaning.
- Stuffing baggage with large payloads: Baggage has size limits. Use it for correlation IDs, not full user objects.
- Forgetting queue workers and Artisan: Long-running workers may hold stale tracer state. Restart workers after SDK upgrades. Verify job spans link to triggering HTTP requests.
- Pinning mismatched package versions: Lock
opentelemetry-apiandopentelemetry-sdktogether. Test upgrades in isolation. - Skipping log correlation: Inject
trace_idandspan_idinto Laravel logs. Jump between traces and log lines during incidents. - Deploying without CI checks: Add instrumentation verification to your GitLab CI pipeline for PHP so broken exporters fail the build.
For REST APIs built the right way in Laravel, ensure exception handlers record errors on the active span. Unhandled exceptions that bypass your handler leave traces looking successful when clients received 500 responses.
When traces still leave gaps, pair them with safe Laravel production debugging. Telescope helps in staging; OpenTelemetry scales in production without exposing internal UI endpoints.
Key Takeaways
- Install
open-telemetry/opentelemetry-instrumentation-laravelwith an OTLP exporter and enable autoload for early request coverage. - Start with auto-instrumentation; add manual spans only for business logic, PDF generation, payments, or other slow domain steps.
- Sample aggressively in production—5–10% is enough for most Laravel apps unless traffic is very low.
- Validate locally with the console exporter, then assert spans in staging before rolling out via zero-downtime Deployer releases.
- Inject W3C trace context on every outbound HTTP call so microservice traces stay connected.
- Correlate logs with
trace_idand follow the Laravel production deployment checklist so telemetry ships with every release.
People Also Ask
Does Laravel have built-in OpenTelemetry support?
Laravel does not ship native OpenTelemetry yet. The community open-telemetry/opentelemetry-instrumentation-laravel package provides auto-instrumentation for HTTP, Eloquent, cache, queues, and Guzzle. It works with Laravel 11, 12, and 13 on PHP 8.2 or higher. Review Laravel 12 new features for framework changes that affect middleware and queue behaviour alongside tracing.
What is the best backend for OpenTelemetry PHP traces?
Most teams route OTLP to an OpenTelemetry Collector, then forward to Jaeger, Grafana Tempo, or a SaaS vendor. Self-hosted Jaeger suits budget-conscious Nepali startups at roughly Rs 3,000–5,000/month (~USD 22–37) on a small VPS. SaaS backends cost more but reduce ops overhead.
How much performance overhead does OpenTelemetry add to PHP?
Well-configured auto-instrumentation with batch export and 10% sampling typically adds 3–8% latency overhead. Synchronous export, 100% sampling, or excessive manual spans can push overhead above 15%. Always benchmark before enabling in production.
Can OpenTelemetry work on shared hosting in Nepal?
Often yes, with limits. Use HTTP/protobuf transport instead of gRPC. Run the collector on a separate VPS or use a hosted backend. If your host blocks outbound ports or long-running background flush, traces may not export reliably. A VPS with full PHP-FPM control is the safer choice for production tracing.
Ship observability with your next Laravel release
To instrument an app with OpenTelemetry effectively, treat traces as product infrastructure. Enable auto-instrumentation first, add manual spans where business logic hides latency, tune sampling before traffic spikes, and validate in staging every time packages change. The goal is actionable insight—not maximum span volume. If you want help wiring OpenTelemetry into an existing Laravel system or designing a collector stack for your budget, contact us about observability and maintenance or explore our support and maintenance services in Nepal. For a direct conversation about your stack, you can also get in touch with me.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

