Gestores de log


1Presentación

Un gestor de log es un objeto que recibe los mensajes de log. Esto permite procesar los logs de una manera más avanzada que simplemente escribiéndolos en un archivo (como hace el framework por defecto).

El uso habitual es enviar los logs a un servidor centralizado (Graylog, LogStash, Fluentd, rsyslog, syslog-ng, Nagios, ELK, Datadog…), además de (o en lugar de) escribirlos en el archivo log/temma.log.


2Configuración

En el archivo de configuración etc/temma.php, la directiva logManager permite definir uno o varios objetos que serán llamados para gestionar los logs generados por la aplicación.
Los objetos pueden colocarse en un namespace.

Aquí tienes un fragmento de configuración, que define un gestor de log:

<?php

return [
    'application' => [
        'logManager' => 'MyLogManager'
    ]
];

También es posible proporcionar una lista de objetos:

<?php

return [
    'application' => [
        'logManager' => [ 'MyLogManager', '\My\Other\Log\Manager' ]
    ]
];

Si quieres usar solo gestores de log, y desactivar la escritura en el archivo log/temma.log, debes definir la directiva logFile como false, null o asignarle una cadena vacía:

<?php

return [
    'application' => [
        'logFile'    => false,
        'logManager' => 'MyLogManager',
    ]
];

3Desarrollo simple

Un gestor de log es un objeto que implementa la interfaz \Temma\Web\LogManager. Se crea una instancia del objeto cuando el framework arranca.

El gestor de log debe contener un método log(), que será llamado en cada nuevo mensaje de log, recibiendo cuatro parámetros:

  1. Un identificador de traza (trace). Es una cadena alfanumérica de 4 caracteres que se usa para identificar los logs generados por la misma petición.
  2. El texto del mensaje de log.
  3. La prioridad del mensaje (DEBUG, NOTE, INFO, …). Puede ser nula.
  4. La "clase de log" del mensaje. Puede ser nula.

Ejemplo:

/** Gestor de log que escribe mensajes en un archivo. */
class FileLogManager implements \Temma\Web\LogManager {
    /**
     * Escribe los logs de la aplicación en un archivo.
     * @param  string   $traceId  Identificador de traza.
     * @param  string   $text     Texto del mensaje.
     * @param  ?string  $priority Prioridad del mensaje.
     * @param  ?string  $class    Clase de log del mensaje.
     */
    public function log(string $traceId, string $text, ?string $priority, ?string $class) : void {
        $path = "/var/log/temma/$traceId.log";
        $message = "[$priority] ($class) $text";
        file_put_contents($path, $message, FILE_APPEND);
    }
}

En este ejemplo, el gestor de log creará un nuevo archivo para cada petición procesada por el framework.

  • Línea 2: Definición del objeto.
  • Línea 10: Definición del método log(), que será llamado en cada escritura de log.
  • Línea 11: Generación de la ruta del archivo.
  • Línea 12: Generación del mensaje de texto.
  • Línea 13: Escribe el mensaje en el archivo. Si el archivo no existía, se crea; en caso contrario, el mensaje se añade al final del archivo.

4Inyección de dependencias

Consulta la documentación de inyección de dependencias para entender su propósito y su uso en Temma.

Para que un gestor de log tenga acceso al componente de inyección de dependencias, debe implementar la interfaz \Temma\Base\Loadable (además de la interfaz \Temma\Web\LogManager). Deberá entonces implementar un constructor que reciba un objeto \Temma\Base\Loader como parámetro, mediante el cual podrá acceder a otros datos y objetos.
El componente ofrece, en particular, acceso a los datos de configuración del framework.

Ejemplo:

/**
 * Gestor de log que envía los logs a un servicio externo.
 * Una configuración extendida "x-external-log" debe contener una clave "url",
 * que contiene la URL a la que se enviarán los mensajes de log.
 */
class ExternalLogManager implements \Temma\Base\Loader, \Temma\Web\LogManager {
    /** URL de conexión externa. */
    private ?string $_url;

    /** Constructor. */
    public function __construct(\Temma\Base\Loader $loader) {
        // recupera la URL a la que se enviarán los logs
        $this->_url = $loader->config->xtra('external-log', 'url');
    }
    /** Envía los logs de la aplicación al servicio externo. */
    public function log(string $traceId, string $text, ?string $priority, ?string $class) : void {
        // verifica los parámetros de conexión
        if (!$this->_url)
            return;
        // creación de la petición
        $ctx = stream_context_create([
            'http' => [
                'method'  => 'POST',
                'header'  => 'Content-Type: application/x-www-form-urlencoded',
                'content' => http_build_query([
                    'message'  => "($traceId) [$priority] ($class) $text",
                    'hostname' => gethostname(),
                ]),
            ],
        ]);
        // envía al servicio externo
        file_get_contents($this->_url, false, $ctx);
    }
}

En este ejemplo, el objeto recupera la URL de conexión del servicio externo desde la configuración y luego la usa para enviar los mensajes de log.


5Acceso a la base de datos

Debes saber que, en la cadena de inicialización del framework, los gestores de log se crean antes de que se creen los objetos de conexión a las fuentes de datos (esto para que, en caso de un error durante la creación de esas conexiones, la información aparezca en los logs).

Esto significa que, si tu gestor de log necesita usar una conexión (por ejemplo, para escribir logs en una base de datos), no podrá recuperar la conexión en su constructor; tendrá que hacerlo en su método log(), y comprobar que la conexión esté activa.

Ejemplo:

/** Gestor de log que escribe en la base de datos. */
class DatabaseLogManager implements \Temma\Base\Loader, \Temma\Web\LogManager {
    /** Componente de inyección de dependencias. */
    private \Temma\Base\Loader $_loader;

    /** Constructor. Obtiene el componente de inyección de dependencias. */
    public function __construct(\Temma\Base\Loader $loader) {
        $this->_loader = $loader;
    }
    /** Escribe los logs de la aplicación en la base de datos. */
    public function log(string $traceId, string $text, ?string $priority, ?string $class) : void {
        // obtiene la conexión a la base de datos
        $db = $this->_loader->dataSources->db ?? null;
        if (!$db)
            return;
        // escritura del log
        $sql = "INSERT INTO Log
                SET log_date = NOW(),
                    host = " . $db->quote(gethostname()) . ",
                    message = " . $db->quote($text) . ",
                    traceId = " . $db->quote($traceId) . ",
                    priority = " . $db->quote($priority) . ",
                    class = " . $db->quote($class);
        $db->exec($sql);
    }
}