karelwintersky / arris.logger
Arris µFramework - AppLogger class
Requires
- php: 8.*
- psr/log: *
Requires (Dev)
- phpunit/phpunit: ^10
Suggests
- karelwintersky/arris: Arris µFramework: AppRouter class
- karelwintersky/arris.helpers: Arris µFramework: helpers
- monolog/monolog: ^2.9.3
Provides
None
Conflicts
None
Replaces
None
README
Обёртка над Monolog для Arris µFramework.
Точка входа — статический класс Arris\AppLogger. Внутри используется вендоренная копия
Monolog 3.x в неймспейсе Arris\AppLogger\Monolog — внешняя зависимость monolog/monolog
не требуется.
use Arris\AppLogger; use Arris\AppLogger\Monolog\Logger; // уровни DEBUG/INFO/NOTICE/... и инстанс логгера use Arris\AppLogger\Monolog\Handler\StreamHandler;
Init
Инициализирует класс логгера:
AppLogger::init($application, $instance, $options = []):void
$application- Имя приложения$instance- код инстанса приложения (рекомендуется генерировать его при старте приложения:bin2hex(random_bytes(8))). Логгеры ключуются внутренне поapplication . instance . scope, поэтому логи параллельных инстансов не смешиваются.$optionsопции приложения:bubbling- [FALSE] - всплывает ли логгируемое сообщение на следующий хэндлер в стеке?default_log_level-[Logger::DEBUG]- уровень логгирования по умолчаниюdefault_logfile_path-['']- путь к файлам логов по умолчанию (префикс относительных имён файлов)default_logfile_prefix-['']- префикс файла лога по умолчаниюdefault_log_file-['_.log']- имя файла лога по умолчанию, применяется если для имени файла передан NULLdefault_handler-[StreamHandler::class]- хэндлер по умолчанию для уровней скоуповadd_scope_to_log- [FALSE] - добавлять ли имя скоупа к имени логгера (каналу)?deferred_scope_creation- [TRUE] - разрешать ли отложенную инициализацию скоуповdeferred_scope_separate_files- [TRUE] - использовать ли отдельные файлы для deferred-скоупов (на основе имени скоупа)
NB: абсолютные пути (начинающиеся с / или [A-Z]:\) и потоки php://... используются как есть — префикс default_logfile_path к ним не применяется.
Add Scope (несколько уровней)
AppLogger::addScope($scope = null, $scope_levels = [], $scope_logging_enabled = true, $is_deferred_scope = false):void
Добавляет скоуп (логгер) с параметрами:
$scope- имя скоупа$scope_levels- массив кортежей с опциями уровней логгирования$scope_logging_enabled- включено ли логгирование для этого скоупа. Глобальная настройка: еслиfalse— для всех уровней хэндлер ставитсяNullHandler, и никакие опции из$scope_levelsего не включат.$is_deferred_scope- служебный аргумент, его не следует указывать напрямую (задаёт создание логгера как deferred)
Если в $scope_levels передан пустой массив — ставятся опции по умолчанию (DEFAULT_SCOPE_OPTIONS, 8 уровней), а скоуп создаётся как deferred.
Повторное объявление уже существующего скоупа заменяет его хэндлеры (не накапливает дубли).
Пример:
AppLogger::addScope('mysql', [ [ '__mysql.100-debug.log', Logger::DEBUG, [ 'enable' => true ] ], [ '__mysql.250-notice.log', Logger::NOTICE, [ 'enable' => true ] ], [ '__mysql.300-warning.log', Logger::WARNING, [ 'enable' => true ] ], [ '__mysql.400-error.log', Logger::ERROR, [ 'enable' => true ] ], ], getenv('IS_MYSQL_LOGGER_ENABLED'));
Элементы кортежа уровня:
filename- имя файла (при отсутствии будет применено имя по умолчанию из глобальных опций). Относительные имена получают префиксdefault_logfile_path+default_logfile_prefix.logging_level- уровень логгирования (числа или константы\Arris\AppLogger\Monolog\Logger::DEBUGи т.д.)
Опции уровня (третий элемент кортежа — ассоциативный массив):
enable- [TRUE], разрешён ли этот уровень. Применяется тот же механизм, что и для глобальной опции$scope_logging_enabledскоупа (приfalse—NullHandler);bubbling- [FALSE], всплывает ли сообщение на следующий хэндлер;handler- [NULL] хэндлер: имя класса, реализующегоArris\AppLogger\Monolog\Handler\HandlerInterface, коллбэк, возвращающий хэндлер, либо NULL →StreamHandler.
NB: Следует отметить, что если используется уровень, не объявленный в скоупе, например:
AppLogger::scope('mysql')->emergency('MYSQL EMERGENCY');
Monolog проспамит этим сообщением по всем объявленным уровням скоупа.
Scope
Вызов AppLogger::scope($scope_name) возвращает инстанс \Arris\AppLogger\Monolog\Logger, к которому можно применить штатные методы логгирования:
debug, notice, warn, error, emergency и так далее
Пример:
AppLogger::scope('mysql')->debug("mysql::Debug", [ ['x'], ['y']]); AppLogger::scope('mysql')->notice('mysql::Notice', ['x', 'y']);
Deferred Scope
Скоупы с отложенной инициализацией и параметрами по умолчанию.
Вызов ничем не отличается от предварительно инициализированного логгера:
AppLogger::scope('usage')->emergency('EMERGENCY USAGE');
Будет создан скоуп usage со всеми уровнями логгирования и параметрами по умолчанию (но реальный вызов логгера произойдёт только для уровня emergency).
При deferred_scope_separate_files = true (по умолчанию) файлы deferred-скоупа получают префикс имени скоупа: usage.600-emergency.log и т.п. Иначе — общие имена 600-emergency.log без префикса.
NB: Если при инициализации обычного скоупа методом addScope() передан пустой массив опций логгеров — будет применён механизм инициализации deferred-скоупа.
Если отложенное создание запрещено (deferred_scope_creation = false), вызов scope() для необъявленного скоупа бросает \RuntimeException.
addScopeLevel()
Метод для описания конкретного уровня логгирования. Рекомендуется использовать в PHP8+ (именованные аргументы):
AppLogger::addScopeLevel(?string $scope = null, ?string $target = '', int $log_level = Logger::DEBUG, bool $enable = true, bool $bubble = false, $handler = null):void
"Обычное" логгирование в файл
AppLogger::addScopeLevel('xxx', 'info.log', Logger::INFO); // Handler не указан → StreamHandler AppLogger::scope('xxx')->info('Message XXX');
Передача хэндлера коллбэком
Коллбэку передаётся $log_level — можно строить хэндлер по уровню:
AppLogger::addScopeLevel('syslog', 'syslog', Logger::DEBUG, handler: function ($log_level) { return new SyslogHandler(AppLogger::$application, LOG_USER, $log_level, false); }); AppLogger::addScopeLevel('syslog', 'syslog', Logger::INFO, handler: function ($log_level) { return new SyslogHandler(AppLogger::$application, LOG_USER, $log_level, false); }); AppLogger::scope('syslog')->debug('Debug message from AppLogger'); AppLogger::scope('syslog')->info('Info message from AppLogger');
Так задаётся кастомный хэндлер через коллбэк с особыми параметрами.
Передача хэндлера строкой
Имя класса, реализующего HandlerInterface. Конструктору передаются level: и bubble:.
AppLogger::addScopeLevel('syslog', 'syslog', Logger::INFO, handler: SyslogHandler::class);
Custom handler — хэндлер, отличный от стандартного StreamHandler
Дефолтное определение хэндлера, выводящего данные в stdout:
AppLogger::addScope('console', [ [ 'php://stdout', Logger::INFO, [ 'handler' => StreamHandler::class ]] ], $options['verbose']);
Добавляем кастомный форматтер и хэндлер логгирования:
AppLogger::addScope('console', [ [ 'php://stdout', Logger::INFO, [ 'handler' => static function() { $formatter = new \Arris\AppLogger\LineFormatterColored("[%datetime%]: %message%\n", "Y-m-d H:i:s", false, true); $handler = new StreamHandler('php://stdout', Logger::INFO); $handler->setFormatter($formatter); return $handler; } ]], $options['verbose']);
Смотри: https://stackoverflow.com/questions/70875746/laravel-monolog-lineformatter-datetime-pattern
или, для PHP8+:
AppLogger::addScopeLevel( scope: 'console', target: 'php://stdout', log_level: Logger::INFO, enable: $options['verbose'], handler: static function($log_level) { $formatter = new \Arris\AppLogger\LineFormatterColored("[%datetime%]: %message%\n", "Y-m-d H:i:s", false, true); $handler = new \Arris\AppLogger\Monolog\Handler\StreamHandler('php://stdout', $log_level); $handler->setFormatter($formatter); return $handler; } );
LineFormatterColored
Arris\AppLogger\LineFormatterColored — форматтер для консоли (ANSI-цвета). Парсит в сообщении:
<br>→ перевод строки<hr [color='...'] [width='N']>→ горизонтальная линия из-<font color='blue'>text</font>→ цветной текст (color— имя из палитрыFOREGROUND_COLORS/BACKGROUND_COLORS)<strong>text</strong>→ жирный белый текст- неизвестный цвет
<font>→ фолбэк на белый
Hints
Один файл для нескольких уровней логгирования
Указываем наименьший используемый уровень логгирования (Logger::NOTICE):
AppLogger::addScope('log.selectel', [ [ '_selectel_upload.log', Logger::NOTICE ] ]);
Теперь оба вызова запишут в файл по строчке:
AppLogger::scope('log.selectel')->error('Error'); AppLogger::scope('log.selectel')->notice('Notice');
Тесты
composer install
vendor/bin/phpunit # или: make test
Статус: 23 теста, 37 assertions — все проходят (PHPUnit 10.5.64, PHP 8.2).
Набор tests/: AppLoggerTest (17 тестов: API, deferred-скоупы, пути, хэндлеры, конфиг)
и LineFormatterColoredTest (6 тестов). Статическое состояние AppLogger сбрасывается между
тестами через рефлексию, логи пишутся во временные каталоги.
Changelog
- 2.2.0 — makefile (
make test) - 2.1.13 —
.phpunit.result.cacheв.gitignore - 2.1.12 —
<hr>без атрибутов больше не генерируетUndefined array key - 2.1.11 — добавлен PHPUnit-набор тестов (23 теста / 37 assertions)
- 2.1.10 — конфиг отключённого уровня хранит
enable = false(исправлено изменение неверного массива) - 2.1.9 — повторное объявление скоупа заменяет хэндлеры вместо накопления дублей
- 2.1.8 — строковому хэндлеру в
addScope()передаётся объявленный уровень (level:) - 2.1.7 —
scope()на необъявленном скоупе бросаетRuntimeException(приdeferred_scope_creation = false) вместоTypeError - 2.1.6 — опция
default_handlerвinit()теперь читается (раньше читалась недокументированнаяhandler) - 2.1.5 — абсолютные пути и
php://-потоки не получают префиксdefault_logfile_path - 2.1.4 — неизвестный цвет
<font>фолбэчится в белый (исправлена опечатка'white ') - 2.1.3 — deferred-скоупы пишут в файлы с префиксом имени скоупа (работает
deferred_scope_separate_files) - 2.1.2 — коллбэку хэндлера в
addScopeLevel()передаётся$log_level
TODO
- В
addScope()коллбэк кастомного хэндлера вызывается без аргументов (call_user_func_array($handler, [])), в отличие отaddScopeLevel(), где передаётся$log_level. Чтобы варьировать хэндлер по уровню внутриaddScope()— используйте отдельные кортежи сhandlerна каждый уровень.