0, 'info' => 1, 'notice' => 2, 'warning' => 3, 'error' => 4, 'critical' => 5, 'alert' => 6, 'emergency' => 7, ]; private function __construct() { } public static function getInstance(): self { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } public static function init(string $path, string $level = 'warning'): void { $instance = self::getInstance(); $instance->logPath = rtrim($path, '/') . '/'; $instance->logLevel = $level; if (!is_dir($instance->logPath)) { mkdir($instance->logPath, 0750, true); } if (!is_writable($instance->logPath)) { throw new \RuntimeException("Log path is not writable: {$instance->logPath}"); } } public function log(string $level, string $message, array $context = []): void { if (!$this->shouldLog($level)) { return; } $timestamp = date('Y-m-d H:i:s'); $contextJson = !empty($context) ? ' ' . json_encode($context, JSON_UNESCAPED_SLASHES) : ''; $userInfo = $this->getUserInfo(); $logEntry = sprintf( "[%s] %s.%s: %s%s %s\n", $timestamp, strtoupper($level), $userInfo, $message, $contextJson, $this->getRequestInfo() ); $this->write($level, $logEntry); } public function debug(string $message, array $context = []): void { $this->log('debug', $message, $context); } public function info(string $message, array $context = []): void { $this->log('info', $message, $context); } public function notice(string $message, array $context = []): void { $this->log('notice', $message, $context); } public function warning(string $message, array $context = []): void { $this->log('warning', $message, $context); } public function error(string $message, array $context = []): void { $this->log('error', $message, $context); } public function critical(string $message, array $context = []): void { $this->log('critical', $message, $context); } public function alert(string $message, array $context = []): void { $this->log('alert', $message, $context); } public function emergency(string $message, array $context = []): void { $this->log('emergency', $message, $context); } private function shouldLog(string $level): bool { $currentLevel = $this->levels[$this->logLevel] ?? 3; $messageLevel = $this->levels[$level] ?? 3; return $messageLevel >= $currentLevel; } private function write(string $level, string $entry): void { $filename = $this->logPath . 'app-' . date('Y-m-d') . '.log'; file_put_contents($filename, $entry, FILE_APPEND | LOCK_EX); if (in_array($level, ['error', 'critical', 'alert', 'emergency'], true)) { $errorFile = $this->logPath . 'error-' . date('Y-m-d') . '.log'; file_put_contents($errorFile, $entry, FILE_APPEND | LOCK_EX); } } private function getUserInfo(): string { $userId = Session::get('user_id'); $username = Session::get('username'); if ($userId) { return "[User:{$userId}:{$username}]"; } return '[Guest]'; } private function getRequestInfo(): string { $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; $method = $_SERVER['REQUEST_METHOD'] ?? 'CLI'; $uri = $_SERVER['REQUEST_URI'] ?? ''; return "[{$ip}] [{$method} {$uri}]"; } }