fix: never persist a half-built user on first login
This commit is contained in:
@@ -4,8 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Logger;
|
||||
|
||||
use App\Entity\LogEntry;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Monolog\Handler\AbstractProcessingHandler;
|
||||
use Monolog\Level;
|
||||
use Monolog\LogRecord;
|
||||
@@ -13,30 +13,40 @@ use Monolog\LogRecord;
|
||||
/**
|
||||
* Monolog handler that persists log entries to the database.
|
||||
*
|
||||
* Writes log records to the LogEntry entity, replacing message placeholders
|
||||
* Writes log records to the log_entry table, replacing message placeholders
|
||||
* with context values. Includes channel, context, and extra data for auditing.
|
||||
* Only processes INFO level and above to avoid storing debug messages.
|
||||
*
|
||||
* The write goes through the DBAL connection rather than the EntityManager on purpose. A log record
|
||||
* can be emitted at any point, including halfway through somebody else's unit of work, and an
|
||||
* EntityManager flush would write out whatever that unit of work has pending so far — 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 EntityManagerInterface $entityManager)
|
||||
public function __construct(private readonly Connection $connection)
|
||||
{
|
||||
parent::__construct(Level::Info);
|
||||
}
|
||||
|
||||
protected function write(LogRecord $record): void
|
||||
{
|
||||
$message = $this->replacePlaceHolder($record);
|
||||
|
||||
$logEntry = new LogEntry($record->channel, $message);
|
||||
$logEntry
|
||||
->setContext($record->context)
|
||||
->setExtra($record->extra)
|
||||
->setErrorCode($record->extra['error_code'] ?? null)
|
||||
;
|
||||
|
||||
$this->entityManager->persist($logEntry);
|
||||
$this->entityManager->flush();
|
||||
$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
|
||||
|
||||
Reference in New Issue
Block a user