OpenTelemetry is the vendor-neutral standard for application observability: distributed tracing, metrics, and logs. In 2026, the PHP implementation has matured significantly, with stable auto-instrumentation for common libraries and a usable SDK for manual instrumentation.
For legacy PHP applications, OpenTelemetry solves a persistent problem: understanding where time is spent across a request. Is it the database? The framework bootstrap? A downstream API call? Cache misses? Without instrumentation, you are guessing. With it, you have a trace waterfall that shows exactly how every millisecond is spent.
This guide covers adding OpenTelemetry to a legacy PHP application: installing the extension, configuring exporters, using auto-instrumentation, adding manual spans for application-specific code, and interpreting the results.
What OpenTelemetry Gives You
Distributed Traces
A trace follows a single request from entry to completion. Each operation within the request is a span. Spans can be nested:
1 | [Request: GET /users/42] ────────────────────── 120ms |
This tells you that template rendering is the bottleneck, not the database. Without this visibility, you might spend hours optimising queries that account for 15% of the response time.
Metrics
OpenTelemetry can export counters, histograms, and gauges:
- Request count per route
- Response time distribution (p50, p95, p99)
- Error rates by handler
- Database query count per request
Logs with Trace Context
OpenTelemetry attaches trace and span IDs to log entries. This lets you correlate a log message to the exact request and operation that produced it.
Installation
The PHP Extension
OpenTelemetry for PHP requires the opentelemetry PECL extension:
1 | pecl install opentelemetry |
Add to your php.ini:
1 | extension=opentelemetry.so |
The extension provides the core instrumentation hooks that auto-instrumentation libraries use to intercept function calls without modifying application code.
The SDK
Install the SDK and an exporter via Composer:
1 | composer require \ |
The SDK provides the API for creating spans, recording attributes, and managing trace context. The exporter sends telemetry data to your chosen backend.
Auto-Instrumentation
Auto-instrumentation packages instrument common libraries automatically:
1 | composer require \ |
Available auto-instrumentation packages cover PDO (database queries), PSR-18 HTTP clients, curl, and several frameworks. Check the OpenTelemetry PHP contrib repository for the current list.
Configuration
Exporter Setup
Configure OpenTelemetry through environment variables. This keeps instrumentation config separate from application code:
1 | # In php.ini or .env |
OTEL_SERVICE_NAME identifies your application in the tracing backend. Use something descriptive: payments-api or user-service, not php-app.
OTEL_TRACES_EXPORTER=otlp uses the OpenTelemetry Protocol, which is supported by most backends (Jaeger, Grafana Tempo, Datadog, New Relic, Honeycomb).
Sampling
In production, tracing every request is expensive. Sampling reduces the volume while maintaining statistical visibility:
1 | OTEL_TRACES_SAMPLER=parentbased_traceidratio |
This samples 10% of traces. For high-traffic applications, 1% (0.01) may be sufficient. For debugging, temporarily increase to 100% (1.0).
Resource Attributes
Add metadata that helps you filter traces:
1 | OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.version=2.4.1 |
This lets you compare traces between environments or before and after a deployment.
Auto-Instrumentation in Practice
With the extension and auto-instrumentation packages installed, common operations are traced automatically:
PDO (Database Queries)
Every PDO::query(), PDO::prepare(), and PDOStatement::execute() call creates a span with:
- The SQL query (with parameters redacted for security)
- The database name
- The execution time
This means you see every database query in your trace waterfall without touching a line of application code.
HTTP Client Calls
PSR-18 HTTP client calls create spans with:
- The target URL
- The HTTP method
- The response status code
- The duration
If the downstream service also uses OpenTelemetry, trace context is propagated automatically. Your trace follows the request across service boundaries.
Manual Instrumentation
Auto-instrumentation covers libraries. For application-specific code, add manual spans:
1 | use OpenTelemetry\API\Globals; |
Nesting Spans
Spans automatically nest based on the active context. If chargePayment() also creates a span, it appears as a child of process-order in the trace waterfall:
1 | private function chargePayment(Order $order): void |
The resulting trace shows the hierarchy:
1 | [process-order] ─────────────── 250ms |
Now you know the payment gateway is the bottleneck.
Integrating with Legacy Frameworks
Bootstrap Hook
For Laminas MVC or similar frameworks, add OpenTelemetry bootstrapping in your entry point:
1 | // public/index.php |
If OTEL_PHP_AUTOLOAD_ENABLED=true, the SDK initialises automatically when the Composer autoloader loads it. No code changes are needed in your bootstrap.
Event Listener Instrumentation
For Zend/Laminas MVC’s event system, add spans around event dispatch:
1 | $eventManager->attach(MvcEvent::EVENT_DISPATCH, function (MvcEvent $e) { |
Performance Impact
OpenTelemetry adds overhead. For a typical PHP application:
- Extension overhead: 1-3% CPU increase per request (the hooks are lightweight)
- Auto-instrumentation: 2-5ms per request (depends on number of instrumented calls)
- Exporter overhead: Minimal with async batch exporters; the data is sent in background batches, not inline with the request
For most applications, the overhead is negligible compared to database queries and business logic. If your application handles thousands of requests per second, use sampling to reduce the export volume.
Minimising Overhead
- Use sampling in production (10% or less for high-traffic services)
- Use the batch span processor (default), not the simple span processor
- Export over gRPC, which is more efficient than HTTP/JSON for large volumes
- Avoid adding spans to tight loops; instrument meaningful operations
Choosing a Backend
OpenTelemetry is exporter-agnostic. Common backends for PHP applications:
- Jaeger: Open source, good for development and small deployments
- Grafana Tempo: Open source, integrates with Grafana for visualisation
- Datadog: Commercial, full observability platform
- New Relic: Commercial, includes APM features beyond tracing
- Honeycomb: Commercial, designed for high-cardinality observability
For getting started, Jaeger is the simplest. Run it locally with Docker and point your OTLP exporter at it.
What to Instrument First
Do not instrument everything at once. Start with the operations that answer your most pressing performance questions:
- Database queries (auto-instrumentation handles this). Identify slow queries and N+1 patterns.
- External HTTP calls (auto-instrumentation handles this). Find slow downstream services.
- Request handler dispatch. Measure total handler time per route.
- Cache operations. Measure hit rates and lookup times.
- Template rendering. Identify expensive views.
After these five, you have visibility into 90% of where request time is spent. Add more granular spans only when you need to debug a specific bottleneck.
FAQ
Does OpenTelemetry replace my existing monitoring?
No. It complements it. APM tools, error trackers, and log aggregators serve different purposes. OpenTelemetry can feed data into many of those tools through its exporter system.
Is the PHP extension stable?
In 2026, the core extension and SDK are stable. Some auto-instrumentation packages are still in beta. Check the stability status of each package before using it in production.
Can I use OpenTelemetry with PHP-FPM?
Yes. Each PHP-FPM request initialises a new trace. The extension and SDK work correctly with PHP-FPM’s share-nothing model. Trace data is batched and exported efficiently.
What about FrankenPHP worker mode?
Worker mode requires care. The tracer and span processor persist across requests. Ensure each request creates a new root span and that span context is properly reset between requests.
Next Steps
Start with auto-instrumentation. Install the extension, the PDO and HTTP auto-instrumentation packages, and point the exporter at a local Jaeger instance. Run your application’s test suite or a representative workflow and examine the traces. The first trace waterfall will likely reveal performance characteristics you did not know about.
The PHP Performance Playbook provides the measurement methodology for acting on what you discover. The PHP-FPM Tuning by Measurement guide covers the infrastructure-level tuning that complements application-level instrumentation.