'monolog', 'deprecated' => 'monolog_deprecated' ]; private array $statements = [ 'default' => null, 'deprecated' => null ]; private array $baseQueries = [ 'check' => "SHOW TABLES LIKE '%s'", 'create' => "CREATE TABLE IF NOT EXISTS %s (channel VARCHAR(255), level VARCHAR(100), message LONGTEXT, time DATETIME, context LONGTEXT, extra LONGTEXT)", 'insert' => "INSERT INTO %s (channel, level, message, time, context, extra) VALUES (:channel, :level, :message, :time, :context, :extra)", 'delete' => "DELETE FROM %s WHERE time < DATE_SUB(CURDATE(), INTERVAL %d DAY)" ]; public function __construct(protected Connection $connection, protected int $retainDays = 90, int|string|Level $level = Level::Debug, bool $bubble = true) { parent::__construct($level, $bubble); } public function write(LogRecord $record): void { if (!$this->initialized) { if (!$this->checkTablesExist()) { $this->createTables(); } $this->cleanup(); $this->initialized(); } if (str_contains(strtolower($record->message), 'deprecated:')) { $this->statements['deprecated']->execute([ 'channel' => $record->channel, 'level' => $record->level->getName(), 'message' => $record->formatted, 'time' => $record->datetime->format('Y-m-d H:i:s.u'), 'context' => (count($record->context) > 0) ? json_encode($record->context, JSON_UNESCAPED_SLASHES) : '', 'extra' => (count($record->extra) > 0) ? json_encode($record->extra, JSON_UNESCAPED_SLASHES) : '' ]); return; } $this->statements['default']->execute([ 'channel' => $record->channel, 'level' => $record->level->getName(), 'message' => $record->formatted, 'time' => $record->datetime->format('Y-m-d H:i:s.u'), 'context' => (count($record->context) > 0) ? json_encode($record->context, JSON_UNESCAPED_SLASHES) : '', 'extra' => (count($record->extra) > 0) ? json_encode($record->extra, JSON_UNESCAPED_SLASHES) : '' ]); } private function initialized(): void { foreach ($this->tables as $type => $table) { $query = sprintf($this->baseQueries['insert'], $table); $this->statements[$type] = $this->connection->getPDO()->prepare($query); } $this->initialized = true; } private function checkTablesExist(): bool { return array_all($this->tables, fn($table) => $this->checkTableExists($table)); } private function checkTableExists(string $table): bool { $query = sprintf($this->baseQueries['check'], $table); try { $result = $this->connection->query($query); } catch (PDOException) { return false; } return $result->rowCount() > 0; } private function createTables(): void { foreach ($this->tables as $table) { if (!$this->checkTableExists($table)) { $this->createTable($table); } } } private function createTable(string $table): void { $query = sprintf($this->baseQueries['create'], $table); try { $result = $this->connection->getPDO()->exec($query); if ($result === false) { throw new PDOException('Failed to create table: ' . $table); } } catch (PDOException) {} } private function cleanup(): void { foreach ($this->tables as $table) { $query = sprintf($this->baseQueries['delete'], $table, $this->retainDays); try { $result = $this->connection->getPDO()->query($query); if ($result === false) { throw new PDOException('Failed to delete from table: ' . $table); } } catch (PDOException) {} } } }