Back to blog

Tooling

PHP Observability: Logging, Tracing & Metrics With OpenTelemetry

Implements the three pillars of observability in PHP - structured logging, distributed tracing, and Prometheus metrics with OTEL PHP SDK.

  • PHP
  • OpenTelemetry
  • Observability
  • Logging
  • Metrics

SEO Metadata

SEO Title Options

  1. PHP Observability: Logging, Tracing & Metrics With
  2. PHP Observability: Logging, Tracing: Practical 2026 Guide
  3. Tooling Playbook: PHP Observability: Logging, Tracing

Meta Description Options

  1. Learn PHP Observability: Logging, Tracing & Metrics With OpenTelemetry with a practical Tooling framework, expert mistakes, implementation steps, examples.
  2. Implements the three pillars of observability in PHP - structured logging, distributed tracing, and Prometheus metrics with OTEL PHP SDK.

URL Slug

php-observability-logging-tracing-metrics-opentelemetry

Focus Keyword

PHP Observability: Logging, Tracing & Metrics With OpenTelemetry

Additional LSI Keywords

  • Tooling
  • PHP
  • OpenTelemetry
  • Observability
  • Logging
  • Metrics
  • PHP Observability: Logging, Tracing & Metrics With OpenTelemetry
  • production checklist
  • implementation guide
  • best practices
  • architecture decisions
  • testing strategy

Table of Contents

Article overview

PHP Observability: Logging, Tracing & Metrics With OpenTelemetry is the kind of topic that looks simple until it reaches production. Teams usually discover the real cost late: unclear boundaries, weak defaults, hidden maintenance work, and decisions that seemed harmless when the codebase was small.

The problem gets worse when the article, tutorial, or implementation guide only explains the happy path. This guide closes that gap with a practical framework, a comparison table, common mistakes, and a deep technical section you can use while planning real work.

Keep reading for the non-obvious part: the safest implementation is rarely the most impressive-looking one. It is the one your team can debug, test, document, and evolve without turning every future change into archaeology.

Key Takeaways

  • PHP Observability: Logging, Tracing & Metrics With OpenTelemetry should be evaluated as a production decision, not only as a syntax or tooling choice.
  • The best implementation keeps responsibilities visible, with clear ownership, tests, documentation, and rollback paths.
  • Search visibility improves when practical depth, structured answers, and expert examples live on the same page.

[IMAGE: A mobile-first technical article layout showing the main concept, decision table, implementation checklist, and FAQ blocks. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry expert guide for Tooling]

What PHP Observability: Logging, Tracing & Metrics With OpenTelemetry means

PHP Observability: Logging, Tracing & Metrics With OpenTelemetry means applying tooling knowledge to a concrete engineering decision, then turning that decision into reliable code, documentation, and operational behavior. In practice, it combines the topic's core concepts with trade-off analysis, implementation boundaries, testing strategy, and maintenance discipline.

This is the definition worth optimizing for featured snippets because it avoids hype. It tells the reader what the topic does and what a professional implementation must include.

Why it matters now

The technical web is more crowded than it was a few years ago. Thin tutorials can still get indexed, but they rarely earn trust from senior developers, buyers, AI answer systems, or teams that need production guidance.

For tooling topics, the strongest content now has three layers:

  • a clear answer for fast scanning
  • a practical framework for implementation
  • expert context that explains what breaks later

That same structure helps search engines understand the page. It also helps readers decide whether the advice fits their project.

Implementation framework

Use this framework before adopting the approach described in this article.

  1. Define the user problem and the production risk.
  2. Identify the smallest reliable implementation boundary.
  3. Keep configuration, secrets, and environment-specific behavior outside the article's core logic.
  4. Add tests for the behavior that would hurt if it regressed.
  5. Document the trade-off, not only the final code.
  6. Measure the result with logs, metrics, or user-facing outcomes.
  7. Revisit the decision after real usage exposes edge cases.

