Обёртка над 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;Инициализирует класс логгера:
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 к ним не применяется.
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 проспамит этим сообщением по всем объявленным уровням скоупа.
Вызов 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']);Скоупы с отложенной инициализацией и параметрами по умолчанию.
Вызов ничем не отличается от предварительно инициализированного логгера:
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.
Метод для описания конкретного уровня логгирования. Рекомендуется использовать в PHP8+ (именованные аргументы):
AppLogger::addScopeLevel(?string $scope = null, ?string $target = '', int $log_level = Logger::DEBUG,
bool $enable = true, bool $bubble = false, $handler = null):voidAppLogger::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);Дефолтное определение хэндлера, выводящего данные в 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;
}
);Arris\AppLogger\LineFormatterColored — форматтер для консоли (ANSI-цвета). Парсит в сообщении:
<br>→ перевод строки<hr [color='...'] [width='N']>→ горизонтальная линия из-<font color='blue'>text</font>→ цветной текст (color— имя из палитрыFOREGROUND_COLORS/BACKGROUND_COLORS)<strong>text</strong>→ жирный белый текст- неизвестный цвет
<font>→ фолбэк на белый
Указываем наименьший используемый уровень логгирования (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 сбрасывается между
тестами через рефлексию, логи пишутся во временные каталоги.
- 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
- В
addScope()коллбэк кастомного хэндлера вызывается без аргументов (call_user_func_array($handler, [])), в отличие отaddScopeLevel(), где передаётся$log_level. Чтобы варьировать хэндлер по уровню внутриaddScope()— используйте отдельные кортежи сhandlerна каждый уровень.