Monitoring complex PHP applications can be challenging. When systems span multiple services and environments, traditional logging approaches often fall short. OpenTelemetry offers a solution - an open-source, vendor-neutral framework that standardizes how we collect and export telemetry data.
This guide covers practical implementation steps for DevOps engineers working with PHP applications.
What is OpenTelemetry PHP?
OpenTelemetry PHP is the official PHP library for the OpenTelemetry project. It provides a standard way to collect observability data—like metrics, logs, and traces—from your applications.
Unlike proprietary solutions that lock you into specific vendors, OpenTelemetry offers a vendor-neutral approach, allowing you to choose where your data goes and how it’s analyzed.
The three pillars of OpenTelemetry observability are:
- Traces: Tracks the path of a request as it moves through your application
- Metrics: Numerical data about your application’s performance
- Logs: Timestamped records of events in your application
// A simple example of OpenTelemetry PHP trace creation$tracer = $tracerProvider->getTracer('example_app');$span = $tracer->spanBuilder('main_operation')->startSpan();// Your code here$span->end();Why DevOps Teams Should Care About OpenTelemetry PHP
For DevOps engineers managing PHP applications, OpenTelemetry brings several key benefits:
- Unified Observability: Collect metrics, logs, and traces through a single framework
- Vendor Neutrality: Switch between observability backends without changing your instrumentation code
- Open Standards: Build on community-driven standards rather than proprietary APIs
- Future-Proof: Adopt a rapidly-growing standard with strong industry support
- Cost Efficiency: Reduce observability costs through better data control
If you’re also trying to understand how data gets out of your app and into your backend, this breakdown of OpenTelemetry Collector vs Exporter can help.
Step-by-Step Guide to Implement OpenTelemetry in PHP
Getting started with OpenTelemetry PHP involves a few straightforward steps:
Getting Required OpenTelemetry Packages
Use Composer to install the OpenTelemetry PHP packages:
composer require open-telemetry/opentelemetrycomposer require open-telemetry/sdkcomposer require open-telemetry/exporter-otlpConfigure Your First Tracer
The following code sets up a basic tracer that sends data to an OTLP endpoint:
<?phprequire 'vendor/autoload.php';
use OpenTelemetry\API\Common\Instrumentation\Globals;use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;use OpenTelemetry\Sdk\Trace\SpanProcessor\SimpleSpanProcessor;use OpenTelemetry\Sdk\Trace\TracerProvider;use OpenTelemetry\Exporter\Otlp\Exporter;
// Configure OTLP exporter$transport = (new OtlpHttpTransportFactory())->create('http://localhost:4318/v1/traces', 'application/json');$exporter = new Exporter($transport);
// Build TracerProvider with SimpleSpanProcessor$spanProcessor = new SimpleSpanProcessor($exporter);$tracerProvider = new TracerProvider([$spanProcessor]);
// Set as global TracerProviderGlobals::setTracerProvider($tracerProvider);
// Get a tracer$tracer = $tracerProvider->getTracer('my_php_app');Build Spans to Track Operations
Now you can create spans to track operations in your code:
// Start a new root span$span = $tracer->spanBuilder('main_operation')->startSpan();$scope = $span->activate();
try { // Your application code here
// Create a child span for a specific operation $childSpan = $tracer->spanBuilder('database_query') ->setParent($span->getContext()) ->startSpan();
try { // Database query code // ...
// Add attributes to the span $childSpan->setAttribute('db.system', 'mysql'); $childSpan->setAttribute('db.statement', 'SELECT * FROM users'); } finally { $childSpan->end(); }
} finally { $span->end(); $scope->detach();}If you’re exploring how to track requests across services in your PHP app, this OpenTelemetry Tracing in Distributed Systems guide offers a clear explanation of the core concepts and setup.
How to Set Up Auto-Instrumentation for PHP Applications
Manual instrumentation is useful, but auto-instrumentation can save significant time. Here’s how to set it up:
Install Auto-Instrumentation Libraries
composer require open-telemetry/opentelemetry-auto-psr15composer require open-telemetry/opentelemetry-auto-psr18Register OpenTelemetry Middleware
For frameworks like Laravel or Symfony, you’ll need to register the OpenTelemetry middleware:
// Example for Laravel in app/Http/Kernel.phpprotected $middleware = [ // Other middleware \OpenTelemetry\Contrib\Instrumentation\Laravel\LaravelMiddleware::class,];Connect OpenTelemetry PHP to Observability Platforms
After collecting telemetry data, you need a reliable place to send and analyze it. Last9 is a solid choice for managed observability, built to work well with OpenTelemetry. By integrating with both OpenTelemetry and Prometheus, our platform brings together metrics, logs, and traces—helping you monitor, alert, and optimize for performance and cost in one place.
Connecting to Last9 Observability Platform
// Configure exporter to send data to Last9$transport = (new OtlpHttpTransportFactory())->create( 'https://your-last9-endpoint/v1/traces', 'application/json', [ 'headers' => [ 'Authorization' => 'Bearer your-api-key' ] ]);$exporter = new Exporter($transport);Implementing Metrics Collection
OpenTelemetry PHP supports metrics collection too:
use OpenTelemetry\API\Metrics\Meter;use OpenTelemetry\SDK\Metrics\MeterProvider;use OpenTelemetry\Exporter\Otlp\MetricsExporter;
// Create metrics exporter$metricsExporter = new MetricsExporter($transport);
// Create meter provider$meterProvider = new MeterProvider($metricsExporter);
// Get a meter$meter = $meterProvider->getMeter('my_php_app');
// Create a counter$counter = $meter->createCounter('http_requests_total', 'The total number of HTTP requests');
// Increment the counter$counter->add(1, ['method' => 'GET', 'route' => '/users']);Installing Essential Logging Packages
composer require open-telemetry/opentelemetry-api-loggerscomposer require open-telemetry/opentelemetry-sdk-loggersConfiguring the LoggerProvider and Exporters
<?phprequire 'vendor/autoload.php';
use OpenTelemetry\API\Logs\LoggerInterface;use OpenTelemetry\API\Logs\LogRecord;use OpenTelemetry\SDK\Logs\LoggerProvider;use OpenTelemetry\SDK\Logs\Processor\SimpleLogRecordProcessor;use OpenTelemetry\Contrib\Otlp\LogsExporter;
// Create exporter for logs$transport = (new OtlpHttpTransportFactory())->create('http://localhost:4318/v1/logs', 'application/json');$exporter = new LogsExporter($transport);
// Create logger provider$logProcessor = new SimpleLogRecordProcessor($exporter);$loggerProvider = new LoggerProvider([$logProcessor]);
// Get a logger$logger = $loggerProvider->getLogger('my_php_app');
// Log a message$record = (new LogRecord()) ->setSeverityText('INFO') ->setSeverityNumber(9) // INFO level ->setBody('User login successful') ->setAttributes([ 'user.id' => '12345', 'login.source' => 'web' ]);$logger->emit($record);Using Standardized Severity Levels for Consistent Logging
// Different severity levels example$logger->emit((new LogRecord()) ->setSeverityText('TRACE') ->setSeverityNumber(1) ->setBody('Detailed debugging information'));
$logger->emit((new LogRecord()) ->setSeverityText('DEBUG') ->setSeverityNumber(5) ->setBody('Debug information'));
$logger->emit((new LogRecord()) ->setSeverityText('INFO') ->setSeverityNumber(9) ->setBody('Normal application behavior'));
$logger->emit((new LogRecord()) ->setSeverityText('WARN') ->setSeverityNumber(13) ->setBody('Warning - potential issue detected'));
$logger->emit((new LogRecord()) ->setSeverityText('ERROR') ->setSeverityNumber(17) ->setBody('Error condition - runtime error occurred'));
$logger->emit((new LogRecord()) ->setSeverityText('FATAL') ->setSeverityNumber(21) ->setBody('Critical error - application failure'));Using Environment Variables for Flexible Configuration
# Set the OTLP endpointexport OTEL_EXPORTER_OTLP_ENDPOINT=https://your-endpoint:4318
# Set the service nameexport OTEL_SERVICE_NAME=my-php-service
# Set the log levelexport OTEL_LOG_LEVEL=info
# Enable batch processingexport OTEL_BSP_SCHEDULE_DELAY=5000In your PHP application, you can then read these variables:
$endpoint = getenv('OTEL_EXPORTER_OTLP_ENDPOINT') ?: 'http://localhost:4318';$transport = (new OtlpHttpTransportFactory())->create($endpoint . '/v1/logs', 'application/json');If you’re working with OpenTelemetry PHP and want to capture more than just averages—like understanding how your response times actually spread out—this guide on OpenTelemetry histograms breaks down how they work and why they matter.
How to Add Context and Attributes to Your Telemetry Data
Enrich your telemetry data with context and custom attributes:
// Add business context to your spans$span->setAttribute('user.id', $userId);$span->setAttribute('transaction.value', $orderTotal);$span->setAttribute('service.version', '1.2.3');
// Add events to your spans$span->addEvent('cache.miss', [ 'key' => $cacheKey, 'lookup_time_ms' => $lookupTime]);Advanced OpenTelemetry PHP Techniques
Once you’ve mastered the basics, try these advanced techniques:
Implement Distributed Tracing
Propagate context across service boundaries:
// Extract context from incoming request$carrier = [];foreach ($request->getHeaders() as $name => $values) { $carrier[strtolower($name)] = $values[0];}$parentContext = TraceContextPropagator::getInstance()->extract($carrier);
// Create span with extracted parent$span = $tracer->spanBuilder('service_operation') ->setParent($parentContext) ->startSpan();
// When making outgoing requests, inject context$outgoingCarrier = [];TraceContextPropagator::getInstance()->inject($outgoingCarrier, null, $span->getContext());
// Add headers to outgoing requestforeach ($outgoingCarrier as $name => $value) { $outgoingRequest = $outgoingRequest->withHeader($name, $value);}Set Up Sampling Controls
Implement sampling to control the volume of data:
use OpenTelemetry\SDK\Trace\Sampler\ParentBased;use OpenTelemetry\SDK\Trace\Sampler\TraceIdRatioBasedSampler;
// Sample 20% of traces$sampler = new ParentBased(new TraceIdRatioBasedSampler(0.2));
// Add sampler to tracer provider$tracerProvider = new TracerProvider( [$spanProcessor], null, null, null, $sampler);Troubleshooting OpenTelemetry PHP
Even with the best setup, issues can occur. Here are solutions to common problems:
No Data Reaching Your Backend
If telemetry data isn’t showing up:
- Verify your exporter configuration and endpoints
- Check for network connectivity issues
- Ensure your API keys and authentication are correct
- Look for PHP errors in your logs
// Add debug logging to your exporter$transport = (new OtlpHttpTransportFactory())->create( 'https://your-endpoint/v1/traces', 'application/json', [ 'debug' => true ]);High Cardinality Problems
Too many unique values can cause performance issues:
// AVOID high cardinality attributes$span->setAttribute('user.ip', $userIp); // Can have millions of values
// BETTER approach$span->setAttribute('user.country', $userCountry); // Lower cardinalityStructured Logging Issues
If your logs aren’t appearing properly structured:
- Ensure you’re using the correct LogRecord format
- Verify that your exporter supports structured logs
- Check for correct context propagation
// Correct structured logging$record = (new LogRecord()) ->setSeverityText('ERROR') ->setSeverityNumber(17) ->setBody('Database connection failed') ->setAttributes([ 'db.connection_id' => $connectionId, 'db.system' => 'mysql', 'error.type' => $exception->getCode() ]) ->setTimestamp(hrtime(true));
// Attach trace context$logger->emit($record, Context::getCurrent());Log Correlation Failures
If logs and traces aren’t correlating:
// Ensure trace context is properly propagated to logs$span = $tracer->getCurrentSpan();$traceId = $span->getContext()->getTraceId();$spanId = $span->getContext()->getSpanId();
// Manual correlation if automatic doesn't work$record = (new LogRecord()) ->setSeverityText('INFO') ->setBody('Operation completed') ->setAttributes([ 'trace_id' => $traceId, 'span_id' => $spanId ]);$logger->emit($record);Memory Usage Concerns
For high-throughput applications, memory management is crucial:
// Use batch processing instead of simple processing$spanProcessor = new BatchSpanProcessor( $exporter, null, 128, // Maximum queue size 5000, // Schedule delay millis 30000 // Export timeout millis);Now, fix production PHP log issues instantly—right from your IDE, with AI and Last9 MCP. Bring real-time production context — logs, metrics, and traces — into your local environment to auto-fix code faster.
Performance Optimization Strategies for OpenTelemetry PHP
Here’s a table comparing the performance impact of different OpenTelemetry PHP configurations:
| Configuration | CPU Overhead | Memory Overhead | Network Traffic | Best For |
|---|---|---|---|---|
| No instrumentation | 0% | 0% | 0 KB/req | N/A |
| Basic tracing | 1-3% | 5-10 MB | 0.5-2 KB/req | Production |
| Detailed tracing | 3-5% | 10-25 MB | 2-10 KB/req | Debug |
| Full auto-instrumentation | 5-10% | 25-50 MB | 10-30 KB/req | Development |
| With 10% sampling | 0.5-1% | 5-10 MB | 0.2-3 KB/req | High-scale prod |
To optimize performance:
- Use appropriate sampling rates based on traffic volume
- Apply filter processors to reduce attribute volume
- Consider using batch processing with appropriate queue limits
- Implement circuit breakers to prevent telemetry issues from affecting application performance
Integrating OpenTelemetry with Popular PHP Frameworks
OpenTelemetry PHP can be integrated with popular PHP frameworks:
Instrumenting Laravel Applications with OpenTelemetry
// ServiceProvider.phppublic function register(){ $this->app->singleton(TracerInterface::class, function ($app) { // Configure tracer as shown earlier return $tracer; });}
// Middleware/TelemetryMiddleware.phppublic function handle($request, Closure $next){ $tracer = app(TracerInterface::class); $span = $tracer->spanBuilder('http_request')->startSpan();
try { $response = $next($request); $span->setAttribute('http.status_code', $response->getStatusCode()); return $response; } catch (\Exception $e) { $span->recordException($e); throw $e; } finally { $span->end(); }}Integrating OpenTelemetry into Symfony Projects
// config/services.yamlservices: OpenTelemetry\Tracer: class: OpenTelemetry\API\Trace\Tracer factory: ['@App\Factory\TracerFactory', 'create']
// src/EventSubscriber/RequestSubscriber.phppublic function onKernelRequest(RequestEvent $event){ $span = $this->tracer->spanBuilder('http_request')->startSpan(); $request = $event->getRequest(); $request->attributes->set('otel_span', $span);}
public function onKernelResponse(ResponseEvent $event){ $request = $event->getRequest(); $span = $request->attributes->get('otel_span'); if ($span) { $span->setAttribute('http.status_code', $event->getResponse()->getStatusCode()); $span->end(); }}Wrapping Up
OpenTelemetry PHP gives you a practical way to monitor what’s happening inside your PHP applications. Set it up, observe how your app behaves, and refine things based on what you learn.
And if you’d like to talk through anything further, our Discord community is open—there’s a dedicated channel where you can chat about your specific use case with other developers.
FAQs
Comparing OpenTelemetry to Traditional APM Tools
OpenTelemetry PHP offers a vendor-neutral approach to telemetry data collection, allowing you to switch between different backends without changing your instrumentation code. Traditional tools often lock you into their ecosystem with proprietary agents and APIs.
Running OpenTelemetry Alongside Existing Monitoring Systems
Yes, you can run OpenTelemetry PHP alongside existing APM solutions during a transition period. Many commercial APM vendors now support ingesting OpenTelemetry data, making a gradual migration possible.
Assessing OpenTelemetry’s Production Readiness
OpenTelemetry PHP has reached a stable state for tracing functionality. Metrics and logging are still maturing but are usable in production with appropriate care. Many organizations now use OpenTelemetry PHP successfully in production environments.
Creating Structured Logs with the LogRecord Class
OpenTelemetry PHP provides a structured logging approach through its LogRecord class. This allows you to create standardized log records with severity levels, timestamps, and structured attributes. The LogRecord format ensures your logs can be properly indexed and searched in your observability platform.
Measuring Performance Overhead in Production
With proper sampling and configuration, OpenTelemetry PHP typically adds 1-3% overhead in CPU usage and minimal memory overhead. The exact impact depends on your instrumentation density and sampling strategy.
Finding Compatible Observability Backends
You can send OpenTelemetry PHP data to any backend that supports the OTLP protocol, including Last9, Jaeger, Zipkin, Prometheus, and many commercial observability platforms.
Managing Version Updates Safely
Update your dependencies via Composer (composer update open-telemetry/*) and check the release notes for any breaking changes. The OpenTelemetry community strives to maintain backward compatibility where possible.