The sequence is deliberately conservative. It keeps the work grounded in outcomes instead of novelty.

[IMAGE: A seven-step implementation framework with discovery, boundary design, configuration, tests, documentation, measurement, and iteration. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry implementation framework]

Practical comparison

Decision areaStrong approachWeak approachWhy it matters
ScopeSolve one clear problemMix unrelated concernsFocus improves testing and search intent
ArchitecturePut logic in explicit classes or documented boundariesHide behavior in templates or incidental callbacksFuture changes stay easier to review
Data flowPass prepared data into the view or endpointQuery or compute in presentation codeReduces regressions and performance surprises
TestingCover the risky behavior directlyTest only the happy pathCatches production failures earlier
DocumentationExplain trade-offs and limitsRepeat generic definitionsBuilds E-E-A-T and reader trust
OperationsTrack logs, metrics, and rollback stepsShip without measurementMakes the decision reversible

This table is intentionally practical. It gives a reviewer something to check before the implementation becomes expensive to change.

Expert workflow

Expert tip: "Treat PHP Observability: Logging, Tracing & Metrics With OpenTelemetry as a system boundary. If the next developer cannot find where the decision lives, how it is tested, and when it should be avoided, the implementation is not finished."

A useful workflow is simple:

  • Start with the smallest working example.
  • Add the constraints that exist in your real project.
  • Remove anything that only demonstrates cleverness.
  • Write down the failure modes.
  • Add links to related decisions so future readers can navigate the topic cluster.

That last point matters for both humans and search systems. A single article can answer a question; a cluster proves authority.

Common mistakes

Mistake 1: Copying a pattern without its context

A pattern that works in a small demo can fail in a real application. The missing context is usually data volume, team experience, deployment process, security requirements, or observability.

Before copying the pattern, ask what assumption made it safe in the original example.

Mistake 2: Putting business logic in the wrong layer

This is the fastest way to make future debugging expensive. In Laravel, PHP, and server-rendered websites, presentation should receive prepared data, not discover rules on its own.

Keep decision logic in models, actions, services, policies, requests, jobs, or documented helpers where it can be tested directly.

Mistake 3: Optimizing for novelty instead of maintainability

Newer tools and language features can be valuable. They can also hide simple behavior behind unfamiliar syntax.

Use the option that makes the next production incident easier to understand.

Mistake 4: Publishing without a measurement plan

If the article describes a performance, SEO, security, or architecture improvement, define how success will be checked. Logs, tests, crawl diagnostics, analytics, and user behavior are all stronger than assumptions.

[IMAGE: A common-mistakes board with context loss, wrong layer, novelty bias, and missing measurement highlighted. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry common mistakes]

Image placeholders

  • [IMAGE: A concept diagram for PHP Observability: Logging, Tracing & Metrics With OpenTelemetry with input, decision boundary, implementation, tests, and production feedback. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry concept diagram]
  • [IMAGE: A mobile screenshot-style checklist for PHP Observability: Logging, Tracing & Metrics With OpenTelemetry. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry mobile checklist]
  • [IMAGE: A comparison table visualization for strong versus weak implementation choices. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry comparison table]

Video placeholder

[VIDEO: Insert a 5-8 minute YouTube walkthrough that demonstrates the main decision, the implementation boundary, the test strategy, and the production caveats for PHP Observability: Logging, Tracing & Metrics With OpenTelemetry.]

Internal linking opportunities

Original Technical Deep Dive

Observability is not "install an APM agent and hope."

For PHP applications, a useful observability setup has three jobs:

  • Logs explain what happened at a point in time.
  • Traces show how one request, job, command, or message moved across code and services.
  • Metrics show aggregate behavior over time and power alerts.

