PHP Zero-Code Instrumentation with Uptrace
This guide explains how to automatically instrument PHP applications without modifying code using the OpenTelemetry PHP extension. The extension uses Zend hooks to automatically detect and instrument popular PHP frameworks and libraries at runtime, exporting telemetry data to Uptrace using OTLP.
Note: The examples below use Uptrace as the OTLP backend, but you can use any OpenTelemetry-compatible backend (Jaeger, Grafana Tempo, Prometheus, etc.) by changing the OTEL_EXPORTER_OTLP_ENDPOINT and removing Uptrace-specific headers.
What is Zero-Code Instrumentation?
Zero-code instrumentation (also called automatic instrumentation) allows you to collect telemetry data from PHP applications without modifying application code. The OpenTelemetry PHP extension uses Zend observer hooks to intercept function calls in popular frameworks and libraries at runtime, automatically generating distributed traces, metrics, and logs.
How it works:
- Install the OpenTelemetry PHP extension (PECL, package manager, or Docker)
- Enable the extension in php.ini
- Install framework-specific instrumentation libraries via Composer
- Configure environment variables
- Run your PHP application - telemetry data is automatically collected and exported to Uptrace
Prerequisites
Before starting, ensure you have:
- PHP 8.0 or higher (PHP 8.1+ recommended for best performance)
- Composer for dependency management
- An existing PHP application (Laravel, Symfony, etc.)
- An Uptrace account with a DSN
Quick Start Guide
Follow these steps to get your first auto-instrumented trace running in 5 minutes.
Step 1: Create an Uptrace Project
Create an Uptrace project to obtain a DSN (Data Source Name), for example, https://<secret>@api.uptrace.dev?grpc=4317.
Step 2: Install OpenTelemetry PHP Extension
Install the PHP extension using one of the following methods:
# Install build dependencies (if not already installed)
sudo apt-get install gcc make autoconf
# Install via PECL
pecl install opentelemetry
# Enable extension
echo "extension=opentelemetry.so" | sudo tee -a /etc/php/8.2/cli/php.ini
echo "extension=opentelemetry.so" | sudo tee -a /etc/php/8.2/fpm/php.ini
Verify the extension is loaded:
php --ri opentelemetry
# Should display: opentelemetry support => enabled
Step 3: Install Instrumentation Libraries
Install OpenTelemetry SDK and framework-specific instrumentation packages:
# Core SDK and OTLP exporter
composer require \
open-telemetry/sdk \
open-telemetry/exporter-otlp
# Auto-instrumentation libraries (install only what you need)
composer require \
open-telemetry/opentelemetry-auto-laravel \
open-telemetry/opentelemetry-auto-symfony \
open-telemetry/opentelemetry-auto-slim \
open-telemetry/opentelemetry-auto-psr15 \
open-telemetry/opentelemetry-auto-psr18
Or for specific frameworks:
composer require \
open-telemetry/sdk \
open-telemetry/exporter-otlp \
open-telemetry/opentelemetry-auto-laravel
Step 4: Configure Environment Variables
Configure the agent to export data to Uptrace. Replace <FIXME> with your actual Uptrace DSN, and myservice with a name that identifies your application:
export OTEL_PHP_AUTOLOAD_ENABLED=true
export OTEL_SERVICE_NAME=myservice
export OTEL_SERVICE_VERSION=1.0.0
export OTEL_TRACES_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.uptrace.dev
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=<FIXME>
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_PROPAGATORS=tracecontext,baggage
Note: OpenTelemetry PHP typically uses HTTP/Protobuf protocol. Uptrace Cloud accepts HTTP on port 443 (implicit with HTTPS) and gRPC on port 4317. For gRPC, use https://api.uptrace.dev:4317 and set OTEL_EXPORTER_OTLP_PROTOCOL=grpc.
Alternatively, add to your php.ini file:
[opentelemetry]
extension=opentelemetry.so
otel.php.autoload_enabled=true
otel.service.name=myservice
otel.traces.exporter=otlp
otel.exporter.otlp.endpoint=https://api.uptrace.dev
otel.exporter.otlp.headers=uptrace-dsn=<FIXME>
Step 5: Run Your Application
Run your PHP application as usual. The extension will automatically instrument it:
php -S localhost:8000
Step 6: View Your Trace
Navigate to the Uptrace UI to view your traces:

