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.

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:

  1. Install the OpenTelemetry PHP extension (PECL, package manager, or Docker)
  2. Enable the extension in php.ini
  3. Install framework-specific instrumentation libraries via Composer
  4. Configure environment variables
  5. 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:

shell PECL (Recommended)
# 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
shell Ubuntu/Debian
# Install from package repository
sudo apt-get install php-opentelemetry

# Verify installation
php --ri opentelemetry
shell Alpine Linux (Docker)
# Add testing repository
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories

# Install extension
apk add php82-pecl-opentelemetry@testing

# Verify
php --ri opentelemetry
dockerfile Docker (php-extension-installer)
FROM php:8.2-fpm

# Install OpenTelemetry extension
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions opentelemetry

# Verify installation
RUN php --ri opentelemetry

Verify the extension is loaded:

shell
php --ri opentelemetry
# Should display: opentelemetry support => enabled

Step 3: Install Instrumentation Libraries

Install OpenTelemetry SDK and framework-specific instrumentation packages:

shell
# 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:

shell Laravel
composer require \
    open-telemetry/sdk \
    open-telemetry/exporter-otlp \
    open-telemetry/opentelemetry-auto-laravel
shell Symfony
composer require \
    open-telemetry/sdk \
    open-telemetry/exporter-otlp \
    open-telemetry/opentelemetry-auto-symfony
shell Slim
composer require \
    open-telemetry/sdk \
    open-telemetry/exporter-otlp \
    open-telemetry/opentelemetry-auto-slim

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:

shell
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

Alternatively, add to your php.ini file:

ini
[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:

shell PHP Built-in Server
php -S localhost:8000
shell Laravel
php artisan serve
shell Symfony
symfony server:start
shell PHP-FPM + Nginx
# Ensure environment variables are passed to PHP-FPM
sudo systemctl restart php8.2-fpm
sudo systemctl restart nginx

Step 6: View Your Trace

Navigate to the Uptrace UI to view your traces:

Basic trace

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:

shell
# 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:

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:

shell
composer remove open-telemetry/opentelemetry-auto-laravel

Production Deployment

Docker Example

dockerfile
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

yaml
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:

ini
; /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:

shell
sudo systemctl restart php8.2-fpm

Kubernetes Deployment

For Kubernetes, include the extension in your container image and configure via environment variables:

yaml
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:

  1. Verify extension is installed:
shell
php --ri opentelemetry
# Should show: opentelemetry support => enabled
  1. Check php.ini includes the extension:
shell
php --ini
grep -r "opentelemetry" /etc/php/*/
  1. Ensure extension=opentelemetry.so is in the correct ini file:
shell
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:

  1. Enable auto-instrumentation:
shell
export OTEL_PHP_AUTOLOAD_ENABLED=true
  1. Verify instrumentation packages are installed:
shell
composer show | grep opentelemetry-auto
  1. Check that Composer autoloader is included in your application:
php
// 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:

shell
# 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:

ini
; /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:

shell
sudo systemctl restart php8.2-fpm

Issue: High Performance Overhead

Symptom: Response times increased after enabling instrumentation.

Solution:

  1. Enable sampling:
shell
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1  # Sample only 10%
  1. Use batch span processor (configured automatically, but can be tuned):
shell
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:

php
// 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:

php
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

AspectZero-CodeManual
Setup Time10-15 minutesHours to days
Code ChangesNoneExtensive
CoverageFramework/library operations onlyFull application including business logic
CustomizationLimited to environment variablesComplete control over spans and attributes
MaintenanceUpdate packages onlyCode changes for new instrumentation
PerformanceModerate (all requests instrumented)Lower (instrument only critical paths)
Best ForQuick start, legacy apps, proof-of-conceptCustom business metrics, fine-grained control

Recommendation: Start with zero-code instrumentation for immediate visibility, then add tracing incrementally for business-critical operations.

Next Steps

See Also