OpenTelemetry gives PHP a vendor-neutral way to emit those signals. It does not replace good application instrumentation. It gives your instrumentation one data model, one propagation format, and one pipeline to Jaeger, Tempo, Prometheus, Loki, Elasticsearch, Honeycomb, Datadog, New Relic, or another backend.

This guide was reviewed on May 7, 2026 against the OpenTelemetry PHP docs, OpenTelemetry Collector docs, Prometheus OTLP docs, and PSR-3.

The short version

Use this shape in production:

PHP app
  -> OpenTelemetry PHP SDK
  -> OTLP over HTTP or gRPC
  -> OpenTelemetry Collector
  -> traces backend, logs backend, metrics backend

Start with:

pecl install opentelemetry

composer require \
  open-telemetry/sdk \
  open-telemetry/exporter-otlp \
  php-http/guzzle7-adapter \
  monolog/monolog \
  open-telemetry/opentelemetry-logger-monolog

Then configure the app:

OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_SERVICE_NAME=checkout-api
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.version=2025.08.11
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
OTEL_PROPAGATORS=baggage,tracecontext

Do not start by instrumenting every function. Start with request boundaries, queue jobs, database calls, external HTTP calls, error paths, and business events that support incident response.

What OpenTelemetry does in PHP

OpenTelemetry PHP can emit:

  • Traces.
  • Metrics.
  • Logs.

There are two instrumentation modes:

ModeUse it forRequirements
Zero-code instrumentationFramework requests, HTTP clients, database clients, common librariesPHP 8.0+, ext-opentelemetry, Composer autoloading, SDK, instrumentation packages
Manual instrumentationBusiness spans, custom metrics, domain logs, gaps in auto instrumentationSDK/API code in your app

The extension alone does not create useful telemetry. It enables auto-instrumentation hooks. You still need the SDK, exporter, and instrumentation libraries.

For Laravel:

composer require \
  open-telemetry/sdk \
  open-telemetry/exporter-otlp \
  open-telemetry/opentelemetry-auto-laravel

For Symfony:

composer require \
  open-telemetry/sdk \
  open-telemetry/exporter-otlp \
  open-telemetry/opentelemetry-auto-symfony

Then add manual instrumentation where the framework cannot know your business meaning.

Define the service identity first

Telemetry without a stable service identity becomes noise.

Set these everywhere:

OTEL_SERVICE_NAME=checkout-api
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.version=2025.08.11,service.namespace=commerce

Good resource attributes:

  • service.name
  • service.version
  • service.namespace
  • deployment.environment
  • service.instance.id

Bad resource attributes:

  • user_id
  • order_id
  • request_id
  • full hostnames when pods are ephemeral and extremely high churn
  • raw paths containing IDs

Resource attributes attach to all telemetry from the process. Keep them stable and low-cardinality.

Run the Collector locally

Use the Collector even in development. It catches configuration problems early.

otel-collector.yaml:

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  batch: {}

exporters:
  debug:
    verbosity: basic

  prometheus:
    endpoint: 0.0.0.0:9464

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug]

    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus, debug]

    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug]

Run it:

docker run --rm \
  -p 4317:4317 \
  -p 4318:4318 \
  -p 9464:9464 \
  -v "$PWD/otel-collector.yaml:/etc/otelcol/config.yaml" \
  otel/opentelemetry-collector:latest

Point PHP at it:

OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

Prometheus can scrape the Collector metrics endpoint:

scrape_configs:
  - job_name: otel-collector
    static_configs:
      - targets: ['otel-collector:9464']

This is the safest starting point because traces, logs, and metrics all flow through one local component before you add production backends.

[IMAGE: Supporting visual 1 for PHP Observability: Logging, Tracing & Metrics With OpenTelemetry, showing PHP Observability: Logging, Tracing & Metrics With OpenTelemetry decisions, examples, and PHP, OpenTelemetry, Observability. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry php-observability-logging-tracing-metrics-opentelemetry visual 1]

