Vibe monitoring with Last9 MCP: Ask your agent to fix production issues! Setup →
Last9 Last9

May 9th, ‘25 / 8 min read

OpenTelemetry PHP: A Detailed Implementation Guide

Learn how to set up OpenTelemetry PHP to collect traces, metrics, and logs from your PHP apps and improve observability across your stack.

OpenTelemetry PHP: A Detailed Implementation Guide

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:

  1. Traces: Tracks the path of a request as it moves through your application
  2. Metrics: Numerical data about your application's performance
  3. 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/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();
}
💡
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-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');
💡
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 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:

  1. Verify your exporter configuration and endpoints
  2. Check for network connectivity issues
  3. Ensure your API keys and authentication are correct
  4. 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:

  1. Ensure you're using the correct LogRecord format
  2. Verify that your exporter supports structured logs
  3. 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:

  1. Use appropriate sampling rates based on traffic volume
  2. Apply filter processors to reduce attribute volume
  3. Consider using batch processing with appropriate queue limits
  4. Implement circuit breakers to prevent telemetry issues from affecting application performance

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.

💡
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.

Contents


Newsletter

Stay updated on the latest from Last9.

Authors
Preeti Dewani

Preeti Dewani

Technical Product Manager at Last9

X