SEO Metadata
SEO Title Options
- PHP Observability: Logging, Tracing & Metrics With
- PHP Observability: Logging, Tracing: Practical 2026 Guide
- Tooling Playbook: PHP Observability: Logging, Tracing
Meta Description Options
- Learn PHP Observability: Logging, Tracing & Metrics With OpenTelemetry with a practical Tooling framework, expert mistakes, implementation steps, examples.
- 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
- What PHP Observability: Logging, Tracing & Metrics With OpenTelemetry means
- Why it matters now
- Implementation framework
- Practical comparison
- Expert workflow
- Common mistakes
- Media and link plan
- Original technical deep dive
- FAQ
- Structured data
- Conclusion
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.
- Define the user problem and the production risk.
- Identify the smallest reliable implementation boundary.
- Keep configuration, secrets, and environment-specific behavior outside the article's core logic.
- Add tests for the behavior that would hurt if it regressed.
- Document the trade-off, not only the final code.
- Measure the result with logs, metrics, or user-facing outcomes.
- 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 area | Strong approach | Weak approach | Why it matters |
|---|---|---|---|
| Scope | Solve one clear problem | Mix unrelated concerns | Focus improves testing and search intent |
| Architecture | Put logic in explicit classes or documented boundaries | Hide behavior in templates or incidental callbacks | Future changes stay easier to review |
| Data flow | Pass prepared data into the view or endpoint | Query or compute in presentation code | Reduces regressions and performance surprises |
| Testing | Cover the risky behavior directly | Test only the happy path | Catches production failures earlier |
| Documentation | Explain trade-offs and limits | Repeat generic definitions | Builds E-E-A-T and reader trust |
| Operations | Track logs, metrics, and rollback steps | Ship without measurement | Makes 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]
Media and link plan
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.]
Trustworthy outbound links
- PHP manual - use this as the trust reference for language-level reference.
- OpenTelemetry documentation - use this as the trust reference for observability reference.
Internal linking opportunities
- Internal guide: The Role of Logging in Debugging: What to - use this when readers need a related Debugging follow-up.
- Internal guide: PHP Workers and Queues: Supervisor, Horizon - use this when readers need a related Tooling follow-up.
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:
| Mode | Use it for | Requirements |
|---|---|---|
| Zero-code instrumentation | Framework requests, HTTP clients, database clients, common libraries | PHP 8.0+, ext-opentelemetry, Composer autoloading, SDK, instrumentation packages |
| Manual instrumentation | Business spans, custom metrics, domain logs, gaps in auto instrumentation | SDK/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.nameservice.versionservice.namespacedeployment.environmentservice.instance.id
Bad resource attributes:
user_idorder_idrequest_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:
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.
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
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
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|failurepayment.provider=stripe|adyenqueue=emails|billinghttp.route=/checkout
Bad metric attributes:
user_idorder_idemail- 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.
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:
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:
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:
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_NAMEis set.OTEL_RESOURCE_ATTRIBUTESincludes 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.