[IMAGE: Supporting visual 1 for PHP Observability: Logging, Tracing & Metrics With OpenTelemetry, showing PHP Observability: Logging, Tracing & Metrics With OpenTelemetry decisions, examples, and PHP, OpenTelemetry, Observability. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry php-observability-logging-tracing-metrics-opentelemetry visual 1]

Alternative: send OTLP metrics directly to Prometheus

Prometheus can ingest OTLP metrics over HTTP when the OTLP receiver is enabled:

prometheus --web.enable-otlp-receiver

Then configure PHP metrics:

OTEL_METRICS_EXPORTER=otlp
OTEL_TRACES_EXPORTER=none
OTEL_LOGS_EXPORTER=none
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://prometheus:9090/api/v1/otlp
OTEL_METRIC_EXPORT_INTERVAL=15000

Prometheus treats OTEL_EXPORTER_OTLP_METRICS_ENDPOINT as a base URL and appends the signal path. Do not append /v1/metrics yourself unless your SDK documentation specifically says to use the full signal URL.

For most systems, the Collector is still the better default because it can batch, retry, fan out, and reshape telemetry before it hits storage.

Structured logging in PHP

Use PSR-3 as the application logging interface. Use Monolog or your framework logger behind it.

Bad log:

$logger->info('User ' . $user->id . ' paid order ' . $order->id . ' with card ' . $cardNumber);

Problems:

  • Message is not stable.
  • User and order IDs are embedded in free text.
  • Sensitive card data is logged.
  • Grouping and querying become harder.

Better:

<?php

declare(strict_types=1);

use Psr\Log\LoggerInterface;

final readonly class CheckoutLogger
{
    public function __construct(
        private LoggerInterface $logger,
    ) {}

    public function paymentAccepted(string $orderId, int $amountCents, string $currency): void
    {
        $this->logger->info('payment accepted', [
            'event' => 'payment.accepted',
            'order_id' => $orderId,
            'amount_cents' => $amountCents,
            'currency' => $currency,
        ]);
    }
}

Keep the message stable. Put searchable details in context.

Send Monolog logs through OpenTelemetry

OpenTelemetry does not expect most PHP code to call an OpenTelemetry logger directly. The normal path is a logging library integration.

<?php

declare(strict_types=1);

use Monolog\Logger;
use OpenTelemetry\API\Globals;
use OpenTelemetry\Contrib\Logs\Monolog\Handler;
use Psr\Log\LogLevel;

require __DIR__ . '/vendor/autoload.php';

$handler = new Handler(
    Globals::loggerProvider(),
    LogLevel::INFO,
);

$logger = new Logger('checkout', [$handler]);

$logger->info('payment accepted', [
    'event' => 'payment.accepted',
    'order_id' => 'ord_123',
    'amount_cents' => 4999,
    'currency' => 'EUR',
]);

When a log is emitted inside an active span, OpenTelemetry PHP can attach the active trace_id and span_id. That correlation is the difference between "there was an error" and "this exact failing request called this exact provider and then threw this exception."

Logging rules that prevent future pain

Use static log messages:

$logger->warning('payment provider retry scheduled', [
    'event' => 'payment.retry_scheduled',
    'provider' => 'stripe',
    'attempt' => 2,
]);

Do not use unbounded values as log fields unless you need them:

// Risky: full request body may contain secrets and huge payloads.
$logger->debug('request body received', [
    'body' => $requestBody,
]);

Prefer safe summaries:

$logger->debug('request body received', [
    'content_type' => $contentType,
    'body_bytes' => strlen($requestBody),
]);

Always log exceptions under the PSR-3 exception key:

try {
    $gateway->charge($payment);
} catch (Throwable $exception) {
    $logger->error('payment charge failed', [
        'event' => 'payment.charge_failed',
        'provider' => 'stripe',
        'order_id' => $payment->orderId,
        'exception' => $exception,
    ]);

    throw $exception;
}

