Observability for PHP with OpenTelemetry

Khimananda Oli 7 min read Programming and Languages
Observability for PHP with OpenTelemetry

By Khimananda Oli | Last reviewed: August 2026

Debugging latency in a distributed PHP application requires more than error logs; you need correlated signals across services. Observability for PHP with OpenTelemetry provides the standardized instrumentation necessary to trace requests from Nginx through your application code, queues, and databases. While earlier versions of the ecosystem were experimental, the 2026 stable releases of the PHP SDK and auto-instrumentation packages now make production-grade tracing reliable and performant. This guide covers the exact configuration needed to instrument a modern PHP stack without degrading throughput.

PHP ApplicationAuto-InstrumentationManual SpansOTLP ExporterCollector / GatewayBatch & ProcessObservability BackendTrace StoreMetrics / LogsgRPC / HTTPEnriched Data
Data flow architecture for observability for PHP with OpenTelemetry showing signal export path

How do you install OpenTelemetry auto-instrumentation for PHP?

The foundation of instrumenting an app with OpenTelemetry in the PHP ecosystem is the auto-instrumentation package. Unlike Java or Go, PHP requires explicit installation of hook libraries because the language runtime does not support bytecode manipulation natively. In 2026, the recommended approach uses Composer to manage dependencies alongside the optional but highly recommended C extension for performance.

Install core packages and extensions

You must install both the SDK and the specific auto-instrumentation hooks for your framework. For a typical Laravel application running on PHP 8.4+, execute the following:

composer require open-telemetry/opentelemetry-auto-instrumentation \
    open-telemetry/auto-laravel \
    open-telemetry/exporter-otlp \
    open-telemetry/transport-grpc

# Install the C extension for zero-cost hook execution (recommended)
pecl install opentelemetry
echo "extension=opentelemetry.so" >> /etc/php/8.4/cli/conf.d/20-opentelemetry.ini
echo "extension=opentelemetry.so" >> /etc/php/8.4/fpm/conf.d/20-opentelemetry.ini

If you cannot install PECL extensions due to shared hosting restrictions or container constraints, the SDK falls back to FFI (Foreign Function Interface) or userland hooks. However, be aware that userland hooks add measurable overhead. For any high-traffic production system, especially in cost-sensitive environments common among Nepal-based startups, the C extension is effectively mandatory to keep CPU tax below 3%.

Configure environment variables

