Monolog¶
Vamos a probar Composer añadiendo la librería de Monolog a nuestro proyecto. Se trata de un librería para la gestión de logs de nuestras aplicaciones, soportando diferentes niveles (info, warning, etc...), salidas (ficheros, sockets, BBDD, Web Services, email, etc) y formatos (texto plano, HTML, JSON, etc...).
Para ello, incluiremos la librería en nuestro proyecto con:
composer require monolog/monolog
Monolog 2 requiere al menos PHP 7.2, cumple con el estándar de logging PSR-3, y es la librería empleada por Laravel y Symfony para la gestión de logs.
¿Cuándo usar un log?
- Seguir las acciones/movimientos de los usuarios
- Registrar las transacciones
- Rastrear los errores de usuario
- Fallos/avisos a nivel de sistema
- Interpretar y recopilar datos para posterior investigación de patrones
Niveles¶
A continuación mostramos los diferentes niveles de menos a más restrictivo:
- debug -100: Información detallada con propósitos de debug. No usar en entornos de producción.
- info - 200: Eventos interesantes como el inicio de sesión de usuarios.
- notice - 250: Eventos normales pero significativos.
- warning - 300: Ocurrencias excepcionales que no llegan a ser error.
- error - 400: Errores de ejecución que permiten continuar con la ejecución de la aplicación pero que deben ser monitorizados.
- critical - 500: Situaciones importantes donde se generan excepciones no esperadas o no hay disponible un componente.
- alert - 550: Se deben tomar medidas inmediatamente. Caída completa de la web, base de datos no disponible, etc... Además, se suelen enviar mensajes por email.
- emergency - 600: Es el error más grave e indica que todo el sistema está inutilizable.
Hola Monolog¶
Por ejemplo, en el archivo pruebaLog.php
que colocaríamos en el raíz, primero incluimos el autoload, importamos las clases a utilizar para finalmente usar los métodos de Monolog:
<?php
include __DIR__ ."/vendor/autoload.php";
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger("MiLogger");
$log->pushHandler(new StreamHandler("logs/milog.log", Logger::DEBUG));
$log->debug("Esto es un mensaje de DEBUG");
$log->info("Esto es un mensaje de INFO");
$log->warning("Esto es un mensaje de WARNING");
$log->error("Esto es un mensaje de ERROR");
$log->critical("Esto es un mensaje de CRITICAL");
$log->alert("Esto es un mensaje de ALERT");
En todos los métodos de registro de mensajes (debug
, info
, ...), además del propio mensaje, le podemos pasar información como el contenido de alguna variable, usuario de la aplicación, etc.. como segundo parámetro dentro de un array, el cual se conoce como array de contexto. Es conveniente hacerlo mediante un array asociativo para facilitar la lectura del log.
<?php
$log->warning("Producto no encontrado", [$producto]);
$log->warning("Producto no encontrado", ["datos" => $producto]);
Funcionamiento¶
Cada instancia Logger
tiene un nombre de canal y una pila de manejadores (handler).
Cada mensaje que mandamos al log atraviesa la pila de manejadores, y cada uno decide si debe registrar la información, y si se da el caso, finalizar la propagación.
Por ejemplo, un StreamHandler
en el fondo de la pila que lo escriba todo en disco, y en el tope añade un MailHandler
que envíe un mail sólo cuando haya un error.
Manejadores¶
Cada manejador también tiene un formateador (Formatter
). Si no se indica ninguno, se le asigna uno por defecto. El último manejador insertado será el primero en ejecutarse. Luego se van ejecutando conforme a la pila.
Los manejadores más utilizados son:
StreamHandler(ruta, nivel)
RotatingFileHandler(ruta, maxFiles, nivel)
NativeMailerHandler(para, asunto, desde, nivel)
FirePHPHandler(nivel)
Si queremos que los mensajes de la aplicación salgan por el log del servidor,
en nuestro caso el archivo error.log
de Apache utilizaremos como ruta la salida de error:
<?php
// error.log
$log->pushHandler(new StreamHandler("php://stderr", Logger::DEBUG));
FirePHP
Por ejemplo, mediante FirePHPHandler
, podemos utilizar FirePHP
, la cual es una herramienta para hacer debug en la consola de Firefox.
Tras instalar la extensión en Firefox, habilitar las opciones y configurar el Handler, podemos ver los mensajes coloreados con sus datos:
<?php
$log = new Logger("MiFirePHPLogger");
$log->pushHandler(new FirePHPHandler(Logger::INFO));
$datos = ["real" => "Bruce Wayne", "personaje" => "Batman"];
$log->debug("Esto es un mensaje de DEBUG", $datos);
$log->info("Esto es un mensaje de INFO", $datos);
$log->warning("Esto es un mensaje de WARNING", $datos);
// ...
Canales¶
Se les asigna al crear el Logger
. En grandes aplicaciones, se crea un canal por cada subsistema: ventas, contabilidad, almacén.
No es una buena práctica usar el nombre de la clase como canal, esto se hace con un procesador.
Para su uso, es recomiendo asignar el log a una propiedad privada a Logger, y posteriormente, en el constructor de la clase, asignar el canal, manejadores y formato.
<?php
$this->log = new Logger("MiApp");
$this->log->pushHandler(new StreamHandler("logs/milog.log", Logger::DEBUG));
$this->log->pushHandler(new FirePHPHandler(Logger::DEBUG));
Y dentro de los métodos para escribir en el log:
<?php
$this->log->warning("Producto no encontrado", [$producto]);
Procesadores¶
Los procesadores permiten añadir información a los mensajes. Para ello, se apilan después de cada manejador mediante el método pushProcessor($procesador)
.
Algunos procesadores conocidos son IntrospectionProcessor
(muestran la linea, fichero, clase y metodo desde el que se invoca el log), WebProcessor
(añade la URI, método e IP) o GitProcessor
(añade la rama y el commit).
<?php
$log = new Logger("MiLogger");
$log->pushHandler(new RotatingFileHandler("logs/milog.log", 0, Logger::DEBUG));
$log->pushProcessor(new IntrospectionProcessor());
$log->pushHandler(new StreamHandler("php://stderr", Logger::WARNING));
// no usa Introspection pq lo hemos apilado después, le asigno otro
$log->pushProcessor(new WebProcessor());
[2020-11-26T13:35:31.076138+01:00] MiLogger.DEBUG: Esto es un mensaje de DEBUG [] {"file":"C:\\xampp\\htdocs\\log\\procesador.php","line":12,"class":null,"function":null}
[2020-11-26T13:35:31.078344+01:00] MiLogger.INFO: Esto es un mensaje de INFO [] {"file":"C:\\xampp\\htdocs\\log\\procesador.php","line":13,"class":null,"function":null}
Formateadores¶
Se asocian a los manejadores con setFormatter
. Los formateadores más utilizados son LineFormatter
, HtmlFormatter
o JsonFormatter
.
<?php
$log = new Logger("MiLogger");
$rfh = new RotatingFileHandler("logs/milog.log", Logger::DEBUG);
$rfh->setFormatter(new JsonFormatter());
$log->pushHandler($rfh);
{"message":"Esto es un mensaje de DEBUG","context":{},"level":100,"level_name":"DEBUG","channel":"MiLogger","datetime":"2020-11-27T15:36:52.747211+01:00","extra":{}}
{"message":"Esto es un mensaje de INFO","context":{},"level":200,"level_name":"INFO","channel":"MiLogger","datetime":"2020-11-27T15:36:52.747538+01:00","extra":{}}
Más información
Más información sobre manejadores, formateadores y procesadores en https://github.com/Seldaek/monolog/blob/master/doc/02-handlers-formatters-processors.md
Uso de Factorías¶
En vez de instanciar un log en cada clase, es conveniente crear una factoría (por ejemplo, siguiendo la idea del patrón de diseño Factory Method).
Para el siguiente ejemplo, vamos a suponer que creamos la factoría en el namespace Dwes\Ejemplos\Util
.
<?php
namespace Dwes\Ejemplos\Util
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
class LogFactory {
public static function getLogger(string $canal = "miApp") : Logger {
$log = new Logger($canal);
$log->pushHandler(new StreamHandler("logs/miApp.log", Logger::DEBUG));
return $log;
}
}
Si en vez de devolver un Monolog\Logger
utilizamos el interfaz de PSR, si en el futuro cambiamos la implementación del log, no tendremos que modificar nuestro código. Así pues, la factoría ahora devolverá Psr\Log\LoggerInterface
:
<?php
namespace Dwes\Ejemplos\Util
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
class LogFactory {
public static function getLogger(string $canal = "miApp") : LoggerInterface {
$log = new Logger($canal);
$log->pushHandler(new StreamHandler("log/miApp.log", Logger::DEBUG));
return $log;
}
}
Finalmente, para utilizar la factoría, sólo cambiamos el código que teníamos en el constructor de las clases que usan el log, quedando algo asi:
<?php
namespace Dwes\Ejemplos\Model;
use Dwes\Ejemplos\Util\LogFactory;
use Monolog\Logger;
class Cliente {
private $codigo;
private Logger $log;
function __construct($codigo){
$this->codigo=$codigo;
$this->log = LogFactory::getLogger();
}
/// ... resto del código
}
Actividades¶
-
AC 503. (RA4 RA5 / CE4f CE5h / IC1 / 3p) - Crea un nuevo proyecto con Composer llamado
Monologos
:- Incluye como librería la última versión de Monolog.
- Crea la clase
Dwes\Monologos\HolaMonolog
. - Define una propiedad privada nombrada
miLog
para guardar el log. - Define en el constructor un
RotatingFileHandler
que escriba en la carpetalogs
del proyecto, y que almacene los mensajes a partir de debug. - Crea los métodos
saludar
ydespedir
que hagan un log de tipo info con la acción correspondiente.
-
AC 504. (RA4 RA5 / CE4f CE5h / IC1 / 3p) - Siguiendo con el proyecto
Monologos
:- Crea un archivo llamado
inicio.php
que permita probarHolaMonolog
. - Comprueba que los mensajes aparecen en el log.
- Cambia el nivel para que el manejador solo muestre los mensajes a partir de warning.
- Vuelve a ejectuar
inicio.php
y comprueba el archivo de log. - Los métodos, además de escribir en el log, han de devolver el saludo como cadena.
- Crea un archivo llamado