Never log passwords, tokens, raw card numbers, session cookies, authorization headers, reset links, or full webhook payloads from untrusted parties.

Distributed tracing

A trace is a tree of spans. A span represents one timed operation.

Examples:

  • HTTP request.
  • Controller action.
  • SQL query.
  • Redis command.
  • Queue job.
  • External API call.
  • Payment authorization.
  • PDF rendering step.

Auto-instrumentation is good for technical spans. Manual instrumentation is where you add business meaning.

Add a manual business span

<?php

declare(strict_types=1);

use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\Context\Context;

final readonly class ChargeCustomer
{
    public function __construct(
        private PaymentGateway $gateway,
    ) {}

    public function __invoke(Payment $payment): PaymentReceipt
    {
        $tracer = Globals::tracerProvider()->getTracer('app.checkout', '1.0.0');

        $span = $tracer
            ->spanBuilder('checkout.charge_customer')
            ->startSpan();

        $scope = Context::storage()->attach(
            $span->storeInContext(Context::getCurrent()),
        );

        try {
            $span->setAttribute('payment.provider', $payment->provider);
            $span->setAttribute('payment.currency', $payment->currency);
            $span->setAttribute('payment.amount_cents', $payment->amountCents);

            $receipt = $this->gateway->charge($payment);

            $span->addEvent('payment accepted', [
                'payment.provider' => $payment->provider,
            ]);

            return $receipt;
        } catch (Throwable $exception) {
            $span->recordException($exception);
            $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());

            throw $exception;
        } finally {
            $scope->detach();
            $span->end();
        }
    }
}

Do not put order_id, email, user_id, or raw URLs on every span by default. Trace attributes are indexed by many backends. High-cardinality attributes can make tracing expensive and noisy.

[IMAGE: Supporting visual 2 for PHP Observability: Logging, Tracing & Metrics With OpenTelemetry, showing PHP Observability: Logging, Tracing & Metrics With OpenTelemetry decisions, examples, and PHP, OpenTelemetry, Observability. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry php-observability-logging-tracing-metrics-opentelemetry visual 2]

Use IDs selectively:

  • Fine: payment.provider=stripe
  • Fine: payment.currency=EUR
  • Fine: feature_flag.checkout_v2=true
  • Risky: user.email=customer@example.com
  • Risky: order_id=ord_2a45e... on every span
  • Bad: request.body=...

[IMAGE: Supporting visual 2 for PHP Observability: Logging, Tracing & Metrics With OpenTelemetry, showing PHP Observability: Logging, Tracing & Metrics With OpenTelemetry decisions, examples, and PHP, OpenTelemetry, Observability. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry php-observability-logging-tracing-metrics-opentelemetry visual 2]

Trace incoming and outgoing HTTP

A PHP service needs both sides:

  • Incoming request span: what route, status code, duration, and error happened.
  • Outgoing client span: which downstream service was called and how long it took.

With auto-instrumentation packages, supported HTTP frameworks and PSR-18 clients can create these spans and propagate W3C trace context headers.

For custom HTTP clients, inject the trace headers explicitly through your chosen propagator or keep the client behind a wrapper that can be instrumented once.

Do not put raw URLs with IDs into span names:

Bad span name:
GET /api/users/123/orders/456

Good span name:
GET /api/users/{userId}/orders/{orderId}

Use route templates for names and low-cardinality attributes. Put raw IDs in logs only when you need request-level debugging and your retention/security policy allows it.

Metrics in PHP

Metrics are for aggregate behavior. They should answer questions like:

  • Is error rate above normal?
  • Is p95 checkout duration too high?
  • Is the queue backlog growing?
  • Are retries increasing?
  • Are external API calls timing out?

OpenTelemetry PHP supports counters, histograms, up/down counters, and observable instruments.

Add counters and histograms

<?php

declare(strict_types=1);

use OpenTelemetry\API\Globals;