OpenTelemetry PHP adheres strictly to the OTel specification for configuration. Avoid hardcoding endpoints in your application source. Instead, inject these variables via your deployment manifest, Dockerfile, or systemd unit:

  • OTEL_SERVICE_NAME: Identifies your service in traces (e.g., payment-api, laravel-web).
  • OTEL_EXPORTER_OTLP_ENDPOINT: The full URL of your collector or backend (e.g., http://otel-collector:4317 for gRPC).
  • OTEL_TRACES_EXPORTER: Set to otlp for standard backends.
  • OTEL_PHP_AUTOLOAD_ENABLED: Set to true to activate auto-instrumentation hooks.
  • OTEL_RESOURCE_ATTRIBUTES: Add semantic attributes like deployment.environment=production,host.name=web-01.

When should you add manual spans to PHP applications?

Auto-instrumentation captures infrastructure boundaries—HTTP requests, database queries, Redis calls, and queue jobs. It cannot understand your business logic. When debugging complex workflows, such as a multi-step checkout or a report generation pipeline, you need manual spans to create visibility inside your domain code. This distinction is critical when implementing metrics, logs, and traces compared effectively; traces provide the structural skeleton, while manual spans add the semantic muscle.

Create spans with the global tracer provider

Use the static API to avoid passing tracer instances through every service constructor. This keeps your code clean while maintaining full trace context propagation:

use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\SpanKind;

$tracer = Globals::tracerProvider()->getTracer('order-processing');

$span = $tracer->spanBuilder('calculate-shipping-rates')
    ->setSpanKind(SpanKind::KIND_INTERNAL)
    ->setAttribute('shipping.origin', 'Kathmandu')
    ->setAttribute('shipping.destination', 'Pokhara')
    ->startSpan();

try {
    $rates = $this->shippingService->getRates($origin, $destination);
    $span->setAttribute('shipping.rate_count', count($rates));
} catch (\Throwable $e) {
    $span->recordException($e);
    $span->setStatus(\OpenTelemetry\API\Trace\StatusCode::STATUS_ERROR);
    throw $e;
} finally {
    $span->end();
}

A common mistake I see in audits is failing to call $span->end() in a finally block. If an exception occurs before the span closes, it leaks memory and breaks the parent-child relationship in the trace visualization. Always use try-finally or a dedicated scope manager if your framework supports it.

HTTP RequestAuto: Laravel RouteManual: Business LogicAuto: DB QueryAuto: Redis CacheCustom Attributes & EventsDomain Context
Trace hierarchy showing auto-instrumented frames wrapping manual business logic spans

How does OpenTelemetry PHP compare to legacy monitoring agents?

Many teams in Nepal and South Asia still rely on proprietary APM agents or basic log parsing. Migrating to OpenTelemetry as the observability standard involves trade-offs. Understanding these differences prevents buyer's remorse and helps justify the migration effort to stakeholders accustomed to vendor-specific dashboards.

FeatureLegacy Proprietary AgentOpenTelemetry PHP SDK (2026)
Vendor Lock-inHigh; proprietary data formatsNone; OTLP is an open CNCF standard
Installation OverheadOpaque binary blobTransparent Composer deps + optional ext
Context PropagationOften limited to single vendorW3C Trace Context standard compliant
Cost ModelPer-host or per-GB ingestion feesFree SDK; pay only for backend storage
Queue/CLI SupportFrequently broken or unsupportedFirst-class support via auto-hooks
Community & DocsVendor support ticketsCNCF community, GitHub, Slack

In practice, the biggest win for PHP shops is W3C Trace Context propagation. If your PHP app calls a Node.js microservice or receives traffic from an Nginx ingress controller, OpenTelemetry seamlessly stitches those hops into a single trace. Legacy agents often break at these boundaries unless every component uses the same vendor's agent.

What are the performance best practices for PHP tracing?

Instrumentation is useless if it causes the outage you're trying to diagnose. PHP's share-nothing architecture means every request pays the initialization cost. Follow these operational guidelines to maintain SLOs while gaining visibility.

Sampling and batching strategies

Never trace 100% of traffic in production unless you have very low volume. Configure head-based sampling to capture a representative subset while keeping resource usage predictable:

# Sample 10% of requests in production
OTEL_TRACES_SAMPLER=parentbased_tracealways
OTEL_TRACES_SAMPLER_ARG=0.1

# Batch exports to reduce network syscalls
OTEL_BSP_SCHEDULE_DELAY=5000
OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512

For high-traffic e-commerce sites during peak seasons like Dashain or Black Friday, consider implementing a custom sampler that always records errors and slow requests (>2s) while sampling successful fast requests at 1%. This ensures you never miss anomalies while discarding noise.

Avoid high-cardinality attributes

Tagging spans with user IDs, session tokens, or full URLs creates cardinality explosions that crash metric backends and inflate trace storage costs. Stick to low-cardinality semantic conventions: http.route instead of http.url, user.role instead of user.id. If you must store high-cardinality data for debugging, use span events or log correlation rather than indexed span attributes.

CPU Overhead Comparison (Lower is Better)0%5%10%Userland Hooks~8.5% CPUC Extension~2.1% CPU4x Reduction
Performance impact comparison showing C extension efficiency for PHP observability workloads

How do you verify OpenTelemetry instrumentation locally?

Before deploying to production, validate your setup locally to avoid silent failures. The most effective method is running a local collector with console export or using the official OTel CLI tool.

  1. Start a local collector: Use Docker to run otel/opentelemetry-collector-contrib with a debug receiver configured to print spans to stdout.
  2. Set endpoint to localhost: Point OTEL_EXPORTER_OTLP_ENDPOINT to http://localhost:4317.
  3. Trigger representative traffic: Hit key routes, dispatch queue jobs, and trigger error paths.
  4. Inspect output: Verify span names follow semantic conventions, parent-child relationships are correct, and no sensitive data leaks in attributes.
  5. Check baggage propagation: Ensure headers like traceparent appear in outgoing HTTP client calls from your PHP app.

This verification step catches misconfigured samplers, missing extensions, and broken context propagation before they impact production reliability metrics. For teams adopting distributed tracing with OpenTelemetry and Jaeger, this local feedback loop accelerates instrumentation development significantly.

Implementing Sustainable Observability for PHP with OpenTelemetry

Adopting observability for PHP with OpenTelemetry transforms how you diagnose production issues, moving from reactive log grepping to proactive trace analysis. Start with auto-instrumentation to establish baseline visibility, then strategically add manual spans where business complexity demands it. Monitor the overhead of your instrumentation itself—treat your observability pipeline with the same rigor as your application code. If you need help designing a compliant, performant tracing architecture for your PHP stack, reach out to discuss your specific requirements.

Frequently Asked Questions

Yes, the opentelemetry-php SDK v1.5 fully supports PHP 8.4 including JIT compilation and fibers. Install via Composer and enable the extension for automatic context propagation across async operations without code changes.

Require open-telemetry/opentelemetry and laravel-instrumentation via Composer. Publish the config file, set OTEL_SERVICE_NAME and OTEL_EXPORTER_OTLP_ENDPOINT in your env file, then register the middleware in bootstrap/app.php for automatic request tracing.

Typically two to five percent CPU overhead with sampling enabled. Use trace sampling at 10% for high-traffic production environments to reduce serialization costs while maintaining statistical accuracy for latency analysis and error detection.

Yes, the ext-opentelemetry PECL extension automatically instruments PDO, mysqli, Redis, and Guzzle without manual span creation. Ensure the extension loads before application code to capture database queries and cache operations with proper context.

Configure OTEL_EXPORTER_OTLP_ENDPOINT to point to your collector or backend like Grafana Tempo or Datadog. Use HTTP/protobuf for lower overhead than gRPC in PHP since native gRPC support requires additional extensions and memory allocation.

Use the Span::setAttribute method within your code or leverage Laravel middleware hooks. Add business context like user_id or order_total as semantic convention attributes to enable filtering and correlation in your observability backend dashboard.

Yes, major platforms use it in production since 2024. Enable batch exporting, configure appropriate sampling rates, and monitor exporter queue depth to prevent memory issues during traffic spikes or backend connectivity failures.

Xdebug provides function-level profiling for development only. OpenTelemetry offers distributed tracing across services with minimal production overhead, contextual metadata, and integration with modern observability stacks for real-time monitoring and alerting.

Missing spans usually result from unloaded extensions, incorrect sampler configuration, or context loss in CLI workers. Verify ext-opentelemetry is active, check OTEL_TRACES_SAMPLER settings, and ensure context propagation headers pass through queue jobs.

Always use TLS encryption for OTLP endpoints in production. Sanitize sensitive attributes before export by configuring attribute filters in your instrumentation library to prevent leaking PII, tokens, or credentials to observability backends.

Yes, inject resource attributes like host.name, container.id, and k8s.pod.name during SDK initialization. This enables joining application traces with Prometheus or Datadog infrastructure metrics using shared dimensional tags for root cause analysis.

Check OTEL_EXPORTER_OTLP_ENDPOINT accessibility, verify TLS certificates, and inspect php-fpm error logs for timeout warnings. Enable OTEL_LOG_LEVEL=debug temporarily to diagnose authentication issues, network policies, or malformed protobuf payloads blocking exports.

Yes, both runtimes are supported. For PHP-FPM, each request creates isolated trace context. Swoole requires explicit context management using coroutine-local storage to prevent span leakage between concurrent requests handled by the same worker process.

Use parent-based trace ID ratio sampling at 5-10% for production. This preserves complete traces for sampled requests while dropping unsampled ones early, reducing export volume without breaking distributed trace continuity across service boundaries.

Costs depend on span volume and backend pricing. A typical Laravel app generating 1M spans monthly costs $50-200 on managed platforms. Reduce expenses through aggressive sampling, attribute filtering, and retaining only error traces beyond seven days.