Files
myep/src/Logger/DatabaseHandler.php
T

63 lines
1.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Logger;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Types\Types;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Level;
use Monolog\LogRecord;
/**
* Persists log entries to the database.
*
* The write goes through DBAL rather than the EntityManager on purpose. A log record can be emitted
* halfway through somebody else's unit of work, and an EntityManager flush would write out whatever
* that unit of work has pending — which is how a half-built User once reached the database with a
* NULL password. Staying out of the UnitOfWork also keeps logging alive after a failed flush has
* closed the EntityManager, which is exactly when there is something worth logging.
*
* Reads still go through the LogEntry entity and its repository.
*/
class DatabaseHandler extends AbstractProcessingHandler
{
public function __construct(private readonly Connection $connection)
{
parent::__construct(Level::Info);
}
protected function write(LogRecord $record): void
{
$this->connection->insert('log_entry', [
'channel' => $record->channel,
'message' => $this->replacePlaceHolder($record),
'context' => $record->context,
'extra' => $record->extra,
'error_code' => $record->extra['error_code'] ?? null,
'created_at' => new \DateTimeImmutable('now'),
], [
'context' => Types::JSON,
'extra' => Types::JSON,
'created_at' => Types::DATETIME_IMMUTABLE,
]);
}
private function replacePlaceHolder(LogRecord $record): string
{
$message = $record->message;
if (!str_contains($message, '{')) {
return $message;
}
$replacements = [];
foreach ($record->context as $k => $v) {
$replacements['{'.$k.'}'] = $v;
}
return strtr($message, $replacements);
}
}