final class CheckoutMetrics
{
    private $orders;

    private $duration;

    public function __construct()
    {
        $meter = Globals::meterProvider()->getMeter('app.checkout', '1.0.0');

        $this->orders = $meter->createCounter(
            'checkout.orders',
            'orders',
            'Completed checkout attempts',
        );

        $this->duration = $meter->createHistogram(
            'checkout.duration',
            'ms',
            'Checkout duration in milliseconds',
        );
    }

    public function recordSuccess(float $durationMs, string $provider): void
    {
        $attributes = [
            'result' => 'success',
            'payment.provider' => $provider,
        ];

        $this->orders->add(1, $attributes);
        $this->duration->record($durationMs, $attributes);
    }

    public function recordFailure(float $durationMs, string $provider, string $reason): void
    {
        $attributes = [
            'result' => 'failure',
            'payment.provider' => $provider,
            'failure.reason' => $reason,
        ];

        $this->orders->add(1, $attributes);
        $this->duration->record($durationMs, $attributes);
    }
}

Keep metric names stable. Renaming a metric breaks dashboards and alerts.

Good metric attributes:

  • result=success|failure
  • payment.provider=stripe|adyen
  • queue=emails|billing
  • http.route=/checkout

Bad metric attributes:

  • user_id
  • order_id
  • email
  • raw url
  • exception message
  • SQL query text

Metric attributes become labels in Prometheus-style systems. High-cardinality labels are one of the fastest ways to make metrics storage expensive.

Add observable gauges

Use observable gauges for current values such as queue depth, active workers, or cache entries.

<?php

declare(strict_types=1);

use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Metrics\ObserverInterface;

final readonly class QueueMetrics
{
    public function __construct(
        private QueueDepthRepository $queueDepths,
    ) {
        $meter = Globals::meterProvider()->getMeter('app.queue', '1.0.0');

        $meter
            ->createObservableGauge(
                'queue.depth',
                'jobs',
                'Current number of queued jobs',
            )
            ->observe(function (ObserverInterface $observer): void {
                foreach ($this->queueDepths->all() as $queue => $depth) {
                    $observer->observe($depth, [
                        'queue' => $queue,
                    ]);
                }
            });
    }
}

Do not query a slow database table inside every metrics callback. If the value is expensive, update a cheap local cache and observe that.

The three signals together

A checkout failure should produce one coherent story.

Metric:

checkout.orders{result="failure",payment.provider="stripe",failure.reason="timeout"} 1

Trace:

HTTP POST /checkout
  checkout.charge_customer
    HTTP POST https://api.stripe.com/v1/payment_intents

Log:

$logger->error('payment charge failed', [
    'event' => 'payment.charge_failed',
    'provider' => 'stripe',
    'failure_reason' => 'timeout',
    'order_id' => $payment->orderId,
    'exception' => $exception,
]);

[IMAGE: Supporting visual 3 for PHP Observability: Logging, Tracing & Metrics With OpenTelemetry, showing PHP Observability: Logging, Tracing & Metrics With OpenTelemetry decisions, examples, and PHP, OpenTelemetry, Observability. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry php-observability-logging-tracing-metrics-opentelemetry visual 3]

The metric pages you. The trace shows where time was spent. The log gives exact business context for the failed operation.

Sampling traces without breaking logs and metrics

Do not sample logs and metrics the same way you sample traces.

Common production pattern:

  • Keep all critical logs.
  • Keep aggregate metrics.
  • Sample successful traces.
  • Keep failed or slow traces at a higher rate.

Example environment:

OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.10

A 10 percent trace sample can be enough for normal traffic, but incidents need failure visibility. Use Collector processors or backend rules to retain errors, high latency requests, and important routes.

[IMAGE: Supporting visual 3 for PHP Observability: Logging, Tracing & Metrics With OpenTelemetry, showing PHP Observability: Logging, Tracing & Metrics With OpenTelemetry decisions, examples, and PHP, OpenTelemetry, Observability. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry php-observability-logging-tracing-metrics-opentelemetry visual 3]

