OpenTelemetry Logs for PHP
This document covers OpenTelemetry Logs for PHP, focusing on integrating popular logging libraries like Monolog.
Prerequisites
Make sure your exporter is configured before you start instrumenting code. Follow Getting started with OpenTelemetry PHP or set up Direct OTLP Configuration first.
If you are not familiar with logs terminology like structured logging or log-trace correlation, read the introduction to OpenTelemetry Logs first.
Overview
OpenTelemetry provides two approaches for collecting logs in PHP:
- Log bridges (recommended): Integrate with existing logging libraries to automatically capture logs and correlate them with traces.
- Logs API: Use the native OpenTelemetry Logs API directly for maximum control.
Log bridges are the recommended approach because they allow you to use familiar logging APIs while automatically adding trace context (trace_id, span_id) to your logs.
Monolog integration
Monolog is PHP's most popular logging library. OpenTelemetry provides a handler to send Monolog logs to your observability backend.
Installation
composer require monolog/monolog open-telemetry/opentelemetry-logger-monolog
Basic configuration
<?php
use Monolog\Logger;
use OpenTelemetry\Contrib\Logs\Monolog\Handler;
use OpenTelemetry\API\Logs\LoggerProviderInterface;
// Get the logger provider from your OpenTelemetry configuration
$loggerProvider = $container->get(LoggerProviderInterface::class);
// Create Monolog logger with OpenTelemetry handler
$logger = new Logger('app');
$logger->pushHandler(new Handler(
$loggerProvider,
'info' // Minimum log level
));
Complete example
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use Monolog\Logger;
use OpenTelemetry\API\Common\Instrumentation\Globals;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\Contrib\Logs\Monolog\Handler;
// Configure Uptrace
$uptrace = Uptrace\Distro::builder()
->setDsn('YOUR_UPTRACE_DSN_HERE')
->setServiceName('myservice')
->setServiceVersion('1.0.0')
->buildAndRegisterGlobal();
// Create Monolog logger with OpenTelemetry handler
$loggerProvider = Globals::loggerProvider();
$logger = new Logger('myservice');
$logger->pushHandler(new Handler($loggerProvider, 'info'));
// Create tracer
$tracer = Globals::tracerProvider()->getTracer('myservice');
// Start a traced operation
$span = $tracer->spanBuilder('process-request')->startSpan();
$scope = $span->activate();
try {
// Logs automatically include trace context
$logger->info('Processing request', [
'user_id' => '12345',
'action' => 'login',
]);
$logger->error('Authentication failed', [
'reason' => 'invalid_credentials',
]);
} finally {
$scope->detach();
$span->end();
}
echo "Trace URL: " . $uptrace->traceUrl($span) . PHP_EOL;
PSR-3 logging integration
If you use a PSR-3 compatible logger, you can add trace context using a processor:
<?php
use Monolog\Logger;
use Monolog\Processor\ProcessorInterface;
use OpenTelemetry\API\Trace\Span;
class TraceContextProcessor implements ProcessorInterface
{
public function __invoke(array $record): array
{
$spanContext = Span::getCurrent()->getContext();
if ($spanContext->isValid()) {
$record['extra']['trace_id'] = $spanContext->getTraceId();
$record['extra']['span_id'] = $spanContext->getSpanId();
$record['extra']['trace_flags'] = $spanContext->getTraceFlags();
}
return $record;
}
}
// Add processor to your logger
$logger = new Logger('app');
$logger->pushProcessor(new TraceContextProcessor());
Log-trace correlation
When you emit a log within an active trace span, OpenTelemetry automatically includes:
- trace_id: Links log to the entire distributed trace
- span_id: Links log to the specific operation
- trace_flags: Indicates if the trace is sampled
This enables bidirectional navigation between logs and traces in your observability backend.
Manual correlation
If you can't use log bridges, manually inject trace context:
<?php
use OpenTelemetry\API\Trace\Span;
use Psr\Log\LoggerInterface;
function logWithContext(LoggerInterface $logger, string $message, array $context = []): void
{
$spanContext = Span::getCurrent()->getContext();
if ($spanContext->isValid()) {
$context['trace_id'] = $spanContext->getTraceId();
$context['span_id'] = $spanContext->getSpanId();
}
$logger->info($message, $context);
}
// Usage
logWithContext($logger, 'User logged in', ['user_id' => '12345']);
Accessing trace context
Get trace context information for custom logging:
<?php
use OpenTelemetry\API\Trace\Span;
function getTraceContext(): array
{
$spanContext = Span::getCurrent()->getContext();
if (!$spanContext->isValid()) {
return [];
}
return [
'trace_id' => $spanContext->getTraceId(),
'span_id' => $spanContext->getSpanId(),
'trace_flags' => sprintf('%02x', $spanContext->getTraceFlags()),
'is_remote' => $spanContext->isRemote(),
];
}
// Use in your logging
$context = getTraceContext();
echo sprintf("Current trace: %s\n", json_encode($context));
Best practices
Use structured logging
Use context arrays to add structured fields:
// Good: Structured fields enable filtering and analysis
$logger->info('Database query executed', [
'query_type' => 'SELECT',
'table' => 'users',
'duration_ms' => 45,
'rows_affected' => 1,
]);
// Avoid: Unstructured messages are harder to analyze
$logger->info('SELECT query on users took 45ms and returned 1 row');
Log within span context
Always log within an active span for automatic correlation:
// Good: Logs are correlated with trace
$span = $tracer->spanBuilder('process-order')->startSpan();
$scope = $span->activate();
try {
$logger->info('Processing order', ['order_id' => $orderId]);
processOrder($orderId);
$logger->info('Order processed successfully');
} finally {
$scope->detach();
$span->end();
}
// Less useful: Logs not correlated with trace
$logger->info('Processing order', ['order_id' => $orderId]);
$span = $tracer->spanBuilder('process-order')->startSpan();
// ...
Use appropriate log levels
Choose log levels based on the information type:
$logger->debug('Detailed debugging information'); // Development only
$logger->info('General operational events'); // Normal operations
$logger->warning('Unexpected but handled events'); // Potential issues
$logger->error('Errors that need attention'); // Failures
$logger->critical('System-level failures'); // Critical issues
Avoid logging sensitive data
Never log passwords, tokens, or PII:
// Bad: Logging sensitive data
$logger->info('User login', ['password' => $password, 'token' => $token]);
// Good: Redact sensitive fields
$logger->info('User login', ['user_id' => $userId, 'ip_address' => $ip]);