Cómo utilizar Monolog para escribir registros

Monolog es una biblioteca para bitácora de eventos en PHP 5.3 utilizada por Symfony2. Inspirada en la biblioteca LogBook de Python.

Usando

Para registrar un mensaje, simplemente consigue el servicio de registro cronológico del contenedor en tu controlador:

public function indexAction()
{
    $logger = $this->get('logger');
    $logger->info('I just got the logger');
    $logger->err('An error occurred');

    // ...
}

El servicio logger tiene diferentes métodos para diferentes niveles de registro cronológico de eventos. Ve la Symfony\Component\HttpKernel\Log\LoggerInterface para detalles de cuáles métodos están disponibles.

Manejadores y canales: escribiendo registros a diferentes ubicaciones

En Monolog cada cual registrador define un canal para su registro cronológico, el cual organiza tus mensajes de registro en diferentes «categorías». Luego, cada canal tiene una pila de manejadores para escribir los registros (los manejadores se pueden compartir).

Truco

Cuándo inyectas el registrador en un servicio puedes usar un canal personalizado para controlar en qué «canal» deberá llevar el registro cronológico de eventos.

El manipulador básico es el StreamHandler el cual escribe los registros en un flujo (por omisión en app/logs/prod.log en el entorno de producción y app/logs/dev.log en el entorno de desarrollo).

Monolog también viene con una potente capacidad integrada para encargarse del registro cronológico de eventos en el entorno de producción: FingersCrossedHandler. Esta te permite almacenar los mensajes en la memoria intermedia y registrarla sólo si un mensaje llega al nivel de acción (ERROR en la configuración prevista en la edición estándar) transmitiendo los mensajes a otro manipulador.

Utilizando varios manejadores

El registro cronológico de eventos utiliza una pila de manejadores que se van llamando sucesivamente. Esto te permite registrar los mensajes de varias formas fácilmente.

  • YAML
    # app/config/config.yml
    monolog:
        handlers:
            applog:
                type: stream
                path: /var/log/symfony.log
                level: error
            main:
                type: fingers_crossed
                action_level: warning
                handler: file
            file:
                type: stream
                level: debug
            syslog:
                type: syslog
                level: error
    
  • XML
    <!-- app/config/config.xml -->
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:monolog="http://symfony.com/schema/dic/monolog"
        xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
                            http://symfony.com/schema/dic/monolog http://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
    
        <monolog:config>
            <monolog:handler
                name="applog"
                type="stream"
                path="/var/log/symfony.log"
                level="error"
            />
            <monolog:handler
                name="main"
                type="fingers_crossed"
                action-level="warning"
                handler="file"
            />
            <monolog:handler
                name="file"
                type="stream"
                level="debug"
            />
            <monolog:handler
                name="syslog"
                type="syslog"
                level="error"
            />
        </monolog:config>
    </container>
    
  • PHP
    // app/config/config.php
    $container->loadFromExtension('monolog', array(
        'handlers' => array(
            'applog' => array(
                'type'  => 'stream',
                'path'  => '/var/log/symfony.log',
                'level' => 'error',
            ),
            'main' => array(
                'type'         => 'fingers_crossed',
                'action_level' => 'warning',
                'handler'      => 'file',
            ),
            'file' => array(
                'type'  => 'stream',
                'level' => 'debug',
            ),
            'syslog' => array(
                'type'  => 'syslog',
                'level' => 'error',
            ),
        ),
    ));
    

La configuración anterior define una pila de controladores que se llamarán en el orden en el cual se definen.

Truco

El controlador denominado «file» no se incluirá en la misma pila que se utiliza como un controlador anidado del controlador fingers_crossed.

Nota

Si deseas cambiar la configuración de MonologBundle en otro archivo de configuración necesitas redefinir toda la pila. No se pueden combinar, ya que el orden es importante y una combinación no te permite controlar el orden.

Cambiando el formateador

