Why does modern Laravel development need full‑stack observability?
When a high‑traffic e‑commerce site on Laravel 11 suddenly spikes latency, developers often scramble through logs, guess which service is throttling, and waste hours on root‑cause analysis. According to the 2023 DevOps Survey, 62% of teams cite lack of real‑time tracing as the top blocker for rapid incident resolution. Combining OpenTelemetry, Jaeger, Prometheus, and Grafana delivers a single pane of glass that turns vague symptoms into actionable metrics.
System requirements and preparation
Before adding any observability stack, verify that your server meets the following baseline:
- PHP 8.2 or newer
- Laravel 11.x
- Docker Engine 20.10+ (recommended for Jaeger and Prometheus containers)
- Network ports 6831/6832 (UDP) for Jaeger, 9090 for Prometheus, 3000 for Grafana
These requirements keep the installation lightweight while allowing horizontal scaling in Kubernetes or traditional VM environments.
Install OpenTelemetry SDK for Laravel
The first step is to bring the OpenTelemetry PHP library into the Laravel ecosystem. Use Composer to add the core SDK and the Laravel bridge:
composer require open-telemetry/opentelemetry "open-telemetry/opentelemetry-laravel" After installation, publish the configuration file so you can tweak exporter settings without touching code:
php artisan vendor:publish --provider="OpenTelemetry\Laravel\OpenTelemetryServiceProvider" --tag="config" The generated config/opentelemetry.php contains a default_exporter key that you will point at Jaeger in the next section.
Configure Jaeger as the tracing backend
Jaeger provides distributed tracing visualized through its UI. Run it locally with Docker to avoid manual binary installs:
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
-p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp \
-p 16686:16686 -p 14250:14250 -p 14268:14268 -p 9411:9411 \
jaegertracing/all-in-one:1.53 Update config/opentelemetry.php to use the Jaeger exporter:
return [
'default_exporter' => 'jaeger',
'exporters' => [
'jaeger' => [
'type' => 'jaeger',
'endpoint' => env('JAEGER_ENDPOINT', 'http://localhost:14268/api/traces'),
],
],
]; Now every incoming HTTP request, queued job, or database query will generate a span that appears in Jaeger’s UI at http://localhost:16686.
Expose Prometheus metrics from Laravel
While tracing tells you *how* a request traveled, metrics answer *how many* requests succeeded, failed, or timed out. Install the Prometheus client for PHP:
composer require superbalist/prometheus-client-laravel Register the service provider in config/app.php and publish its config:
'providers' => [
// ... other providers
Superbalist\Prometheus\Laravel\PrometheusServiceProvider::class,
];
php artisan vendor:publish --provider="Superbalist\Prometheus\Laravel\PrometheusServiceProvider" --tag="config"
The package adds a /metrics endpoint that Prometheus scrapes. Add a route in routes/web.php:
use Illuminate\Support\Facades\Route;
Route::get('/metrics', function () {
return response()->json(app('prometheus')->render(), 200, ['Content-Type' => 'text/plain']);
});
Start a Prometheus container that points to this endpoint:
docker run -d --name prometheus \
-p 9090:9090 \
-v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus:latest
In prometheus.yml add a scrape job:
scrape_configs:
- job_name: 'laravel'
static_configs:
- targets: ['host.docker.internal:8000']
labels:
env: 'local'
After a minute, Prometheus begins collecting http_requests_total, db_query_duration_seconds, and any custom counters you register.
Build Grafana dashboards for real‑time insight
Grafana connects to both Jaeger (via the Loki data source) and Prometheus. Launch a Grafana container and add the two data sources through the UI or by provisioning JSON files:
docker run -d --name grafana \
-p 3000:3000 \
-e "GF_SECURITY_ADMIN_PASSWORD=admin" \
grafana/grafana:10.2
Create a dashboard that shows:
- Request latency percentile (Prometheus query
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))) - Error rate per endpoint (
rate(http_requests_total{status=~"5.."}[1m])) - Top 5 slowest traces from Jaeger (use the Jaeger data source with a trace ID filter)
Export the dashboard JSON and version‑control it alongside your Laravel codebase. This practice guarantees that new environments spin up with identical observability views.
Practical tips for production readiness
1. **Batch export** – Enable the batch exporter option in OpenTelemetry to reduce network overhead when traffic exceeds 10k RPS.
2. **Sample intelligently** – Use a probabilistic sampler (e.g., 0.1 for 10% of requests) in high‑volume APIs to keep storage costs manageable while still catching anomalies.
3. **Secure endpoints** – Protect /metrics and Jaeger UI with HTTP basic auth or IP whitelisting; expose them only on internal networks.
4. **Correlation IDs** – Propagate a UUID through Laravel’s middleware and attach it as a tag to both spans and metrics. This creates a one‑to‑one link between a trace and its aggregated counters.
Conclusion
Integrating OpenTelemetry, Jaeger, Prometheus, and Grafana transforms a Laravel 11 application from a black box into an observable system where latency, errors, and performance trends are visible instantly. By following the step‑by‑step setup—installing the SDK, configuring Jaeger, exposing Prometheus metrics, and wiring Grafana dashboards—you gain a repeatable, code‑driven observability pipeline that scales from local development to multi‑region production. The payoff is faster incident response, data‑driven optimization, and confidence that your Laravel services are running as intended.
Sources
- OpenTelemetry PHP Documentation
- Jaeger Tracing Official Guides
- Prometheus and Grafana Official Documentation
Author: Mahmut Sarıkaya — sarikayadev.com