If a trace is sampled out, your logs and metrics still need to be useful.

Laravel integration shape

For Laravel, start with auto-instrumentation for framework spans, then add manual business telemetry in services and jobs.

Example service provider:

<?php

declare(strict_types=1);

namespace App\Providers;

use App\Observability\CheckoutMetrics;
use Illuminate\Support\ServiceProvider;

final class ObservabilityServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(CheckoutMetrics::class);
    }

    public function boot(CheckoutMetrics $metrics): void
    {
        // Resolving the singleton registers observable callbacks.
    }
}

For queue jobs:

<?php

declare(strict_types=1);

namespace App\Jobs;

use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\Context\Context;
use Throwable;

final class CapturePayment
{
    public function handle(): void
    {
        $tracer = Globals::tracerProvider()->getTracer('app.jobs', '1.0.0');
        $span = $tracer->spanBuilder('job.capture_payment')->startSpan();
        $scope = Context::storage()->attach($span->storeInContext(Context::getCurrent()));

        try {
            // Capture payment.
        } catch (Throwable $exception) {
            $span->recordException($exception);
            $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());

            throw $exception;
        } finally {
            $scope->detach();
            $span->end();
        }
    }
}

Long-running queue workers must flush telemetry before shutdown. If your process manager sends SIGTERM during deploys, make sure the worker gets enough time to finish the current job and flush providers.

Symfony integration shape

For Symfony, use auto-instrumentation for HttpKernel spans, then add manual spans to handlers and services.

Example Messenger middleware:

<?php

declare(strict_types=1);

namespace App\Messenger;

use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\Context\Context;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;
use Throwable;

final class TraceMessageMiddleware implements MiddlewareInterface
{
    public function handle(Envelope $envelope, StackInterface $stack): Envelope
    {
        $message = $envelope->getMessage();
        $tracer = Globals::tracerProvider()->getTracer('app.messenger', '1.0.0');

        $span = $tracer
            ->spanBuilder('message ' . $message::class)
            ->startSpan();

        $scope = Context::storage()->attach($span->storeInContext(Context::getCurrent()));

        try {
            return $stack->next()->handle($envelope, $stack);
        } catch (Throwable $exception) {
            $span->recordException($exception);
            $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());

            throw $exception;
        } finally {
            $scope->detach();
            $span->end();
        }
    }
}

For high-volume systems, avoid using full class names as metric labels. For spans, class names are usually manageable. For metrics, prefer a small stable message category:

$attributes = [
    'message.type' => 'payment_capture',
];

Operational checks

Before shipping observability changes, prove each signal works.

Traces:

curl -i http://localhost/checkout

Check that a trace appears with:

  • service name
  • route name or template
  • HTTP status
  • downstream spans
  • exception events for failures

Logs:

rg "payment accepted|payment charge failed" var/log

Check that production log records include:

  • stable event name
  • severity
  • service name
  • trace ID and span ID when inside an active span
  • no secrets

Metrics:

curl -s http://localhost:9464/metrics | rg "checkout|queue"

Check that metrics include:

  • stable names
  • units
  • low-cardinality labels
  • useful success/failure dimensions
  • values that move during a test request or job

Alert on symptoms, debug with detail

Good alerts use metrics:

checkout failure rate > 3 percent for 5 minutes
payment provider p95 latency > 2 seconds for 10 minutes
queue depth > 1000 jobs for 15 minutes

Bad alerts use one-off logs:

page when any "payment failed" log appears

One failure might be normal. A rising error rate is a user problem.

[IMAGE: Supporting visual 4 for PHP Observability: Logging, Tracing & Metrics With OpenTelemetry, showing PHP Observability: Logging, Tracing & Metrics With OpenTelemetry decisions, examples, and PHP, OpenTelemetry, Observability. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry php-observability-logging-tracing-metrics-opentelemetry visual 4]

