'DEBUG', self::INFO => 'INFO', self::WARNING => 'WARNING', self::ERROR => 'ERROR', ]; public function debug(string $message, array $context = []); public function info(string $message, array $context = []): void; public function warning(string $message, array $context = []): void; public function error(string $message, array $context = []): void; } final class SimpleLogger implements Logger { private string $name; private array $handlers; /** * @param callable[] $handlers */ public function __construct( string $name, array $handlers = [] ) { $this->name = $name; $this->handlers = $handlers; } public function addHandler(callable $fn) { $this->handlers[] = $fn; } public function debug(string $message, array $context = []) { $this->log(self::DEBUG, $message, $context); } public function info(string $message, array $context = []): void { $this->log(self::INFO, $message, $context); } public function warning(string $message, array $context = []): void { $this->log(self::WARNING, $message, $context); } public function error(string $message, array $context = []): void { $this->log(self::ERROR, $message, $context); } private function log(int $level, string $message, array $context = []): void { $ignoredMessages = [ 'Format name invalid', 'Unknown format given', 'Unable to find channel', ]; foreach ($ignoredMessages as $ignoredMessage) { if (str_starts_with($message, $ignoredMessage)) { return; } } foreach ($this->handlers as $handler) { $handler([ 'name' => $this->name, 'created_at' => now(), 'level' => $level, 'level_name' => self::LEVEL_NAMES[$level], 'message' => $message, 'context' => $context, ]); } } } final class StreamHandler { private string $stream; private int $level; public function __construct(string $stream, int $level = Logger::DEBUG) { $this->stream = $stream; $this->level = $level; } public function __invoke(array $record) { if ($record['level'] < $this->level) { return; } if (isset($record['context']['e'])) { /** @var \Throwable $e */ $e = $record['context']['e']; unset($record['context']['e']); $record['context']['type'] = get_class($e); $record['context']['code'] = $e->getCode(); $record['context']['message'] = sanitize_root($e->getMessage()); $record['context']['file'] = sanitize_root($e->getFile()); $record['context']['line'] = $e->getLine(); $record['context']['url'] = get_current_url(); $record['context']['trace'] = trace_to_call_points(trace_from_exception($e)); } $context = ''; if ($record['context']) { try { $context = Json::encode($record['context']); } catch (\JsonException $e) { $record['context']['message'] = null; $context = Json::encode($record['context']); } } $text = sprintf( "[%s] %s.%s %s %s\n", $record['created_at']->format('Y-m-d H:i:s'), $record['name'], $record['level_name'], $record['message'], $context ); $bytes = file_put_contents($this->stream, $text, FILE_APPEND); } } final class ErrorLogHandler { private int $level; public function __construct(int $level = Logger::DEBUG) { $this->level = $level; } public function __invoke(array $record) { if ($record['level'] < $this->level) { return; } if (isset($record['context']['e'])) { /** @var \Throwable $e */ $e = $record['context']['e']; unset($record['context']['e']); $record['context']['type'] = get_class($e); $record['context']['code'] = $e->getCode(); $record['context']['message'] = sanitize_root($e->getMessage()); $record['context']['file'] = sanitize_root($e->getFile()); $record['context']['line'] = $e->getLine(); $record['context']['url'] = get_current_url(); $record['context']['trace'] = trace_to_call_points(trace_from_exception($e)); } $context = ''; if ($record['context']) { try { $context = Json::encode($record['context']); } catch (\JsonException $e) { $record['context']['message'] = null; $context = Json::encode($record['context']); } } // Intentionally omitting newline $text = sprintf( '[%s] %s.%s %s %s', $record['created_at']->format('Y-m-d H:i:s'), $record['name'], $record['level_name'], $record['message'], $context ); error_log($text); } } final class NullLogger implements Logger { public function debug(string $message, array $context = []) { } public function info(string $message, array $context = []): void { } public function warning(string $message, array $context = []): void { } public function error(string $message, array $context = []): void { } }