Magento 2. Monolog ou como escrever logs

Estudando os vários módulos do Magento 2, você notará que o registro é usado com muito menos frequência que o Magento 1. Isso se deve principalmente ao fato de o registro se tornar mais difícil. Aqui eu gostaria de me concentrar no lado técnico da questão, a saber: como registrar dados, como gravar registros em seu próprio arquivo e o que é o Monolog.

Sumário


Monolog
Recursos de aplicativos no Magento 2
Implementação
Log usando o logger padrão
Log usando um criador de logs padrão com um canal personalizado
Gravando em um arquivo personalizado usando seu próprio manipulador
Gravando em um arquivo personalizado usando o virtualType
Registro rápido de dados
Conclusão

Monolog


Vamos começar com a pergunta mais importante - O que é o Monolog e de onde ele vem.

Monolog - Esta é uma biblioteca que implementa o padrão PSR-3 para registro de dados. É o Monolog usado no Magento 2 para gravar logs.

O PSR-3 é, por sua vez, um padrão que descreve uma abordagem comum para registro de dados e recomendações para a implementação de registradores que fornecem uma interface comum.

Destaques do PSR-3
1. O criador de logs (objeto) deve implementar a interface \ Psr \ Log \ LoggerInterface.
2. Temos os seguintes níveis de erro (indicados em ordem de prioridade, de maior para menor):
EMERGÊNCIA - O sistema não pode ser utilizado.
ALERTA - A ação deve ser tomada imediatamente. Exemplo: site inteiro inativo, banco de dados indisponível etc.
CRÍTICO - Condições críticas. Exemplo: Componente do aplicativo indisponível, exceção inesperada.
ERRO - Erros de tempo de execução que não requerem ação imediata, mas normalmente devem ser monitorados.
AVISO - Ocorrências excepcionais que não são erros.Exemplo: Uso de APIs obsoletas.
AVISO - Eventos normais, mas significativos.
INFO - Eventos interessantes. Exemplo: Usuário efetua login, logs SQL.
DEBUG - Informações detalhadas sobre depuração.

3. Cada nível tem seu próprio método (depuração, informação, aviso, aviso, erro, crítico, alerta, emergência / emergência) e também deve haver um método de log, que leva o nível de erro como o primeiro parâmetro.
4. Os métodos aceitam uma string ou qualquer coisa que implemente __toString () (ou seja, você deve usar print_r ($ message, true) manualmente para matrizes ou passá-las no próximo parâmetro).
5. Todos os métodos aceitam uma matriz de contexto $ para complementar o log.
6. Pode, mas não necessariamente, ser implementada a substituição de dados do array de contexto $ na mensagem. Nesse caso, o formato {name} é recomendado, em que name -> a chave da matriz em $ context.


Monolog é muito fácil de usar. Vejamos o exemplo a seguir.

use Monolog\Logger; use Monolog\Handler\StreamHandler; use Monolog\Formatter\HtmlFormatter; //     "name" $log = new Logger('name'); //  ,      "path/to/your1.log"       "WARNING"   (notice, info  debug   ). $log->pushHandler(new StreamHandler('path/to/your1.log', Logger::WARNING)); //  ,      "path/to/your2.log"       "ALERT"   (..        alert  emergency).    ,    .         html   HtmlFormatter. $log->pushHandler(new StreamHandler('path/to/your2.log', Logger::ALERT,false) ->setFormatter(new HtmlFormatter)); //  ,       . $log->pushProcessor(function ($record) { $record['extra']['dummy'] = 'Hello world!'; return $record; }); //   $log->warning('Foo'); $log->error('Bar',['test']); $log->info('Test'); //   ,    1     INFO //your1.log // [2019-08-12 02:57:52] name.WARNING: Foo [] ['extra'=>['dummy'=>'Hello world!']] // [2019-08-12 02:57:53] name.ERROR: BAR ['test'] ['extra'=>['dummy'=>'Hello world!']] //your2.log // , .    ALERT  EMERGENCY 

Destaques do trabalho de Monolog a serem lembrados:

  • Logger é um objeto que usamos para registrar logs. O próprio criador de logs não registra, mas gerencia manipuladores. Qualquer número pode ser criado.
  • Manipulador - um objeto que processa diretamente os dados. Você pode adicionar quantos manipuladores quiser ao criador de logs. Todos eles serão chamados por sua vez, independentemente de o manipulador fornecido poder processar o erro ou não. O método isHandling determina se esse manipulador poderá manipular o erro recebido.

     public function isHandling(array $record) { return $record['level'] >= $this->level; } 

    A semelhança mais próxima do manipulador, na minha opinião, é o Observador de Eventos.
  • Processador - qualquer entidade chamada (exigível). Talvez alguns. Eles podem ser atribuídos globalmente e instalados para o manipulador. Primeiro, os processadores globais são lançados. A principal tarefa do processador é adicionar dados adicionais ao log (por exemplo, o IP do qual a conexão estava, o valor das variáveis ​​globais, informações em qual ramificação git o código está localizado e assim por diante).
  • Formatador - Converte a saída da mensagem antes de escrever. Só pode haver 1 por manipulador. É necessário alterar a formatação do corpo da mensagem, por exemplo, para converter texto em html ou json.
  • Canal - o nome do criador de logs. Será gravado ao gravar o log. Como 1 manipulador pode ser usado em 2 registradores diferentes (ele gravará os registros em 1 no mesmo arquivo), isso determinará a origem do erro.
  • Nível - nível de erro. Este parâmetro para o manipulador significa o nível mínimo de erro que ele manipulará.
  • Bolha - pop-up de mensagem. Depois que o manipulador processa a mensagem, o criador de logs passa a mensagem para o próximo manipulador. Este processo pode ser parado usando a propriedade da bolha. Se o manipulador tiver o valor dessa propriedade false (o padrão é sempre verdadeiro), depois que esse manipulador fizer seu trabalho (foi capaz de processar esse erro), outros manipuladores não serão iniciados.
  • Ordem de classificação - ordem de execução. O último manipulador adicionado é sempre iniciado primeiro. É esse recurso que permite implementar um mecanismo para desativar completamente o criador de logs (por meio de bolhas falsas). Manipuladores adicionados por meio do construtor vão na ordem especificada no construtor.

Como o PSR-3 não obriga os desenvolvedores a implementar valores de AutoCorreção no texto, o Monolog não faz isso por padrão. Se você escrever -> emerg ('test 1111 {placeholder}', ['placeholder' => 'foo']), você obterá o seguinte
[2019-08-12 02:57:52] main.EMERGENCY: test 1111 {espaço reservado} {"espaço reservado": "foo"} []

Para que a substituição funcione, é necessário conectar um processador adicional - \ Monolog \ Processor \ PsrLogMessageProcessor.
Vale dizer que o Monolog possui um grande número de formatadores, processadores e manipuladores prontos para uso. Você pode usá-los ou escrever seus próprios.

Recursos de aplicativos no Magento 2


No site oficial do Magento, você pode encontrar um exemplo geral de como usar o logger. Infelizmente, o exemplo apresentado não revela todos os detalhes e, infelizmente, não responde à pergunta "como gravar logs em seu próprio arquivo". Portanto, vamos entender tudo com mais detalhes.

Nos dias do Magento 1, provavelmente mais cedo ou mais tarde todo mundo usava o método Mage :: log, que estava disponível em todo o código e a entrada de log mais simples parecia Mage :: log ('ALARM!', Null, 'api.log'). Como resultado, registramos o seguinte formulário no arquivo var / log / api.log

 2019-08-12T01:00:27+00:00 DEBUG (7): ALARM! 

Formato padrão:% timestamp %% priorityName% (% priority%):% message%.

Vamos ver como registrar dados no caso mais simples do Magento 2. Geralmente, você usará $ this -> _ logger-> info ('ALARM!'); (se um objeto tiver essa propriedade, por exemplo, herdada).

Como resultado dessa chamada, obtemos a seguinte entrada no arquivo var / log / system.log

 [2019-08-12 02:56:43] main.INFO: ALARM! [] [] 

O formato padrão é [% datetime%]% channel%.% Level_name%:% message %% context %% extra%
Se o objeto não tiver essa propriedade (_logger ou logger), primeiro precisamos adicionar a dependência \ Psr \ Log \ LoggerInterface à sua classe e gravar o objeto resultante na propriedade $ logger (de acordo com o ponto 4.2 do PSR-2 e o exemplo apresentado no site Magento ) .
Ao contrário do Magento 1, há muito mais nuances aqui.

1. Parâmetros para o log.

Considere uma chamada geral para o método write

 $this->_logger->{level}($message, $context = []); //$this->_logger->log('{level}', $message, $context = []); 

1) Onde {level} é, de acordo com o PSR-3, 1 dos métodos reservados para registrar um certo nível de erro (depuração, informações, aviso, aviso, erro, crítico, alerta, emergência / emergência).
2) $ message - diferente do Magento 1, deve ser uma string. I.e. $ object-> getData () não funcionará aqui. A matriz de dados deve ser passada para o próximo parâmetro. Os objetos \ Exception são uma exceção, pois a implementação do \ Magento \ Framework \ Logger \ Monolog os processa separadamente e rola automaticamente -> getMessage () ainda mais como $ message se o objeto \ Exception foi passado como uma mensagem.
3) $ context é um parâmetro opcional, uma matriz.

2. A propriedade $ this -> _ logger não está disponível em todas as classes.

Presente em: Bloco, Auxiliar, Modelo, Coleção, etc.
Não disponível em: ResourceModel, Controller, Comand, Setup, etc.

Saiba mais sobre o ResourceModel e o Collection.
ResourceModel possui a propriedade _logger, mas não é preenchida no construtor. Ele é preenchido apenas usando o método getLogger privado em \ Magento \ Framework \ Model \ ResourceModel \ AbstractResource. O método é chamado apenas em caso de erro ao gravar no banco de dados (no bloco catch) dentro do método commit (). Até lá, o modelo de recursos não terá um criador de logs.

 public function commit() { $this->getConnection()->commit(); if ($this->getConnection()->getTransactionLevel() === 0) { $callbacks = CallbackPool::get(spl_object_hash($this->getConnection())); try { foreach ($callbacks as $callback) { call_user_func($callback); } } catch (\Exception $e) { $this->getLogger()->critical($e); } } return $this; } … private function getLogger() { if (null === $this->_logger) { $this->_logger = ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class); } return $this->_logger; } 

A coleção possui um logger desde o início. É atribuído no construtor \ Magento \ Framework \ Data \ Collection \ AbstractDb e posteriormente herdado.

É impossível não dizer, mas nos controladores existe uma maneira de obter o logger usando o ObjectManager (através da propriedade $ this -> _ objectManager). Mas essa não é, obviamente, a maneira mais correta.

3. O criador de logs padrão e a lista de manipuladores.

No di.xml global (app / etc / di.xml), você pode descobrir que \ Psr \ Log \ LoggerInterface é implementado pela classe \ Magento \ Framework \ Logger \ Monolog, que por sua vez herda de \ Monolog \ Logger. O nome do criador de logs é principal. Vários manipuladores também são definidos lá.

 <preference for="Psr\Log\LoggerInterface" type="Magento\Framework\Logger\Monolog" /> ... <type name="Magento\Framework\Logger\Monolog"> <arguments> <argument name="name" xsi:type="string">main</argument> <argument name="handlers" xsi:type="array"> <item name="system" xsi:type="object">Magento\Framework\Logger\Handler\System</item> <item name="debug" xsi:type="object">Magento\Framework\Logger\Handler\Debug</item> <item name="syslog" xsi:type="object">Magento\Framework\Logger\Handler\Syslog</item> </argument> </arguments> </type> ... 

Algumas classes diferem das listadas acima (como são redefinidas no módulo Magento \ Developer):

1) Magento \ Framework \ Logger \ Handler \ System ( ouve INFO)
2) Magento \ Desenvolvedor \ Modelo \ Logger \ Handler \ Debug ( escuta DEBUG )
3) Magento \ Desenvolvedor \ Modelo \ Logger \ Handler \ Syslog ( escuta DEBUG )

Nas classes especificadas (Debug e Syslog) , a capacidade de desativar o log (dev / debug / debug_logging e dev / syslog / syslog_logging, respectivamente) é adicionada.

Observe que não há manipulador de exceções na lista de manipuladores que grava em exception.log. É chamado no manipulador do sistema.

Magento \ Framework \ Logger \ Manipulador \ Sistema
 ... public function write(array $record) { if (isset($record['context']['exception'])) { $this->exceptionHandler->handle($record); return; } $record['formatted'] = $this->getFormatter()->format($record); parent::write($record); } ... 


O Magento 2 a 2.2 teve um problema com o logger incapaz de pular para outro manipulador após o primeiro encontrado. Esse problema foi causado pelo fato de o Monolog esperar que todos os manipuladores cheguem a ele em uma matriz com chaves digitais e com chaves alfabéticas (['system' =>, 'debug' =>, ...]). Os desenvolvedores do Magento mais tarde corrigiram a situação - eles convertem o hash em uma matriz regular com chaves digitais antes de passá-lo para o Monolog. Agora o Monolog também mudou o algoritmo de enumeração do manipulador e usa o método next ().
4. Apresentando seu manipulador na lista dos existentes.

Chegamos ao ponto mais interessante, que estraga um pouco a impressão de implementação no Magento 2. Você não pode adicionar um manipulador personalizado à lista dos existentes usando di.xml sem ... "movimentos corporais adicionais". Isso ocorre devido ao princípio das configurações de mesclagem.

Existem vários escopos de configuração :

1) Inicial (app / etc / di.xml)
2) Global ({moduleDir} /etc/di.xml)
3) Específico da área ({moduleDir} / etc / {area} /di.xml, por exemplo, Frontend / adminhtml / crontab / webapi_soap / webapi_rest, etc.)

Dentro do nível 1, as configurações são mescladas, mas o próximo nível as redefine na mesclagem (se também forem declaradas lá). Isso torna impossível adicionar manipuladores em seus módulos à lista existente, porque ela é declarada no escopo inicial.

Talvez no futuro veremos uma implementação na qual a adição de manipuladores será movida do escopo inicial para outro módulo, sendo transferida para o escopo global.

Implementação


Vejamos as principais maneiras de registrar logs, que podem ser úteis para nós na implementação de tarefas.

1. Log usando o registrador padrão


