Я пишу консольное приложение с компонентами 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);
}
Независимо от того, откуда я вызываю мой регистратор, он создает канал для каждого класса, который его вызвал. Выглядит круто, но как только регистратор называется тратой времени, это убивает производительность.
Вот мой вопрос:
Знаете ли вы лучший общий способ создания одного отдельного монологического канала для класса, который имеет свойство логгера?
Приведенный выше код упакован для тестирования:
composer.json
{
"require" : {
"monolog/monolog": "~1.11.0"}
}
test.php
<?php
require('vendor/autoload.php');
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
class Test
{
public function __construct($logger)
{
$logger->info("test!");
}
}
class Hello
{
public function __construct($logger)
{
$logger->log(Monolog\Logger::ALERT, "hello!");
}
}
class LeveragedLogger implements \Psr\Log\LoggerInterface
{
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! [] []
Что бы вы подумали о принятии решения, какой регистратор следует использовать непосредственно перед созданием потребителей? Это может быть легко достигнуто с помощью какого-то 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;
}
}
Пример использования:
test.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();
services.yml
parameters:
my.service.class: Fuz\Runner\MyService
services:
my.service:
class: %my.service.class%
MyService.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');
}
}