PSR-3 defines Psr\Log\LoggerInterface: one method per syslog severity (emergency down to debug), each taking a message with {placeholders} and a context array whose exception key holds a Throwable. Monolog 21,402 (github.com/Seldaek/monolog (https://github.com/Seldaek/monolog 21,402 )), used by Laravel 2,157 and Symfony 6,093 , implements it; 3.12.0 (September 2026) needs PHP 8.1. After composer require monolog/monolog, a Logger is a channel plus handlers (destinations with a minimum level) and processors (fields added to every record).
<?php
require __DIR__ . '/vendor/autoload.php';
use Monolog\{Level, Logger, Formatter\JsonFormatter, Handler\StreamHandler};
use Monolog\Processor\{PsrLogMessageProcessor, UidProcessor};
$json = new StreamHandler(__DIR__ . '/logs/shop.json', Level::Warning);
$json->setFormatter(new JsonFormatter());
$log = new Logger('shop', [new StreamHandler('php://stdout', Level::Info), $json]);
$log->pushProcessor(new PsrLogMessageProcessor())->pushProcessor(new UidProcessor(8));
function checkout(Psr\Log\LoggerInterface $log, string $sku): void {
$log->debug('cart loaded'); // below Info: dropped
$log->info('checkout of {sku}', ['sku' => $sku]);
$log->warning('out of stock', ['exception' => new RuntimeException("$sku: 1 left", 409)]);
}
checkout($log, 'BK-101');
echo file_get_contents(__DIR__ . '/logs/shop.json');[2026-09-23T08:30:06.368975+00:00] shop.INFO: checkout of BK-101 {"sku":"BK-101"}
{"uid":"89c4e66d"}
[2026-09-23T08:30:06.369476+00:00] shop.WARNING: out of stock {"exception":"[object]
(RuntimeException(code: 409): BK-101: 1 left at /home/dev/shop/logging.php:12)"}
{"uid":"89c4e66d"}
{"message":"out of stock","context":{"exception":{"class":"RuntimeException","message":"BK-101:
1 left","code":409,"file":"/home/dev/shop/logging.php:12"}},"level":300,"level_name":"WARNING
","channel":"shop","datetime":"2026-09-23T08:30:06.369476+00:00","extra":{"uid":"89c4e66d"}
}The console got readable lines; the file got only the warning, as one JSON object per line for a log search. Both share one uid, tying a request's lines together. Monolog 3 levels are the Level enum and records are LogRecord objects, not Monolog 2's arrays.