PSR-3 Telemetry Bridge
This bridge has a PSR-3 LoggerInterface implementation on Flow PHP
Telemetry. Each framework or library that depends on Psr\Log\LoggerInterface can write through this adapter. Its
records then become Telemetry log signals.
Installation
For the installation instructions, read the installation page.
Overview
This bridge wraps Flow\Telemetry\Logger\Logger behind a PSR-3 LoggerInterface. It:
- Maps PSR-3 log levels to Telemetry severities (8 → 6, customizable).
- Does PSR-3
{placeholder}interpolation with the context array (only scalars andStringable). - Stores each context entry as a log record attribute under its raw key.
- Sends a
Throwableunder theexceptionkey throughLogRecord::setException(). This fillsexception.type,exception.message, andexception.stacktrace. - Throws
Psr\Log\InvalidArgumentExceptionfor alog()call with an unknown level, as the specification says.
Basic Usage
<?php
use function Flow\Bridge\Psr3\Telemetry\DSL\psr3_telemetry_logger;
use function Flow\Telemetry\DSL\telemetry;
$telemetry = telemetry($resource);
$psrLogger = psr3_telemetry_logger($telemetry->logger('my-service'));
$psrLogger->info('User {user_id} logged in', ['user_id' => 123]);
// → body: 'User 123 logged in'
// → severity: INFO
// → attributes: {user_id: 123}
The returned TelemetryLogger is a drop-in Psr\Log\LoggerInterface. You can give it to each thing that accepts a
PSR-3 logger (Symfony, Laravel, third-party libraries).
Severity Mapping
The default mapping maps the eight PSR-3 levels to the six OTEL-aligned Telemetry severities:
| PSR-3 Level | Telemetry Severity |
|---|---|
debug |
DEBUG |
info |
INFO |
notice |
INFO |
warning |
WARN |
error |
ERROR |
critical |
FATAL |
alert |
FATAL |
emergency |
FATAL |
To override the mapping, give a custom psr3_severity_mapper() to psr3_log_record_converter():
<?php
use Flow\Telemetry\Logger\Severity;
use Psr\Log\LogLevel;
use function Flow\Bridge\Psr3\Telemetry\DSL\{psr3_log_record_converter, psr3_severity_mapper, psr3_telemetry_logger};
$converter = psr3_log_record_converter(
severityMapper: psr3_severity_mapper([
LogLevel::DEBUG => Severity::TRACE,
LogLevel::INFO => Severity::INFO,
LogLevel::NOTICE => Severity::WARN,
LogLevel::WARNING => Severity::WARN,
LogLevel::ERROR => Severity::ERROR,
LogLevel::CRITICAL => Severity::FATAL,
LogLevel::ALERT => Severity::FATAL,
LogLevel::EMERGENCY => Severity::FATAL,
]),
);
$psrLogger = psr3_telemetry_logger($telemetry->logger('my-service'), $converter);
Context Handling
Attributes
Each context entry becomes an attribute on the emitted LogRecord, with the same key:
$psrLogger->info('Request processed', [
'http.method' => 'GET',
'http.status' => 200,
'duration_ms' => 12.4,
]);
// attributes: {http.method: 'GET', http.status: 200, duration_ms: 12.4}
AttributeValueNormalizer normalizes non-scalar values:
null→'null'DateTimeInterface,Throwable→ passed through- arrays → recursively normalized
- objects with
__toString()→ string cast - objects without
__toString()→ class name - other types →
get_debug_type()result
Exceptions
LogRecord::setException() unpacks a Throwable under the exception key into the standard OTEL exception
attributes:
try {
// ...
} catch (\Throwable $e) {
$psrLogger->error('Operation failed', ['exception' => $e]);
}
// attributes:
// exception.type: 'RuntimeException'
// exception.message: '...'
// exception.stacktrace: '...'
The original exception key is not stored as a separate attribute. A non-Throwable value under exception is a
regular attribute.
Message Interpolation
As PSR-3 §1.2 says, matching context entries replace the {placeholder} tokens in the message body. Only scalars
and Stringable objects are used. Arrays, Throwable instances, and plain objects stay in the template. The context
entries are always also available as attributes.
$psrLogger->warning('Quota exceeded for {tenant} ({used}/{limit})', [
'tenant' => 'acme',
'used' => 105,
'limit' => 100,
]);
// body: 'Quota exceeded for acme (105/100)'
// attributes: {tenant: 'acme', used: 105, limit: 100}
DSL Functions
psr3_telemetry_logger()
Wraps a Flow Telemetry Logger in a PSR-3 LoggerInterface.
$psrLogger = psr3_telemetry_logger(
logger: $telemetry->logger('my-service'),
converter: $converter, // optional, defaults to LogRecordConverter()
);
psr3_log_record_converter()
Builds the converter that turns each PSR-3 call into a LogRecord. Use it to add a custom severity mapper or value
normalizer.
$converter = psr3_log_record_converter(
severityMapper: psr3_severity_mapper([...]),
valueNormalizer: psr3_attribute_value_normalizer(),
);
psr3_severity_mapper()
Builds a SeverityMapper. Give null (default) for the standard PSR-3 → OTEL mapping, or a full PSR-3 → Severity
array to override it.
$mapper = psr3_severity_mapper([
LogLevel::DEBUG => Severity::TRACE,
// ... rest of PSR-3 levels
]);
psr3_attribute_value_normalizer()
Builds the default AttributeValueNormalizer. It exists for symmetry with the other DSL helpers. Most users do not need it.
$normalizer = psr3_attribute_value_normalizer();
Found a typo or an outdated section? Edit this page on GitHub