Периодически сталкиваюсь с неправильным использованием
$context в PSR логгере. Давайте вместе внимательно перечитаем стандарт.1.3.1 Every method accepts an array as context data. This is meant to hold any extraneous information that does not fit well in a string. The array can contain anything. Implementors MUST ensure they treat context data with as much lenience as possible. A given value in the context MUST NOT throw an exception nor raise any php error, warning or notice.
Жирным выделил то, что многие упускают: вы можете передавать в контекст абсолютно любые значения, в том числе массивы, объекты, замыкания и ресурсы. Не нужно самостоятельно проводить нормализацию. По стандарту это задача логгера, а не ваша. Вот как, например, Monolog обработает контекст со сложными элементами:
$logger = new Monolog\Logger(
name: 'app',
handlers: [new Monolog\Handler\StreamHandler(STDOUT)],
processors: [new Monolog\Processor\PsrLogMessageProcessor()]
);
final readonly class Foo
{
public function __construct(
public string $public = 'bar',
private string $private = 'baz',
) {}
}
$logger->debug('Message', [
'birthday' => new DateTimeImmutable('06.04.2019'),
'object' => new Foo(),
'closure' => static fn (): int => 1,
'resource' => STDIN,
]);
[2024-09-27T13:02:16.303651+00:00] app.DEBUG: Message {"birthday":"2019-04-06T00:00:00+00:00","object":{"Foo":{"public":"bar"}},"closure":{"Closure":[]},"resource":"[resource(stream)]"} []
1.3.2 If an Exception object is passed in the context data, it MUST be in the 'exception' key. Logging exceptions is a common pattern and this allows implementors to extract a stack trace from the exception when the log backend supports it. Implementors MUST still verify that the 'exception' key is actually an Exception before using it as such, as it MAY contain anything.
Исключения нужно добавлять в контекст с ключом
'exception'. Никогда не раскладывайте объект исключения на массив вида ['message' => $e->getMessage(), 'line' => $e->getLine(), ...] — так вы не даёте процессорам и форматтерам самим принять решение относительно состава лога исключения и глубины его нормализации.
$logger->critical('Oops!', [
'exception' => new LogicException(
message: 'Level 2',
previous: new RuntimeException('Level 1'),
),
]);
[2024-09-27T13:14:04.880133+00:00] app.CRITICAL: Oops! {"exception":"[object] (LogicException(code: 0): Level 1 at test.php:27)\n[previous exception] [object] (RuntimeException(code: 0): Level 2 at test.php:29)"} []
1.2.2 The message MAY contain placeholders which implementors MAY replace with values from the context array.
Placeholder names MUST correspond to keys in the context array.
Placeholder names MUST be delimited with a single opening brace{and a single closing brace}.
Placeholder names SHOULD be composed only of the charactersA-Z,a-z,0-9, underscore_, and period.. The use of other characters is reserved for future modifications of the placeholders specification.
Users SHOULD NOT pre-escape placeholder values since they can not know in which context the data will be displayed.
Ну и конечно же не забывайте про плейсхолдеры в сообщении. Вместо интерполяции и
sprintf куда удобнее писать так:
$logger->notice('Process {pid} started at {time}', [
'pid' => getmypid(),
'time' => new DateTimeImmutable(),
]);
[2024-09-27T13:23:29.306682+00:00] app.NOTICE: Process 45104 started at 2024-09-27T13:23:29.306681+00:00 {"pid":45104,"time":"2024-09-27T13:23:29+00:00"} []