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
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/opentelemetry
composer require open-telemetry/sdk
composer require open-telemetry/exporter-otlp
Configure Your First Tracer
The following code sets up a basic tracer that sends data to an OTLP endpoint:
<?php
require '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 TracerProvider
Globals::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();
}
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-psr15
composer require open-telemetry/opentelemetry-auto-psr18
Register OpenTelemetry Middleware
For frameworks like Laravel or Symfony, you'll need to register the OpenTelemetry middleware:
// Example for Laravel in app/Http/Kernel.php
protected $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-loggers
composer require open-telemetry/opentelemetry-sdk-loggers
Configuring the LoggerProvider and Exporters
<?php
require '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 endpoint
export OTEL_EXPORTER_OTLP_ENDPOINT=https://your-endpoint:4318
# Set the service name
export OTEL_SERVICE_NAME=my-php-service
# Set the log level
export OTEL_LOG_LEVEL=info
# Enable batch processing
export OTEL_BSP_SCHEDULE_DELAY=5000
In 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');
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 request
foreach ($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 cardinality
Structured 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
);
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.php
public function register()
{
$this->app->singleton(TracerInterface::class, function ($app) {
// Configure tracer as shown earlier
return $tracer;
});
}
// Middleware/TelemetryMiddleware.php
public 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.yaml
services:
OpenTelemetry\Tracer:
class: OpenTelemetry\API\Trace\Tracer
factory: ['@App\Factory\TracerFactory', 'create']
// src/EventSubscriber/RequestSubscriber.php
public 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.
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.