El controlador utiliza un formateador para dar formato al registro del evento antes de ingresarlo. Todos los manipuladores Monolog utilizan una instancia de Monolog\Formatter\LineFormatter por omisión, pero la puedes reemplazar fácilmente. Tu formateador debe implementar Monolog\Formatter\FormatterInterface.

  • YAML
    # app/config/config.yml
    services:
        my_formatter:
            class: Monolog\Formatter\JsonFormatter
    monolog:
        handlers:
            file:
                type: stream
                level: debug
                formatter: my_formatter
    
  • XML
    <!-- app/config/config.xml -->
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:monolog="http://symfony.com/schema/dic/monolog"
        xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
                            http://symfony.com/schema/dic/monolog http://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
    
            <services>
            <service id="my_formatter" class="Monolog\Formatter\JsonFormatter" />
            </services>
    
        <monolog:config>
            <monolog:handler
                name="file"
                type="stream"
                level="debug"
                formatter="my_formatter"
            />
        </monolog:config>
    </container>
    
  • PHP
    // app/config/config.php
    $container
        ->register('my_formatter', 'Monolog\Formatter\JsonFormatter');
    
    $container->loadFromExtension('monolog', array(
        'handlers' => array(
            'file' => array(
                'type'      => 'stream',
                'level'     => 'debug',
                'formatter' => 'my_formatter',
            ),
        ),
    ));
    

Agregando algunos datos adicionales en los mensajes de registro

Monolog te permite procesar el registro del evento antes de ingresarlo añadiendo algunos datos adicionales. Un procesador se puede aplicar al controlador de toda la pila o sólo a un controlador específico.

Un procesador simplemente es un ejecutable que recibe el registro como primer argumento.

Los procesadores se configuran usando la etiqueta DIC monolog.processor. Consulta la referencia sobre esto.

Agregando un segmento Sesión/Petición

A veces es difícil saber cuál de las entradas en el registro pertenece a cada sesión y/o petición. En el siguiente ejemplo agregaremos una ficha única para cada petición usando un procesador.

namespace Acme\MiBundle;

use Symfony\Component\HttpFoundation\Session\Session;

class SessionRequestProcessor
{
    private $session;
    private $token;

    public function __construct(Session $session)
    {
        $this->session = $session;
    }

    public function processRecord(array $record)
    {
        if (null === $this->token) {
            try {
                $this->token = substr($this->session->getId(), 0, 8);
            } catch (\RuntimeException $e) {
                $this->token = '????????';
            }
            $this->token .= '-' . substr(uniqid(), -8);
        }
        $record['extra']['token'] = $this->token;

        return $record;
    }
}
  • YAML
    # app/config/config.yml
    services:
        monolog.formatter.session_request:
            class: Monolog\Formatter\LineFormatter
            arguments:
                - "[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%%\n"
    
        monolog.processor.session_request:
            class: Acme\MyBundle\SessionRequestProcessor
            arguments:  ["@session"]
            tags:
                    - { name: monolog.processor, method: processRecord }
    
    monolog:
        handlers:
            main:
                type: stream
                path: "%kernel.logs_dir%/%kernel.environment%.log"
                level: debug
                formatter: monolog.formatter.session_request
    
  • XML
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:monolog="http://symfony.com/schema/dic/monolog"
        xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
                            http://symfony.com/schema/dic/monolog http://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
    
            <services>
            <service id="monolog.formatter.session_request" class="Monolog\Formatter\LineFormatter">
                <argument>[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%%\n</argument>
            </service>
    
            <service id="monolog.processor.session_request" class="Acme\MyBundle\SessionRequestProcessor">
                <argument type="service" id="session" />
                <tag name="monolog.processor" method="processRecord" />
            </service>
            </services>
    
        <monolog:config>
            <monolog:handler
                name="main"
                type="stream"
                path="%kernel.logs_dir%/%kernel.environment%.log"
                level="debug"
                formatter="monolog.formatter.session_request"
            />
        </monolog:config>
    </container>
    
  • PHP
    // app/config/config.php
    $container
        ->register('monolog.formatter.session_request', 'Monolog\Formatter\LineFormatter')
        ->addArgument('[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%%\n');
    
    $container
        ->register('monolog.processor.session_request', 'Acme\MyBundle\SessionRequestProcessor')
        ->addArgument(new Reference('session'))
        ->addTag('monolog.processor', array('method' => 'processRecord'));
    
    $container->loadFromExtension('monolog', array(
        'handlers' => array(
            'main' => array(
                'type'      => 'stream',
                'path'      => '%kernel.logs_dir%/%kernel.environment%.log',
                'level'     => 'debug',
                'formatter' => 'monolog.formatter.session_request',
            ),
        ),
    ));
    

Nota

Si utilizas varios manipuladores, también puedes registrar el procesador a nivel del manipulador en lugar de globalmente.

Bifúrcame en GitHub