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:
- 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.
- El texto del mensaje de log.
- La prioridad del mensaje (DEBUG, NOTE, INFO, …). Puede ser nula.
- 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);
}
}