2014-10-21 2 views
2

Я пишу консольное приложение с компонентами 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! [] [] 

ответ

0

я, наконец, создал MonologContainer класс, который расширяет стандартный контейнер Symfony2, и впрыскивает Logger на LoggerAware услуги. Перегружая метод контейнера обслуживания, я могу получить идентификатор службы и использовать его в качестве канала для регистратора.

<?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!"); 
    } 

} 

Demo

ninsuo:runner alain$ php test.php 
ninsuo:runner alain$ cat test.log 
[2014-11-06 08:18:55] my.service.ALERT: Hello, world! [] [] 
2

Что вы думаете о принятии решения, которое регистратор должен использоваться непосредственно перед созданы потребители? Это может быть легко достигнуто с помощью какого-либо 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)); 
+0

Спасибо за ваш идея. Я также отправлю ответ (я выбрал переопределение службы Container, он автоматически вводит регистратор в сервисы LoggerAware), но я все равно награждаю вас щедростью. –

0

Вы можете попробовать это

<?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'); 

    } 
} 
Смежные вопросы