Use logs and traces after the metric alert fires.

Production checklist

Before rollout:

  • OTEL_SERVICE_NAME is set.
  • OTEL_RESOURCE_ATTRIBUTES includes environment and version.
  • The OpenTelemetry extension is installed where auto-instrumentation is expected.
  • The SDK and exporter packages are installed.
  • Framework instrumentation packages match the app framework.
  • OTLP endpoint and protocol are correct.
  • Collector has batching enabled.
  • Prometheus metrics are visible.
  • Trace IDs appear in logs emitted inside spans.
  • Sensitive data is redacted before logging.
  • Metric labels do not include user IDs, order IDs, raw URLs, or exception messages.
  • Queue workers flush telemetry on shutdown.
  • Dashboards use service, route, result, provider, and environment dimensions.
  • Alerts are based on rates, latency, saturation, and error budgets.

Common failure modes

No traces appear:

php --ri opentelemetry
composer show | rg "open-telemetry"

The extension may be missing, OTEL_PHP_AUTOLOAD_ENABLED may be false, or no instrumentation package is installed.

[IMAGE: Supporting visual 4 for PHP Observability: Logging, Tracing & Metrics With OpenTelemetry, showing PHP Observability: Logging, Tracing & Metrics With OpenTelemetry decisions, examples, and PHP, OpenTelemetry, Observability. Alt: PHP Observability: Logging, Tracing & Metrics With OpenTelemetry php-observability-logging-tracing-metrics-opentelemetry visual 4]

Logs appear but have no trace IDs:

The log is emitted outside an active span, the Monolog OpenTelemetry handler is not used, or the logger is bypassing the OpenTelemetry pipeline.

Metrics appear with too many time series:

Search for high-cardinality attributes:

rg -n "user_id|order_id|email|url|exception.message" src

Collector receives data but backend does not:

Run the Collector with debug exporter first. Once the signal is visible there, fix the backend exporter.

Prometheus has no OTLP metrics:

Confirm whether you are using Collector scrape mode or direct OTLP mode. For direct OTLP mode, Prometheus must run with:

--web.enable-otlp-receiver

FAQ

What is PHP Observability: Logging, Tracing & Metrics With OpenTelemetry?

PHP Observability: Logging, Tracing & Metrics With OpenTelemetry is a practical tooling topic that should be evaluated through implementation scope, production risk, testing, documentation, and long-term maintainability.

When should a team use PHP Observability: Logging, Tracing & Metrics With OpenTelemetry?

Use PHP Observability: Logging, Tracing & Metrics With OpenTelemetry when it solves a real project constraint, improves clarity, or reduces operational risk. Avoid it when it only adds novelty or hides behavior from future maintainers.

What is the biggest risk with PHP Observability: Logging, Tracing & Metrics With OpenTelemetry?

The biggest risk is copying a pattern without its context. Production systems need clear boundaries, rollback options, tests, and observability before a technique becomes dependable.

How do you test PHP Observability: Logging, Tracing & Metrics With OpenTelemetry?

Test the smallest unit that owns the behavior, then add integration coverage for the path users or systems actually rely on. Include failure cases, configuration differences, and regression checks.

How does PHP Observability: Logging, Tracing & Metrics With OpenTelemetry affect SEO and AI search visibility?

It improves visibility when the article gives a direct answer, expert context, structured headings, internal links, trustworthy references, and FAQ content that matches the visible page.

Conclusion

PHP Observability: Logging, Tracing & Metrics With OpenTelemetry is worth doing when the implementation improves clarity, reliability, or delivery speed. It is not worth doing when it hides ownership, increases operational risk, or makes the system harder to explain.

Use the framework above as a review checklist. Then connect this topic to the rest of the project documentation so readers can move from concept to implementation without losing context.

Top