Как настроить универсальное логирование с использованием монолога?
Я пишу консольное приложение с компонентами Symfony2, и я хочу добавить отдельные каналы регистрации для моих служб, моих команд и так далее. Проблема: для создания нового канала требуется создать новый экземпляр Monolog, и я действительно не знаю, как справиться с этим универсальным способом, и без необходимости передавать обработчик потока, канал и соответствующий код для связывания одного и другого внутри всех служб.
Я сделал трюк, используя debug_backtrace()
:
public function log($level, $message, array $context = array ())
{
$trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1);
$caller = $trace[0]['class'] !== __CLASS__ ? $trace[0]['class'] : $trace[1]['class'];
if (!array_key_exists($caller, $this->loggers))
{
$monolog = new Monolog($caller);
$monolog->pushHandler($this->stream);
$this->loggers[$caller] = $monolog;
}
$this->loggers[$caller]->log($level, $message, $context);
}
Независимо от того, откуда я вызываю свой регистратор, он создает канал для каждого класса, который его вызвал. Выглядит круто, но как только лесоруба называют тоннами времени, это убивает производительность.
Итак, вот мой вопрос:
Знаете ли вы лучший универсальный способ создать один отдельный канал монолога для каждого класса, который имеет свойство logger?
Приведенный выше код упакован для тестирование:
композитор.json
{
"require" : {
"monolog/monolog": "~1.11.0"
}
}
тест.php
<?php
require('vendor/autoload.php');
use MonologLogger;
use MonologHandlerStreamHandler;
class Test
{
public function __construct($logger)
{
$logger->info("test!");
}
}
class Hello
{
public function __construct($logger)
{
$logger->log(MonologLogger::ALERT, "hello!");
}
}
class LeveragedLogger implements PsrLogLoggerInterface
{
protected $loggers;
protected $stream;
public function __construct($file, $logLevel)
{
$this->loggers = array ();
$this->stream = new StreamHandler($file, $logLevel);
}
public function alert($message, array $context = array ())
{
$this->log(Logger::ALERT, $message, $context);
}
public function critical($message, array $context = array ())
{
$this->log(Logger::CRITICAL, $message, $context);
}
public function debug($message, array $context = array ())
{
$this->log(Logger::DEBUG, $message, $context);
}
public function emergency($message, array $context = array ())
{
$this->log(Logger::EMERGENCY, $message, $context);
}
public function error($message, array $context = array ())
{
$this->log(Logger::ERROR, $message, $context);
}
public function info($message, array $context = array ())
{
$this->log(Logger::INFO, $message, $context);
}
public function log($level, $message, array $context = array ())
{
$trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1);
$caller = $trace[0]['class'] !== __CLASS__ ? $trace[0]['class'] : $trace[1]['class'];
if (!array_key_exists($caller, $this->loggers))
{
$monolog = new Logger($caller);
$monolog->pushHandler($this->stream);
$this->loggers[$caller] = $monolog;
}
$this->loggers[$caller]->log($level, $message, $context);
}
public function notice($message, array $context = array ())
{
$this->log(Logger::NOTICE, $message, $context);
}
public function warning($message, array $context = array ())
{
$this->log(Logger::WARNING, $message, $context);
}
}
$logger = new LeveragedLogger('php://stdout', Logger::DEBUG);
new Test($logger);
new Hello($logger);
использование
ninsuo:test3 alain$ php test.php
[2014-10-21 08:59:04] Test.INFO: test! [] []
[2014-10-21 08:59:04] Hello.ALERT: hello! [] []
3 ответа:
Что бы вы подумали о принятии решения, какой регистратор следует использовать непосредственно перед созданием потребителей? Это можно было легко сделать с помощью какого-нибудь DIC или, возможно, фабрики.
<?php require('vendor/autoload.php'); use Monolog\Logger; use Monolog\Handler\StreamHandler; use Psr\Log\LoggerInterface; use Monolog\Handler\HandlerInterface; class Test { public function __construct(LoggerInterface $logger) { $logger->info("test!"); } } class Hello { public function __construct(LoggerInterface $logger) { $logger->log(Monolog\Logger::ALERT, "hello!"); } } class LeveragedLoggerFactory { protected $loggers; protected $stream; public function __construct(HandlerInterface $streamHandler) { $this->loggers = array(); $this->stream = $streamHandler; } public function factory($caller) { if (!array_key_exists($caller, $this->loggers)) { $logger = new Logger($caller); $logger->pushHandler($this->stream); $this->loggers[$caller] = $logger; } return $this->loggers[$caller]; } } $loggerFactory = new LeveragedLoggerFactory(new StreamHandler('php://stdout', Logger::DEBUG)); new Test($loggerFactory->factory(Test::class)); new Hello($loggerFactory->factory(Hello::class));
Я, наконец, создал класс
MonologContainer
, который расширяет стандартный контейнер Symfony2 и вводитLogger
вLoggerAware
сервисы. Перегружая методget()
контейнера службы, я могу получить идентификатор службы и использовать его в качестве канала для регистратора.<?php namespace Fuz\Framework\Core; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Monolog\Handler\HandlerInterface; use Monolog\Logger; use Psr\Log\LoggerAwareInterface; class MonologContainer extends ContainerBuilder { protected $loggers = array (); protected $handlers = array (); protected $processors = array (); public function __construct(ParameterBagInterface $parameterBag = null) { parent::__construct($parameterBag); } public function pushHandler(HandlerInterface $handler) { foreach (array_keys($this->loggers) as $key) { $this->loggers[$key]->pushHandler($handler); } array_unshift($this->handlers, $handler); return $this; } public function popHandler() { if (count($this->handlers) > 0) { foreach (array_keys($this->loggers) as $key) { $this->loggers[$key]->popHandler(); } array_shift($this->handlers); } return $this; } public function pushProcessor($callback) { foreach (array_keys($this->loggers) as $key) { $this->loggers[$key]->pushProcessor($callback); } array_unshift($this->processors, $callback); return $this; } public function popProcessor() { if (count($this->processors) > 0) { foreach (array_keys($this->loggers) as $key) { $this->loggers[$key]->popProcessor(); } array_shift($this->processors); } return $this; } public function getHandlers() { return $this->handlers; } public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { $service = parent::get($id, $invalidBehavior); return $this->setLogger($id, $service); } public function setLogger($id, $service) { if ($service instanceof LoggerAwareInterface) { if (!array_key_exists($id, $this->loggers)) { $this->loggers[$id] = new Logger($id, $this->handlers, $this->processors); } $service->setLogger($this->loggers[$id]); } return $service; } }
Пример использования:
тест.php
#!/usr/bin/env php <?php use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Monolog\Logger; use Monolog\Handler\StreamHandler; use Fuz\Framework\Core\MonologContainer; if (!include __DIR__ . '/vendor/autoload.php') { die('You must set up the project dependencies.'); } $container = new MonologContainer(); $loader = new YamlFileLoader($container, new FileLocator(__DIR__)); $loader->load('services.yml'); $handler = new StreamHandler(__DIR__ ."/test.log", Logger::WARNING); $container->pushHandler($handler); $container->get('my.service')->hello();
услуги.yml
parameters: my.service.class: Fuz\Runner\MyService services: my.service: class: %my.service.class%
Моя служба.php
<?php namespace Fuz\Runner; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerInterface; class MyService implements LoggerAwareInterface { protected $logger; public function setLogger(LoggerInterface $logger) { $this->logger = $logger; } public function hello() { $this->logger->alert("Hello, world!"); } }
демо
ninsuo:runner alain$ php test.php ninsuo:runner alain$ cat test.log [2014-11-06 08:18:55] my.service.ALERT: Hello, world! [] []
Вы можете попробовать это
<?php use Monolog\Logger; use Monolog\Handler\StreamHandler; use Monolog\Handler\FirePHPHandler; class Loggr{ private static $_logger; public $_instance; public $_channel; private function __construct(){ if(!isset(self::$_logger)) self::$_logger = new Logger('Application Log'); } // Create the logger public function logError($error){ self::$_logger->pushHandler(new StreamHandler(LOG_PATH . 'application.'. $this->_channel . '.log', Logger::ERROR)); self::$_logger->addError($error); } public function logInfo($info){ self::$_logger->pushHandler(new StreamHandler(LOG_PATH . 'application.'. $this->_channel . '.log', Logger::INFO)); self::$_logger->addInfo($info); } public static function getInstance($channel) { $_instance = new Loggr(); $_instance->_channel = strtolower($channel); return $_instance; } }
И может потребляться как
class LeadReport extends Controller{ public function __construct(){ $this->logger = Loggr::getInstance('cron'); $this->logger->logError('Error generating leads'); } }