/
proxyr
/
Logger
Обзор
Документация
Войти
/
proxyr
/
Logger
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
master
logger.php
456 строк
16 KB
forge
update logger.php
28 сен 2024, 13:46
28 сен 2024, 13:46
08917b0
Код
Авторство
О чём код?
<?php namespace RRK; /** * Класс Logger предоставляет гибкий и настраиваемый способ логирования. * Использует паттерн Singleton для обеспечения единственного экземпляра логгера. */ class Logger { /** * Экземпляр Singleton. * @var Logger|null */ private static $instance = null; /** * Уровни логирования. */ const LEVEL_EMERGENCY = 'emergency'; const LEVEL_ALERT = 'alert'; const LEVEL_CRITICAL = 'critical'; const LEVEL_ERROR = 'error'; const LEVEL_WARNING = 'warning'; const LEVEL_NOTICE = 'notice'; const LEVEL_INFO = 'info'; const LEVEL_DEBUG = 'debug'; /** * Шаблон строки лога по умолчанию. */ private $format = "{date} {level}: {message} {context} {source}"; /** * Формат даты по умолчанию. */ private $dateFormat = "Y-m-d\TH:i:s"; /** * Разрешенные уровни логирования. * @var array */ private $allowedLevels = []; // По умолчанию логируются все уровни /** * Имя файла по умолчанию для записи логов. * @var string */ private $defaultFilename = 'debug.txt'; /** * Флаг использования директории скрипта для логирования. * @var bool */ private $useScriptDirectory = false; /** * Флаг включения информации об источнике вызова. * @var bool */ private $includeSource = true; /** * Максимальный размер файла лога в байтах (по умолчанию 5 MB). * @var int */ private $maxFileSize = 5 * 1024 * 1024; /** * Флаг использования цветового выделения для консольного вывода. * @var bool */ private $useColors = false; /** * Допустимые уровни логирования. * @var array */ private $allowedMethods = [ self::LEVEL_EMERGENCY, self::LEVEL_ALERT, self::LEVEL_CRITICAL, self::LEVEL_ERROR, self::LEVEL_WARNING, self::LEVEL_NOTICE, self::LEVEL_INFO, self::LEVEL_DEBUG, ]; /** * Получает экземпляр Singleton класса Logger. * @return Logger */ public static function getInstance(): Logger { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } /** * Закрытый конструктор для реализации Singleton. */ private function __construct() { // Инициализация при создании экземпляра } /** * Предотвращает клонирование экземпляра Singleton. */ private function __clone() { } /** * Предотвращает десериализацию экземпляра Singleton. */ private function __wakeup() { } // --- Методы настройки --- /** * Устанавливает уровни логирования, которые нужно отслеживать. * Если передать пустой массив, будут отслеживаться все уровни. * @param array $levels - Массив допустимых уровней. */ public function setAllowedLevels(array $levels = []) { $this->allowedLevels = array_map('strtolower', $levels); } /** * Устанавливает имя файла по умолчанию для логирования. * @param string $filename - Имя файла лога. */ public function setDefaultFilename(string $filename) { $this->defaultFilename = $filename; } /** * Устанавливает флаг использования директории скрипта для логирования. * @param bool $use - True для использования директории скрипта. */ public function setUseScriptDirectory(bool $use) { $this->useScriptDirectory = $use; } /** * Устанавливает флаг включения информации об источнике вызова. * @param bool $include - True для включения информации об источнике. */ public function setIncludeSource(bool $include) { $this->includeSource = $include; } /** * Устанавливает шаблон формата строки лога. * @param string $format - Шаблон формата. */ public function setFormat(string $format) { $this->format = $format; } /** * Устанавливает формат даты для сообщений лога. * @param string $dateFormat - Формат даты. */ public function setDateFormat(string $dateFormat) { $this->dateFormat = $dateFormat; } /** * Устанавливает максимальный размер файла лога. * @param int $size - Максимальный размер в байтах. */ public function setMaxFileSize(int $size) { $this->maxFileSize = $size; } /** * Устанавливает флаг использования цветового выделения для консольного вывода. * @param bool $useColors - True для использования цветов. */ public function setUseColors(bool $useColors) { $this->useColors = $useColors; } // --- Методы логирования --- public function emergency($message, $context = []) { $this->log(self::LEVEL_EMERGENCY, $message, $context); } public function alert($message, $context = []) { $this->log(self::LEVEL_ALERT, $message, $context); } public function critical($message, $context = []) { $this->log(self::LEVEL_CRITICAL, $message, $context); } public function error($message, $context = []) { $this->log(self::LEVEL_ERROR, $message, $context); } public function warning($message, $context = []) { $this->log(self::LEVEL_WARNING, $message, $context); } public function notice($message, $context = []) { $this->log(self::LEVEL_NOTICE, $message, $context); } public function info($message, $context = []) { $this->log(self::LEVEL_INFO, $message, $context); } public function debug($message, $context = []) { $this->log(self::LEVEL_DEBUG, $message, $context); } /** * Основной метод для записи лога с заданным уровнем. * @param mixed $level - Уровень лога. * @param mixed $message - Сообщение для записи. * @param mixed $context - Дополнительный контекст. */ public function log($level, $message, $context = []) { $level = strtolower($level); // Проверяем, является ли уровень допустимым if (!in_array($level, $this->allowedMethods)) { return; } // Проверяем, нужно ли логировать данный уровень if (!empty($this->allowedLevels) && !in_array($level, $this->allowedLevels)) { return; } $filename = null; // Если $context не массив, преобразуем его в массив if (!is_array($context)) { $context = ['data' => $context]; } else { // Проверяем наличие 'filename' в контексте $filename = $context['filename'] ?? null; } $this->write($this->formatMessage($level, $message, $context), $filename); } // --- Вспомогательные методы --- /** * Форматирует сообщение лога. * @param string $level - Уровень лога. * @param mixed $message - Сообщение для записи. * @param array $context - Дополнительный контекст. * @return string - Отформатированное сообщение. */ private function formatMessage(string $level, $message, array $context = []): string { if (is_array($message)) { $message = json_encode($message, JSON_UNESCAPED_UNICODE); } elseif (is_object($message)) { $message = serialize($message); } // Исключаем специальные ключи из контекста для отображения $contextString = ''; if (!empty($context)) { $contextFiltered = $context; unset($contextFiltered['filename']); if (!empty($contextFiltered)) { $contextString = "\n" . print_r($contextFiltered, true); } } // Получаем информацию об источнике вызова, если это включено $source = ''; if ($this->includeSource) { $source = $this->getSourceInfo(); } $replacements = [ "{date}" => (new \DateTime())->format($this->dateFormat), "{level}" => strtoupper($level), "{message}" => $message, "{context}" => $contextString, "{source}" => $source, ]; $formattedMessage = str_replace( array_keys($replacements), array_values($replacements), $this->format ); // Добавляем цветовое выделение для консольного вывода, если включено if ($this->useColors && php_sapi_name() === 'cli') { $formattedMessage = $this->applyColors($level, $formattedMessage); } return $formattedMessage; } /** * Применяет цветовое выделение к сообщению. * @param string $level - Уровень лога. * @param string $message - Сообщение для форматирования. * @return string - Сообщение с цветовыми кодами. */ private function applyColors(string $level, string $message): string { $colors = [ self::LEVEL_EMERGENCY => "\033[1;31m", // Красный яркий self::LEVEL_ALERT => "\033[1;31m", // Красный яркий self::LEVEL_CRITICAL => "\033[0;31m", // Красный self::LEVEL_ERROR => "\033[0;31m", // Красный self::LEVEL_WARNING => "\033[0;33m", // Желтый self::LEVEL_NOTICE => "\033[0;36m", // Голубой self::LEVEL_INFO => "\033[0;32m", // Зеленый self::LEVEL_DEBUG => "\033[0;37m", // Белый ]; $color = $colors[$level] ?? "\033[0m"; $reset = "\033[0m"; return $color . $message . $reset; } /** * Возвращает информацию об источнике вызова (файл и строка). * @return string - Строка с информацией об источнике вызова. */ private function getSourceInfo(): string { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); $caller = isset($backtrace[2]) ? $backtrace[2] : $backtrace[1]; $file = $caller['file'] ?? 'unknown file'; $line = $caller['line'] ?? 'unknown line'; return "[{$file}:{$line}]"; } /** * Возвращает путь к директории для хранения логов. * @return string - Путь к директории логов. */ private function getLogDir(): string { if ($this->useScriptDirectory) { // Используем директорию скрипта $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); $callerFile = isset($backtrace[2]['file']) ? $backtrace[2]['file'] : __FILE__; return dirname($callerFile); } else { return $_SERVER["DOCUMENT_ROOT"] . "/local/log"; } } /** * Возвращает путь к файлу лога. * @param string|null $filename - Имя файла лога. * @return string - Полный путь к файлу внутри директории логов. */ private function getLogPath(?string $filename = null): string { $dir = $this->getLogDir(); if ($this->useScriptDirectory) { // Используем имя скрипта с добавлением .log $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); $callerFile = isset($backtrace[2]['file']) ? $backtrace[2]['file'] : __FILE__; $scriptFilename = basename($callerFile); $filename = $scriptFilename . '.log'; } else { $filename = $filename ?? $this->defaultFilename; } return $dir . "/" . $filename; } /** * Проверяет существование директории для логов. Если директория не существует, она создается. * @param string $dir - Директория для проверки. * @return bool - True, если директория существует или была успешно создана. */ private function checkLogDir(string $dir): bool { return is_dir($dir) || mkdir($dir, 0777, true); } /** * Записывает отформатированное сообщение в файл лога. * @param string $msg - Отформатированное сообщение для записи. * @param string|null $filename - Имя файла лога. */ private function write(string $msg, ?string $filename = null) { $dir = $this->getLogDir(); if ($this->checkLogDir($dir)) { $logPath = $this->getLogPath($filename); // Проверяем размер файла и выполняем ротацию при необходимости if (file_exists($logPath) && filesize($logPath) >= $this->maxFileSize) { $this->rotateLogFile($logPath); } file_put_contents($logPath, $msg . "\n", FILE_APPEND); } } /** * Выполняет ротацию файла лога. * @param string $filePath - Путь к файлу лога. */ private function rotateLogFile(string $filePath) { $pathInfo = pathinfo($filePath); $rotatedFile = $pathInfo['dirname'] . '/' . $pathInfo['filename'] . '_' . date('Ymd_His') . '.' . ($pathInfo['extension'] ?? 'log'); rename($filePath, $rotatedFile); } // --- Магический метод для обработки вызова недопустимых методов --- public function __call($method, $args) { throw new \BadMethodCallException("Метод {$method} не поддерживается."); } }