Skip to content

Repository files navigation

Arris AppLogger

Обёртка над 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'] - имя файла лога по умолчанию, применяется если для имени файла передан NULL
    • default_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 скоупа (при falseNullHandler);
  • 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.7scope() на необъявленном скоупе бросает 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 на каждый уровень.

About

Arris AppLogger class

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages