Magento 2. Monolog o cómo escribir registros

Al estudiar los diversos módulos para Magento 2, notará que el registro se usa con mucha menos frecuencia que Magento 1. Esto se debe en gran medida al hecho de que el registro se ha vuelto más difícil. Aquí me gustaría concentrarme en el aspecto técnico del problema, a saber, cómo registrar datos, cómo escribir registros en su propio archivo y qué es Monolog.

Tabla de contenidos


Monolog
Características de la aplicación en Magento 2
Implementación
Registro utilizando el registrador estándar
Registro utilizando un registrador estándar con un canal personalizado
Escribir en un archivo personalizado con su propio controlador
Escribir en un archivo personalizado usando virtualType
Registro de datos rápido
Conclusión

Monolog


Comencemos con la pregunta más importante: qué es Monolog y de dónde viene.

Monolog : esta es una biblioteca que implementa el estándar PSR-3 para el registro de datos. Es Monolog que se usa en Magento 2 para grabar registros.

PSR-3 es, a su vez, un estándar que describe un enfoque común para el registro de datos y recomendaciones para implementar registradores que proporcionan una interfaz común.

Puntos destacados de PSR-3
1. El registrador (objeto) debe implementar la interfaz \ Psr \ Log \ LoggerInterface.
2. Tenemos los siguientes niveles de error (indicados en orden de prioridad de mayor a menor):
EMERGENCIA : el sistema no se puede utilizar.
ALERTA : se deben tomar medidas de inmediato. Ejemplo: sitio web completo inactivo, base de datos no disponible, etc.
CRÍTICO - Condiciones críticas. Ejemplo: componente de aplicación no disponible, excepción inesperada.
ERROR : errores de tiempo de ejecución que no requieren una acción inmediata pero que, por lo general, deben monitorearse.
ADVERTENCIA : ocurrencias excepcionales que no son errores. Ejemplo: uso de API obsoletas.
AVISO : eventos normales pero significativos.
INFORMACIÓN - Eventos interesantes. Ejemplo: el usuario inicia sesión, los registros de SQL.
DEPURACIÓN : información de depuración detallada.

3. Cada nivel tiene su propio método (depuración, información, aviso, advertencia, error, crítico, alerta, emergencia / emergencia) y también debe haber un método de registro, que toma el nivel de error como primer parámetro.
4. Los métodos aceptan una cadena o cualquier cosa que implemente __toString () (es decir, debe usar print_r ($ mensaje, verdadero) manualmente para las matrices o pasarlas en el siguiente parámetro).
5. Todos los métodos aceptan una matriz $ context que complementa el registro.
6. Puede, pero no necesariamente, ser la sustitución de datos de la matriz de contexto $ en el mensaje. En este caso, se recomienda el formato {nombre}, donde nombre -> la clave de la matriz en $ contexto.


Monolog es bastante fácil de usar. Veamos el siguiente ejemplo.

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 

Aspectos destacados del trabajo de Monolog para tener en cuenta:

  • El registrador es un objeto que usamos para grabar registros. El registrador en sí no graba, pero administra los manejadores. Cualquier número puede ser creado.
  • Manejador : un objeto que procesa datos directamente. Puede agregar tantos controladores al registrador como desee. Todos ellos serán llamados por turno, independientemente de si el controlador dado pudo procesar el error o no. El método isHandling determina si este controlador podrá manejar el error recibido.

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

    El parecido más cercano al controlador, en mi opinión, es el Observador de eventos.
  • Procesador : cualquier entidad llamada (invocable). Quizás unos pocos. Se pueden asignar globalmente e instalar para el controlador. Primero, se lanzan procesadores globales. La tarea principal del procesador es agregar datos adicionales al registro (por ejemplo, la IP desde la que se realizó la conexión, el valor de las variables globales, la información en qué rama git se encuentra el código, etc.).
  • Formateador : convierte la salida del mensaje antes de escribir. Solo puede haber 1 por manejador. Es necesario cambiar el formato del cuerpo del mensaje, por ejemplo, para convertir texto a html o json.
  • Canal : el nombre del registrador. Se escribirá al grabar el registro. Dado que se puede usar 1 controlador en 2 registradores diferentes (escribirá registros en 1 el mismo archivo), esto determinará de dónde vino el error.
  • Nivel - nivel de error. Este parámetro para el controlador significa el nivel mínimo de error que manejará.
  • Burbuja : mensaje emergente. Después de que el controlador haya procesado el mensaje, el registrador pasa el mensaje al siguiente controlador. Este proceso se puede detener utilizando la propiedad bubble. Si el controlador tiene el valor de esta propiedad falso (el valor predeterminado siempre es verdadero), luego de que este controlador haga su trabajo (pudo procesar este error), otros controladores no se iniciarán.
  • Orden de clasificación - orden de ejecución. El último controlador agregado siempre se inicia el primero. Es esta característica la que le permite implementar un mecanismo para deshabilitar completamente el registrador (mediante burbujeo falso). Los controladores agregados a través del constructor van en el orden especificado en el constructor.

Dado que PSR-3 no obliga a los desarrolladores a implementar valores de Autocorrección en el texto, Monolog no lo hace de manera predeterminada. Si escribe -> emerg ('prueba 1111 {marcador de posición}', ['marcador de posición' => 'foo']) obtendrá lo siguiente
[2019-08-12 02:57:52] main.EMERGENCY: test 1111 {placeholder} {"placeholder": "foo"} []

Para que el reemplazo funcione, debe conectar un procesador adicional: \ Monolog \ Processor \ PsrLogMessageProcessor.
Vale la pena decir que Monolog tiene una gran cantidad de Formatter, Processor, Handler listos para usar. Puedes usarlos o escribir los tuyos.

Características de la aplicación en Magento 2


En el sitio web oficial de Magento puede encontrar un ejemplo general de cómo usar el registrador. Desafortunadamente, el ejemplo presentado no revela todos los detalles y, por desgracia, no responde a la pregunta "cómo escribir registros en su propio archivo". Por lo tanto, comprendamos todo con más detalle.

En los días de Magento 1, probablemente tarde o temprano todos usaron el método Mage :: log, que estaba disponible en todas partes en el código y la entrada de registro más simple se parecía a Mage :: log ('¡ALARMA!', Nulo, 'api.log'). Como resultado, teníamos un registro de la siguiente forma en el archivo var / log / api.log

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

Formato predeterminado:% timestamp %% priorityName% (% priority%):% message%.

Veamos cómo registrar datos en el caso más simple en Magento 2. La mayoría de las veces usará $ this -> _ logger-> info ('¡ALARMA!'); (si un objeto tiene dicha propiedad, por ejemplo, heredada).

Como resultado de dicha llamada, obtenemos la siguiente entrada en el archivo var / log / system.log

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

El formato predeterminado es [% datetime%]% channel%.% Level_name%:% message %% context %% extra%
Si el objeto no tiene dicha propiedad (_logger o logger), primero debemos agregar la dependencia \ Psr \ Log \ LoggerInterface a su clase y escribir el objeto resultante en la propiedad $ logger (de acuerdo con PSR-2 punto 4.2 y el ejemplo presentado en el sitio web de Magento ) .
A diferencia de Magento 1, hay muchos más matices aquí.

1. Parámetros para el registro.

Considere una llamada genérica al método de escritura

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

1) Donde {nivel} es, de acuerdo con PSR-3, 1 de los métodos reservados para registrar un cierto nivel de error (depuración, información, aviso, advertencia, error, crítico, alerta, emergencia / emergencia).
2) $ mensaje: a diferencia de Magento 1, debería ser una cadena. Es decir $ object-> getData () no funcionará aquí. La matriz de datos debe pasarse al siguiente parámetro. Los objetos \ Exception son una excepción, ya que la implementación de \ Magento \ Framework \ Logger \ Monolog los procesa por separado y automáticamente lanza -> getMessage () más como $ message si el objeto \ Exception se pasó como un mensaje.
3) $ context es un parámetro opcional, una matriz.

2. La propiedad $ this -> _ logger no está disponible en todas las clases.

Presente en: Bloque, Ayudante, Modelo, Colección, etc.
No disponible en: ResourceModel, Controller, Comand, Setup, etc.

Obtenga más información sobre ResourceModel y Collection.
ResourceModel tiene la propiedad _logger, pero no se completa en el constructor. Se rellena solo con el método privado getLogger en \ Magento \ Framework \ Model \ ResourceModel \ AbstractResource. Se llama al método solo en caso de error al escribir en la base de datos (en el bloque catch) dentro del método commit (). Hasta entonces, el modelo de recurso no tendrá un registrador.

 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; } 

La colección tiene un registrador desde el principio. Se asigna en el constructor \ Magento \ Framework \ Data \ Collection \ AbstractDb y luego se hereda.

Es imposible no decirlo, pero en los controladores hay una manera de obtener el registrador utilizando ObjectManager (a través de la propiedad $ this -> _ objectManager). Pero esta no es, por supuesto, la forma más correcta.

3. El registrador predeterminado y la lista de controladores.

En el di.xml global (aplicación / etc / di.xml) puede encontrar que \ Psr \ Log \ LoggerInterface es implementado por la clase \ Magento \ Framework \ Logger \ Monolog, que a su vez hereda de \ Monolog \ Logger. El nombre del registrador es main. Varios manejadores también se definen allí.

 <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> ... 

Algunas clases difieren de las mencionadas anteriormente (ya que se redefinen en el módulo Magento \ Developer):

1) Magento \ Framework \ Logger \ Handler \ System ( escucha INFO)
2) Magento \ Developer \ Model \ Logger \ Handler \ Debug ( escucha en DEBUG )
3) Magento \ Developer \ Model \ Logger \ Handler \ Syslog ( escucha DEBUG )

En las clases especificadas (Debug y Syslog) , se agrega la capacidad de deshabilitar el registro (dev / debug / debug_logging y dev / syslog / syslog_logging, respectivamente).

Tenga en cuenta que no hay un controlador de excepciones en la lista de controladores que escribe en exception.log. Se llama en el controlador del sistema.

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


Magento 2 a 2.2 tuvo un problema con el registrador al no poder saltar a otro controlador después del primero encontrado. Este problema fue causado por el hecho de que Monolog calculó que todos los manejadores llegaron a él en una matriz con claves digitales, y vinieron con claves alfabéticas (['system' =>, 'debug' =>, ...]). Los desarrolladores de Magento luego corrigieron la situación: convirtieron el hash en una matriz regular con claves digitales antes de pasarlo a Monolog. Monolog ahora también ha cambiado el algoritmo de enumeración del controlador y utiliza el método next ().
4. Introducción de su controlador en la lista de los existentes.

Llegamos a lo más interesante, que estropea un poco la impresión de implementación en Magento 2. No puede agregar un controlador personalizado a la lista de los existentes que usan di.xml sin ... "movimientos corporales adicionales". Esto se debe al principio de las configuraciones de fusión.

Hay varios ámbitos de configuración :

1) Inicial (aplicación / etc / di.xml)
2) Global ({moduleDir} /etc/di.xml)
3) Área específica ({moduleDir} / etc / {area} /di.xml, es decir, Frontend / adminhtml / crontab / webapi_soap / webapi_rest, etc.)

Dentro del nivel 1, las configuraciones se fusionan, pero el siguiente nivel las redefine en la fusión (si también se declaran allí). Esto hace que sea imposible agregar controladores en sus módulos a la lista existente, porque se declara en el ámbito inicial.

Quizás en el futuro veremos una implementación en la que la adición de controladores se trasladará del alcance inicial a otro módulo, transfiriéndose así al alcance global.

Implementación


Veamos las principales formas de registrar registros, que pueden sernos útiles en la implementación de tareas.

1. Registro utilizando el registrador estándar


Este método nos permite escribir fácilmente registros en 1 de los registros estándar (debug.log, system.log o 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 [] [] } } 

Todo se vuelve aún más simple si ya hay una dependencia de registrador heredada en nuestra clase.

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

2. Registro utilizando un registrador estándar con un canal personalizado


Este método difiere del anterior en que se crea un clon del registrador y se le asigna otro canal (nombre). Lo que simplificará la búsqueda dentro del archivo de registro.

 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 buscar los registros necesarios, ahora solo use la búsqueda por “api” (el registrador predeterminado en Magento 2 se llama main) en los archivos system.log, debug.log, exception.log existentes. Puede usar
 grep -rn 'api' var/log/system.log 


3. Escribir en un archivo personalizado utilizando su propio controlador


Creemos un controlador simple que registre todos los errores de nivel Crítico y superior en un archivo separado var / log / critical.log. Agregue la capacidad de bloquear todos los demás controladores para un nivel de error determinado y superior. Esto evitará la duplicación de datos en los archivos debug.log y 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() } } 

En Magento 2 2.2+ en el constructor \ Magento \ Framework \ Logger \ Handler \ Base, la forma de procesar la ruta al archivo de registro ha cambiado
 // BP . $this->fileName // BP. DIRECTORY_SEPARATOR . $this->fileName 

Por lo tanto, en los controladores más antiguos todavía puede encontrar / al comienzo de $ fileName.


Por ejemplo, vale la pena dar una pequeña explicación. Dado que Base no le permite establecer la propiedad de burbuja a través de los parámetros del constructor, tendríamos que repetir parte del código del constructor Base para pasar el parámetro de entrada correctamente al padre de la clase Base (que, por cierto, tiene un parámetro de entrada para establecer esta propiedad) o usar Tal enfoque. Elegí la segunda opción.

 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 [] [] } } 

Este método de agregar un controlador no es ideal, pero le permite alejarse del alcance de configuración del problema, lo que requerirá que dupliquemos todos los registradores en nuestro di.xml. Si el objetivo es reemplazar todos los registradores por los suyos, entonces es mucho mejor usar el enfoque virtualType, que consideraremos más adelante.

4. Escribir en un archivo personalizado usando virtualType


Este enfoque nos permite forzar la clase que necesitamos para escribir registros en el archivo de registro especificado para esto usando di.xml. Puede encontrar un enfoque similar en los módulos Magento \ Payment y Magento \ Shipping. Le llamo la atención sobre el hecho de que este enfoque funciona a partir de Magento 2 2.2 y superior.
En Magento 2 2.2+, se agregó un nuevo parámetro al constructor \ Magento \ Framework \ Logger \ Handler \ Base, que le permite crear un controlador virtual y especificar a través de di.xml la ruta relativa al archivo para escribir el registro. Anteriormente, era necesario especificar la ruta completa a través de $ filePath o crear un nuevo controlador y escribir la ruta relativa a la propiedad del archivo protegido $ fileName.

En el di.xml de nuestro módulo, agregue lo siguiente
  <!--      ,     --> <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> 

Agregue una clase de registrador a 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'); } } 

Ahora absolutamente todos los registros que se escribirán en nuestra clase serán procesados ​​por nuestra versión del registrador, que escribirá registros utilizando nuestro controlador en el archivo var / log / api.log.

4.1. Si la clase recibe el registrador a través del objeto $ context, y no a través de su constructor.
Estos incluyen \ Magento \ Catalog \ Model \ Product, cuyas dependencias no tienen \ Psr \ Log \ LoggerInterface, pero hay \ Magento \ Framework \ Model \ Context a través del cual el registrador se establece en la propiedad de clase. En este caso, necesitamos complicar un poco la opción anterior y reemplazar el registrador ubicado en el objeto $ context. Y para que esto no afecte a todo el Magento, reemplazaremos $ context solo para nuestra clase con 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 datos


Hay momentos en que necesitamos agregar rápidamente el registro. Muy a menudo, esto puede ser necesario ya sea en el servidor de producción o para pruebas rápidas.
 ... $log = new \Monolog\Logger('custom', [new \Monolog\Handler\StreamHandler(BP.'/var/log/custom.log')]); $log->error('test'); ... 

Ventajas de este enfoque: escribe una fecha, hay un contexto (matriz), agrega automáticamente \ n al final

En el ejemplo anterior, \ Monolog \ Logger se aplica específicamente, y no \ Magento \ Framework \ Logger \ Monolog que lo extiende. El hecho es que con este uso no hay diferencia, pero escribir menos (y recordarlo más fácilmente).

\ Monolog \ Handler \ StreamHandler, a su vez, se usa en lugar de \ Magento \ Framework \ Logger \ Handler \ Base ya que usar Base como un fragmento no es muy conveniente debido a dependencias adicionales en clases de terceros.

Otro enfoque que no se puede decir es el viejo y bueno file_put_contents.

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

Ventajas de este enfoque: escriba relativamente rápido y no necesita memorizar clases.

En ambos casos, el papel principal lo desempeña la constante BP . Ella siempre señala la carpeta con el magenta (1 nivel más alto que el pub), lo cual es conveniente y siempre nos ayuda a escribir registros en el lugar correcto.

Conclusión


Espero que la información anterior sea o sea de utilidad para usted.

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


All Articles