Auto-Instrumented Libraries
The OpenTelemetry PHP extension automatically instruments popular PHP frameworks and libraries when corresponding instrumentation packages are installed:
Web Frameworks
- Laravel - automatic request/response tracing, Eloquent queries
- Symfony - HTTP kernel, controllers, Doctrine ORM
- Slim - routing and middleware
- PSR-15 (HTTP Server Request Handlers) - middleware and request handlers
HTTP Clients
- PSR-18 (HTTP Client) - automatic tracing of HTTP requests
- Guzzle - HTTP client operations
- cURL - native PHP HTTP requests
Database Clients
- PDO (PHP Data Objects) - SQL queries and connections
- Doctrine DBAL/ORM - database operations
- Eloquent ORM (Laravel) - query builder and models
- mysqli - MySQL native driver
Other Libraries
- PSR-3 (Logger Interface) - log correlation with traces
- WordPress (community instrumentation available)
For the complete list of available instrumentation libraries, see OpenTelemetry PHP Instrumentation on Packagist.
Configuration Options
Environment Variables
Common configuration options for zero-code instrumentation:
# Service identification
export OTEL_SERVICE_NAME=my-php-app
export OTEL_SERVICE_VERSION=1.0.0
export OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.namespace=backend
# Enable auto-instrumentation
export OTEL_PHP_AUTOLOAD_ENABLED=true
# Exporter configuration
export OTEL_TRACES_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.uptrace.dev
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=<your_dsn>
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
# Sampling configuration
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1 # Sample 10% of traces
# Propagators
export OTEL_PROPAGATORS=tracecontext,baggage
# Logging
export OTEL_LOG_LEVEL=info
For the full list of configuration options, see the OpenTelemetry PHP Configuration documentation.
php.ini Configuration
You can also configure options in php.ini:
[opentelemetry]
extension=opentelemetry.so
; Extension validation
otel.php.validate_hook_functions=On
otel.php.conflicts=none
; Auto-instrumentation
otel.php.autoload_enabled=On
; Service configuration
otel.service.name=my-php-app
otel.service.version=1.0.0
; Exporter configuration
otel.traces.exporter=otlp
otel.exporter.otlp.endpoint=https://api.uptrace.dev
otel.exporter.otlp.headers=uptrace-dsn=<your_dsn>
otel.exporter.otlp.protocol=http/protobuf
; Sampling
otel.traces.sampler=parentbased_traceidratio
otel.traces.sampler.arg=1.0
Disabling Specific Instrumentations
Disable instrumentation for specific libraries by not installing their Composer packages. The extension only activates instrumentations for installed packages.
To disable after installation, remove the package:
composer remove open-telemetry/opentelemetry-auto-laravel
Production Deployment
Docker Example
FROM php:8.2-fpm
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
unzip \
libzip-dev \
&& rm -rf /var/lib/apt/lists/*
# Install OpenTelemetry extension
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions opentelemetry zip
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Copy application files
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader
COPY . .
# Configure OpenTelemetry via php.ini
RUN echo "extension=opentelemetry.so" >> /usr/local/etc/php/conf.d/opentelemetry.ini && \
echo "otel.php.autoload_enabled=On" >> /usr/local/etc/php/conf.d/opentelemetry.ini
# Set environment variables
ENV OTEL_SERVICE_NAME=my-php-app
ENV OTEL_TRACES_EXPORTER=otlp
ENV OTEL_EXPORTER_OTLP_ENDPOINT=https://api.uptrace.dev
EXPOSE 9000
CMD ["php-fpm"]
Docker Compose Example
version: '3.8'
services:
app:
build: .
volumes:
- ./:/app
environment:
- OTEL_SERVICE_NAME=laravel-app
- OTEL_SERVICE_VERSION=1.0.0
- OTEL_PHP_AUTOLOAD_ENABLED=true
- OTEL_TRACES_EXPORTER=otlp
- OTEL_METRICS_EXPORTER=otlp
- OTEL_EXPORTER_OTLP_ENDPOINT=https://api.uptrace.dev
- OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=${UPTRACE_DSN}
- OTEL_TRACES_SAMPLER=parentbased_traceidratio
- OTEL_TRACES_SAMPLER_ARG=0.1
nginx:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./:/app
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
PHP-FPM Configuration
For PHP-FPM deployments, pass environment variables via the pool configuration:
; /etc/php/8.2/fpm/pool.d/www.conf
[www]
; ... existing configuration ...
; OpenTelemetry environment variables
env[OTEL_SERVICE_NAME] = my-php-app
env[OTEL_PHP_AUTOLOAD_ENABLED] = true
env[OTEL_TRACES_EXPORTER] = otlp
env[OTEL_EXPORTER_OTLP_ENDPOINT] = https://api.uptrace.dev
env[OTEL_EXPORTER_OTLP_HEADERS] = uptrace-dsn=<your_dsn>
Restart PHP-FPM:
sudo systemctl restart php8.2-fpm
Kubernetes Deployment
For Kubernetes, include the extension in your container image and configure via environment variables:
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app
spec:
template:
spec:
containers:
- name: app
image: my-php-app:latest
env:
- name: OTEL_SERVICE_NAME
value: "php-app"
- name: OTEL_SERVICE_VERSION
value: "1.0.0"
- name: OTEL_PHP_AUTOLOAD_ENABLED
value: "true"
- name: OTEL_TRACES_EXPORTER
value: "otlp"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "https://api.uptrace.dev"
- name: OTEL_EXPORTER_OTLP_HEADERS
valueFrom:
secretKeyRef:
name: uptrace-secrets
key: dsn
For more details, see the Kubernetes monitoring guide.
Troubleshooting
Issue: Extension Not Loading
Symptom: No traces appear, php --ri opentelemetry shows nothing.
Solution:
- Verify extension is installed:
php --ri opentelemetry
# Should show: opentelemetry support => enabled
- Check php.ini includes the extension:
php --ini
grep -r "opentelemetry" /etc/php/*/
- Ensure
extension=opentelemetry.sois in the correct ini file:
echo "extension=opentelemetry.so" | sudo tee -a $(php --ini | grep "Loaded Configuration" | awk '{print $4}')
Issue: Auto-instrumentation Not Working
Symptom: Extension loaded but no traces generated.
Solution:
- Enable auto-instrumentation:
export OTEL_PHP_AUTOLOAD_ENABLED=true
- Verify instrumentation packages are installed:
composer show | grep opentelemetry-auto
- Check that Composer autoloader is included in your application:
// Ensure this is at the top of your entry point
require __DIR__ . '/vendor/autoload.php';
Issue: Missing Framework Traces
Symptom: HTTP requests traced but framework-specific operations missing.
Solution:
Install the framework-specific instrumentation package:
# Laravel
composer require open-telemetry/opentelemetry-auto-laravel
# Symfony
composer require open-telemetry/opentelemetry-auto-symfony
# Slim
composer require open-telemetry/opentelemetry-auto-slim
Issue: PHP-FPM Not Sending Traces
Symptom: CLI works but PHP-FPM requests don't generate traces.
Solution:
PHP-FPM doesn't inherit shell environment variables. Configure in pool config:
; /etc/php/8.2/fpm/pool.d/www.conf
env[OTEL_PHP_AUTOLOAD_ENABLED] = true
env[OTEL_SERVICE_NAME] = my-app
env[OTEL_TRACES_EXPORTER] = otlp
env[OTEL_EXPORTER_OTLP_ENDPOINT] = https://api.uptrace.dev
env[OTEL_EXPORTER_OTLP_HEADERS] = uptrace-dsn=<your_dsn>
Restart PHP-FPM after changes:
sudo systemctl restart php8.2-fpm
Issue: High Performance Overhead
Symptom: Response times increased after enabling instrumentation.
Solution:
- Enable sampling:
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1 # Sample only 10%
- Use batch span processor (configured automatically, but can be tuned):
export OTEL_BSP_SCHEDULE_DELAY=5000
export OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512
Limitations of Zero-Code Instrumentation
While zero-code instrumentation provides good observability, it has some limitations:
Generic Span Names
Automatic instrumentation generates generic span names:
- ❌ Automatic:
GET /api/orders - ✅ Manual:
ProcessHighPriorityOrders
No Business Context
Zero-code instrumentation can't capture domain-specific information:
// Automatically traced (HTTP request)
Route::get('/orders/{id}', function ($id) {
return Order::find($id);
});
// NOT traced (business logic details)
function processOrder($order, $user) {
// Business rules invisible without manual instrumentation
if ($user->isPremium()) {
applyDiscounts($order);
}
return $order;
}
To add business context, combine zero-code with tracing:
use OpenTelemetry\API\Globals;
function processOrder($order, $user) {
$tracer = Globals::tracerProvider()->getTracer('my-app');
$span = $tracer->spanBuilder('process_order')
->setAttribute('order.id', $order->id)
->setAttribute('user.id', $user->id)
->setAttribute('user.tier', $user->tier)
->startSpan();
$scope = $span->activate();
try {
$result = applyBusinessRules($order, $user);
$span->setAttribute('order.total', $result->total);
return $result;
} finally {
$scope->detach();
$span->end();
}
}
Framework Coverage Only
Only instrumented frameworks generate spans. Custom code and proprietary frameworks remain invisible without manual instrumentation.
Performance Overhead
Instrumenting all requests introduces overhead:
- Typical overhead: 2-5% CPU increase
- Memory overhead: 10-30MB additional memory
- Startup time: Minimal (extension loaded once per process)
For performance-critical applications, enable sampling to reduce overhead.
Zero-Code vs Manual Instrumentation
| Aspect | Zero-Code | Manual |
|---|---|---|
| Setup Time | 10-15 minutes | Hours to days |
| Code Changes | None | Extensive |
| Coverage | Framework/library operations only | Full application including business logic |
| Customization | Limited to environment variables | Complete control over spans and attributes |
| Maintenance | Update packages only | Code changes for new instrumentation |
| Performance | Moderate (all requests instrumented) | Lower (instrument only critical paths) |
| Best For | Quick start, legacy apps, proof-of-concept | Custom business metrics, fine-grained control |
Recommendation: Start with zero-code instrumentation for immediate visibility, then add tracing incrementally for business-critical operations.
Next Steps
- Learn about OpenTelemetry PHP Tracing API to add custom spans
- Configure OpenTelemetry Sampling for production environments
- Explore OpenTelemetry Metrics to track business KPIs
- Review Laravel monitoring guide for comprehensive setup
- Instrument PHP-FPM using the OpenTelemetry PHP-FPM guide for full request visibility
- Compare Symfony instrumentation for Symfony applications