
August 19, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
If you cannot trace a request from Nginx through your Laravel application to the database and back, you are debugging blind. To instrument an app with OpenTelemetry effectively in 2026, you must move beyond basic logging and implement structured telemetry that captures latency, errors, and context across service boundaries. This is especially critical for complex systems like legal-tech portals or multi-vendor eCommerce platforms where a single user action triggers multiple backend processes. I recently outlined modern Laravel architecture best practices, and observability is now a non-negotiable pillar of that stack.
open-telemetry/opentelemetry-instrumentation-laravel package via Composer, configure the OTEL exporter endpoint in your .env, and enable auto-instrumentation for HTTP, Eloquent, and Redis. Add manual spans only for critical business logic to maintain signal quality without performance overhead.How do you set up automatic instrumentation for Laravel in 2026?
Automatic instrumentation is the foundation. It captures incoming HTTP requests, outgoing HTTP calls, database queries, cache operations, and queue jobs without modifying your application code. In 2026, the PHP OpenTelemetry ecosystem has matured significantly; the official Laravel instrumentation package supports Laravel 11 and 12 on PHP 8.2+ reliably.
Install required packages
You need the core SDK, the Laravel-specific instrumentation, and an exporter. For most production setups targeting Jaeger, Grafana Tempo, or Datadog, the OTLP exporter is the standard choice.
composer require open-telemetry/opentelemetry-instrumentation-laravel \
open-telemetry/exporter-otlp \
open-telemetry/transport-grpc \
guzzlehttp/guzzle:^7.0 Note that open-telemetry/transport-grpc requires the gRPC PHP extension. If your hosting environment makes installing extensions difficult (common on shared hosting), use open-telemetry/transport-http instead, which sends OTLP over HTTP/protobuf and works everywhere Guzzle runs.
Configure environment variables
The SDK reads configuration entirely from environment variables following the OpenTelemetry specification. Add these to your .env:
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_tracealways
OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_INSTRUMENTATION_LARAVEL_ENABLED=true In practice, I set OTEL_PHP_AUTOLOAD_ENABLED=true so the SDK hooks into Composer’s autoloader. This eliminates the need to manually bootstrap the tracer in a service provider, reducing the chance of missing early-request spans during framework boot.
Verify the installation
Create a test route that performs a database query and returns a response. Hit it, then check your observability backend. You should see a root span for the HTTP request with child spans for the Eloquent query and any middleware execution. If spans appear but lack attributes, verify your OTEL_SERVICE_NAME matches what your backend expects.
When should you add manual spans to your application code?
Auto-instrumentation covers infrastructure, not business intent. When debugging why "court marriage applications" take 4 seconds to process on a legal-tech portal I built, auto-instrumentation showed me slow database queries but not that the bottleneck was actually PDF generation happening synchronously inside a controller method. Manual spans bridge this gap.
Creating spans with the global tracer
Use the static Tracer facade provided by the Laravel instrumentation package. This avoids injecting dependencies into every service class:
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 wrap manual span logic in try/catch/finally. If an exception occurs and you forget to call $span->end(), the span leaks and corrupts trace timing. The finally block guarantees cleanup regardless of success or failure.
Attributes matter more than span names
A span named process.payment is useless if you cannot filter by payment gateway, currency, or transaction amount. Set high-cardinality attributes cautiously (user IDs can explode index size), but always include business-critical dimensions. On eCommerce projects integrating eSewa or Khalti, I consistently tag spans with payment.gateway, order.id, and transaction.status because those are the exact filters needed during incident response.
What are the production safety considerations for PHP telemetry?
Instrumentation adds overhead. In my experience working on production Laravel applications serving thousands of daily users, unconfigured OpenTelemetry can increase p99 latency by 15–30% and consume significant memory during traffic spikes. These safeguards are mandatory, not optional.
Sampling strategy
Never sample at 100% in production unless your volume is very low. Use parent-based sampling to respect upstream decisions, combined with a rate limiter:
OTEL_TRACES_SAMPLER=parentbased_tracealways
# Or for head-based sampling at 10%:
# OTEL_TRACES_SAMPLER=traceidratio
# OTEL_TRACES_SAMPLER_ARG=0.1 For high-traffic eCommerce sites during Dashain/Tihar sales periods, I typically drop to 5% sampling. You still capture enough data to identify patterns while keeping collector costs manageable. Remember: sampling happens at the SDK level before export, so unsampled spans never leave your server.
Batch processing and timeouts
The default batch processor buffers spans and flushes periodically. Tune these for your workload:
OTEL_BSP_SCHEDULE_DELAY=5000
OTEL_BSP_EXPORT_TIMEOUT=30000
OTEL_BSP_MAX_QUEUE_SIZE=2048
OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512 If your exporter endpoint is slow or unreachable, the queue fills up. Once MAX_QUEUE_SIZE is reached, new spans are dropped silently. Monitor otel.exporter.otlp.failed_spans metrics to detect this condition. On one project, we discovered our collector was rejecting batches due to payload size limits; without monitoring export failures, we lost hours of trace data during a critical outage.
Context propagation across services
If your Laravel app calls external APIs or microservices, ensure W3C Trace Context headers propagate correctly. The auto-instrumentation handles outgoing HTTP requests automatically, but custom Guzzle clients or cURL calls may bypass it. Always use the instrumented HTTP client or manually inject context:
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
$headers = [];
TraceContextPropagator::getInstance()->inject($headers);
$response = Http::withHeaders($headers)->get('https://api.nepal-gift-card.com/v1/cards'); Without propagation, downstream services create orphaned traces disconnected from the originating request. This is particularly painful when debugging cross-service workflows like payment verification callbacks from ConnectIPS or IME Pay.
| Configuration | Development | Production (Low Traffic) | Production (High Traffic) |
|---|---|---|---|
| Sampler | always_on | parentbased_tracealways | traceidratio (0.05–0.1) |
| Export Protocol | http/protobuf | http/protobuf | grpc (lower overhead) |
| Batch Delay | 1000ms | 5000ms | 5000–10000ms |
| Max Queue Size | 512 | 2048 | 4096+ |
| Attribute Limits | Default | Default | Cap at 128 attrs/span |
| Exception Recording | All exceptions | All exceptions | Only 5xx / critical |
How do you validate OpenTelemetry instrumentation before deploying?
Never deploy instrumentation changes directly to production without validation. Broken telemetry is worse than no telemetry because it creates false confidence.
Local validation with console exporter
During development, switch to the console exporter to see spans in your terminal:
OTEL_EXPORTER_OTLP_ENDPOINT=""
OTEL_TRACES_EXPORTER=console
OTEL_LOG_LEVEL=debug This outputs JSON-formatted spans to stdout. Verify span names follow conventions (GET /api/applications/{id} not handle), attributes contain expected values, and parent-child relationships form coherent trees. If your local Docker Compose includes Jaeger, point the OTLP endpoint there for visual inspection.
Staging environment smoke tests
Deploy to staging first and run automated tests that exercise key user journeys. After each test run, query your observability backend programmatically to assert expected spans exist. For example, after submitting a marriage application, verify a span named marriage.application.submit exists with attribute application.type=court. Catching missing instrumentation in staging prevents silent failures in production.
Performance benchmarking
Before enabling instrumentation in production, benchmark your application with and without the SDK loaded. Use Apache Bench or k6 against a representative endpoint:
k6 run --iterations=1000 --concurrency=50 bench.js Compare p50, p95, and p99 latencies. If overhead exceeds 10%, investigate whether excessive attributes, synchronous exporters, or misconfigured batching is the cause. On a recent Laravel API project, we reduced instrumentation overhead from 18% to 4% simply by switching from gRPC to HTTP/protobuf (our server lacked the gRPC extension and was falling back to a slow polyfill).
Common pitfalls when implementing OpenTelemetry in PHP
After helping teams adopt observability across multiple Nepali and international projects, certain mistakes recur consistently.
- Over-instrumenting trivial operations: Wrapping every helper function in a span creates noise, not signal. Reserve manual spans for operations taking >50ms or representing meaningful business steps.
- Ignoring baggage propagation limits: Baggage (key-value pairs propagated across services) has size limits. Stuffing entire user objects into baggage causes silent truncation. Use baggage only for routing hints and correlation IDs.
- Forgetting CLI and queue workers: Auto-instrumentation activates differently in long-running processes. Queue workers may hold stale tracer state across jobs. Restart workers after SDK updates and verify job spans link to their triggering HTTP request.
- Mixing incompatible package versions: The PHP OpenTelemetry ecosystem moves fast. Pin exact versions in
composer.jsonand test upgrades in isolation. A mismatchedopentelemetry-apiandopentelemetry-sdkversion can silently disable instrumentation. - Neglecting log correlation: Traces without correlated logs force context-switching between tools. Configure your logger to inject
trace_idandspan_idinto every log entry. Most Laravel logging channels support this via the OpenTelemetry log bridge.
For teams building REST APIs in Laravel, pay special attention to error handling middleware. Unhandled exceptions that bypass your error handler won’t be recorded as span events. Ensure your exception handler explicitly records exceptions on the active span before re-throwing or returning error responses.
Start instrumenting with intention, not defaults
To instrument an app with OpenTelemetry successfully, treat observability as a first-class engineering concern rather than an afterthought. Begin with auto-instrumentation to establish baseline visibility, add manual spans surgically for business-critical paths, and enforce production safeguards from day one. The goal isn’t maximum data collection—it’s actionable insight with minimal overhead. If you’re planning a new build or modernizing an existing system, review what’s changed in Laravel 12 to understand native observability improvements that complement OpenTelemetry. Need help designing an observability strategy tailored to your application’s scale and budget? Get in touch to discuss your specific requirements.