Esse método nos permite gravar logs facilmente em um dos logs padrão (debug.log, system.log ou exception.log).

 class RandomClass { private $logger; public function __construct(\Psr\Log\LoggerInterface $logger) { $this->logger = $logger; } public function foo() { $this->logger->info('Something went wrong'); //[...some date...] main.INFO: Something went wrong [] [] } } 

Tudo se torna ainda mais simples se já houver uma dependência herdada do criador de logs em nossa classe.

 $this->_logger->info('Something went wrong'); //    ->debug,   ,      debug.log ... 

2. Log usando um registrador padrão com um canal personalizado


Esse método difere do anterior, pois um clone do criador de logs é criado e outro canal (nome) é atribuído a ele. O que simplificará a pesquisa dentro do arquivo de log.

 class RandomClass { private $logger; public function __construct(\Psr\Log\LoggerInterface $logger) { $this->logger = $logger->withName('api'); //    } public function foo() { $this->logger->info('Something went wrong'); //[...some date...] api.INFO: Something went wrong [] [] } } 


Para pesquisar os logs necessários, basta agora usar a pesquisa por "api" (o criador de logs padrão no Magento 2 é chamado main) nos arquivos system.log, debug.log, exception.log existentes. Pode usar
 grep -rn 'api' var/log/system.log 


3. Gravando em um arquivo personalizado usando seu próprio manipulador


Vamos criar um manipulador simples que registre todos os erros do nível Crítico e superior em um arquivo separado var / log / critical.log. Adicione a capacidade de bloquear todos os outros manipuladores para um determinado nível de erro e superior. Isso evitará a duplicação de dados nos arquivos debug.log e system.log.

 <?php namespace Oxis\Log\Logger\Handler; use Magento\Framework\Filesystem\DriverInterface; use Magento\Framework\Logger\Handler\Base; use Monolog\Logger; class Test extends Base { protected $fileName = 'var/log/critical.log'; protected $loggerType = Logger::CRITICAL; public function __construct(DriverInterface $filesystem) { parent::__construct($filesystem,null,null); $this->bubble = false; //      setBubble() } } 

No Magento 2 2.2+ no construtor \ Magento \ Framework \ Logger \ Handler \ Base, a maneira de processar o caminho para o arquivo de log mudou
 // BP . $this->fileName // BP. DIRECTORY_SEPARATOR . $this->fileName 

Portanto, em manipuladores mais antigos, você ainda pode encontrar / no início de $ fileName.


Por exemplo, vale a pena dar uma pequena explicação. Como o Base não permite definir a propriedade da bolha por meio de parâmetros do construtor, teríamos que repetir parte do código do construtor Base para passar o parâmetro de entrada corretamente para o pai da classe Base (que, a propósito, tem um parâmetro de entrada para definir essa propriedade) ou usar tal abordagem. Eu escolhi a segunda opção.

 use Oxis\Log\Logger\Handler\Test; use Psr\Log\LoggerInterface; class RandomClass { private $logger; public function __construct( LoggerInterface $logger, Test $handler ) { $logger->pushHandler($handler); //  setHandlers([$handler]),        . $this->logger = $logger; } public function foo() { $this->logger->critical('Something went wrong'); //      critical.log //[...some date...] main.CRITICAL: Something went wrong [] [] } } 

Esse método de adicionar um manipulador não é ideal, mas permite que você se afaste do escopo de configuração do problema, o que exigirá que duplicemos todos os registradores em nosso di.xml. Se o objetivo é substituir todos os criadores de logs pelos seus, é muito melhor usar a abordagem virtualType, que consideraremos mais adiante.

4. Gravando em um arquivo personalizado usando o virtualType


Essa abordagem nos permite forçar a classe de que precisamos gravar logs no arquivo de log especificado para isso usando di.xml. Você pode encontrar uma abordagem semelhante nos módulos Magento \ Payment e Magento \ Shipping. Chamo a atenção para o fato de que essa abordagem funciona a partir do Magento 2 2.2 e superior.
No Magento 2 2.2+, um novo parâmetro foi adicionado ao construtor \ Magento \ Framework \ Logger \ Handler \ Base, que permite criar um manipulador virtual e especificar através de di.xml o caminho relativo ao arquivo para gravar o log. Anteriormente, era necessário especificar o caminho completo através de $ filePath ou criar um novo manipulador e gravar o caminho relativo na propriedade $ fileName do arquivo protegido.

No di.xml do nosso módulo, adicione o seguinte
  <!--      ,     --> <virtualType name="ApiHandler" type="Magento\Framework\Logger\Handler\Base"> <arguments> <argument name="fileName" xsi:type="string">var/log/api.log</argument> </arguments> </virtualType> <!--  ,     --> <virtualType name="ApiLogger" type="Magento\Framework\Logger\Monolog"> <arguments> <argument name="name" xsi:type="string">api</argument> <argument name="handlers" xsi:type="array"> <item name="default" xsi:type="object">ApiHandler</item> </argument> </arguments> </virtualType> <!--       --> <type name="Oxis\Log\Model\A"> <arguments> <argument name="logger" xsi:type="object">ApiLogger</argument> </arguments> </type> 

Adicione uma classe de logger ao Oxis \ Log \ Model \ A.
 namespace Oxis\Log\Model; class A { private $logger; public function __construct(\Psr\Log\LoggerInterface $logger) { $this->logger = $logger; } public function foo() { $this->logger->info('Something went wrong'); } } 

Agora, absolutamente todos os logs que serão gravados em nossa classe serão processados ​​por nossa versão do logger, que gravará logs usando nosso manipulador no arquivo var / log / api.log.

4.1 Se a classe receber o criador de logs através do objeto de contexto $, e não através de seu construtor.
Isso inclui \ Magento \ Catalog \ Model \ Product, cujas dependências não possuem \ Psr \ Log \ LoggerInterface, mas existe \ Magento \ Framework \ Model \ Context através do qual o logger é definido como a propriedade da classe. Nesse caso, precisamos complicar um pouco a opção acima e substituir o criador de logs localizado no objeto $ context. E para que isso não afete todo o Magento, substituiremos $ context apenas para nossa classe por virtualType.
 <virtualType name="ApiHandler" type="Magento\Framework\Logger\Handler\Base"> <arguments> <argument name="fileName" xsi:type="string">var/log/api.log</argument> </arguments> </virtualType> <virtualType name="ApiLogger" type="Magento\Framework\Logger\Monolog"> <arguments> <argument name="name" xsi:type="string">api</argument> <argument name="handlers" xsi:type="array"> <item name="default" xsi:type="object">ApiHandler</item> </argument> </arguments> </virtualType> <!--       logger--> <virtualType name="ApiLogContainingContext" type="Magento\Framework\Model\Context"> <arguments> <argument name="logger" xsi:type="object">ApiLogger</argument> </arguments> </virtualType> <!--    --> <type name="Oxis\Log\Model\A"> <arguments> <argument name="context" xsi:type="object">ApiLogContainingContext</argument> </arguments> </type> 


5. Registro rápido de dados


Há momentos em que precisamos adicionar rapidamente o log. Na maioria das vezes, isso pode ser necessário no servidor de produção ou para testes rápidos.
 ... $log = new \Monolog\Logger('custom', [new \Monolog\Handler\StreamHandler(BP.'/var/log/custom.log')]); $log->error('test'); ... 

Prós desta abordagem: escreve uma data, existe um contexto (array), adiciona automaticamente \ n ao final

No exemplo acima, \ Monolog \ Logger é aplicado especificamente, e não \ Magento \ Framework \ Logger \ Monolog que o estende. O fato é que, com esse uso, não há diferença, mas escrever menos (e lembrar mais fácil).

\ Monolog \ Handler \ StreamHandler, por sua vez, é usado em vez de \ Magento \ Framework \ Logger \ Handler \ Base, pois o uso do Base como um snippet não é muito conveniente devido a dependências adicionais de classes de terceiros.

Outra abordagem que não se pode dizer é sobre o bom e velho file_put_contents.

 ... file_put_contents(BP.'/var/log/custom.log', 'test',FILE_APPEND); ... 

Vantagens dessa abordagem: escreva com relativa rapidez e não precise memorizar as aulas.

Nos dois casos, o papel principal é desempenhado pela BP constante. Ela sempre aponta para a pasta com a magenta (1 nível acima do pub), o que é conveniente e sempre nos ajuda a escrever registros no lugar certo.

Conclusão


Espero que as informações acima tenham sido ou sejam úteis para você.

Source: https://habr.com/ru/post/pt450636/


All Articles