Logando com Monolog

Em desenvolvimento e manutenção de aplicações logs são importantes, eles fornecem informações que auxiliam no monitoramento e ações corretivas de sua aplicação. O Monolog é uma biblioteca de logs para PHP muito popular que permite registrar logs em diversos meios como arquivos, bancos de dados, e-mail, sockets, etc., também implementando a interface de log PSR-3, garantindo assim compatibilidade com outras bibliotecas.

Para instalar o Monolog utilizamos o composer:

composer require monolog/monolog

Antes de mais nada precisamos entender alguns conceitos do Monolog, primeiro todo Logger possui um channel ou canal, que é um nome que será associado a cada entrada do log do logger, podendo cada parte da aplicação ter um logger com um channel diferente para melhor diferenciá-los, facilitando a filtragem de informações,

Ao Logger deve ser adicionado um ou mais handlers que são componentes que registram os logs em determinados meios, como o clássico arquivos ou sockets e bancos de dados por exemplo.

Outro conceito importante no uso de logs é o nível do registro de log, nem toda informação tem a mesma “importância” no log, ou a mesma urgência para ser tratada, então as entradas de um log são categorizadas por níveis:

  • DEBUG (100): Informação de depuração.
  • INFO (200): Eventos interessantes. Por exemplo: um usuário realizou o login ou logs de SQL.
  • NOTICE (250): Eventos normais, mas significantes.
  • WARNING (300): Ocorrências excepcionais, mas que não são erros. Por exemplo: Uso de APIs descontinuadas, uso inadequado de uma API. Em geral coisas que não estão erradas mas precisam de atenção.
  • ERROR (400): Erros de tempo de execução que não requerem ação imediata, mas que devem ser logados e monitorados.
  • CRITICAL (500): Condições criticas. Por exemplo: Um componente da aplicação não está disponível, uma exceção não esperada ocorreu.
  • ALERT (550): Uma ação imediata deve ser tomada. Exemplo: O sistema caiu, o banco de dados está indisponível , etc. Deve disparar um alerta para o responsável tomar providencia o mais rápido possível.
  • EMERGENCY (600): Emergência: O sistema está inutilizável.

Então vamos a um exemplo:

use Monolog\Handler\StreamHandler;
use Monolog\Logger;
require('vendor/autoload.php');

$logger = new Logger('app-name-log');
$handler = new StreamHandler('php://stdout', Logger::DEBUG);
$logger->pushHandler($handler);

$logger->debug('uma mensagem de debug');
$logger->info('uma mensagem de info');
$logger->notice('uma mensagem de notice');
$logger->warning('Uma mensagem de aviso');
$logger->error('Uma mensagem de erro');   
$logger->critical('Uma mensagem critica');    
$logger->alert('Uma mensagem de alerta');    
$logger->emergency('Uma mensagem de emergencia');  

Primeiro, na linha 5, criamos um objeto Logger passando para ele o channel do log, que será adicionado a cada registro de log, neste caso “app-name-log”.

Na linha 6 criamos um objeto StreamHandler. Este objeto representa o meio onde os registro de log serão feitos, neste caso em uma Stream apontando para php://stdout, ou seja, a saída padrão o console. Como segundo parâmetro passamos o nível do log a partir do qual este handler irá registrar, neste caso informamos DEBUG que é o nível de log mais baixo, então os níveis INFO, NOTICEWARNING, ERROR, etc., também serão registrados. Se tivéssemos informado WARNING como o nível mínimo do handler, registros de nível DEBUG, INFO e NOTICE não seriam registrados por este handler. Na linha 7 adicionamos o handler criado ao logger através do método pushHandler.

Da linha 8 em diante registramos uma entrada de log para cada um dos níveis através dos seus respectivos métodos. A saída do log será assim:

[2021-03-24T16:50:13.837560-03:00] app-name-log.DEBUG: uma mensagem de debug [] []
[2021-03-24T16:50:13.889085-03:00] app-name-log.INFO: uma mensagem de info [] []
[2021-03-24T16:50:13.889194-03:00] app-name-log.NOTICE: uma mensagem de notice [] []
[2021-03-24T16:50:13.889299-03:00] app-name-log.WARNING: Uma mensagem de aviso [] []
[2021-03-24T16:50:13.889403-03:00] app-name-log.ERROR: Uma mensagem de erro [] []
[2021-03-24T16:50:13.889519-03:00] app-name-log.CRITICAL: Uma mensagem de erro [] []
[2021-03-24T16:50:13.889677-03:00] app-name-log.ALERT: Uma mensagem de erro [] []
[2021-03-24T16:50:13.889846-03:00] app-name-log.EMERGENCY: Uma mensagem de erro [] []

Note que cada entrada possui a data do registro, o channel do logger seguido por seu nível e a mensagem associada.

Podemos também ter mais de um handler para um logger registrando em meios diferentes por exemplo no console como visto acima e em um arquivo.

$logger = new Logger('app-name-log');

$handlerConsole = new StreamHandler('php://stdout', Logger::DEBUG);
$logger->pushHandler($handlerConsole);

$handlerFile = new StreamHandler('my-log-file.log', Logger::DEBUG);
$logger->pushHandler($handlerFile);

$logger->debug('uma mensagem de debug');

Neste exemplo além do handler para a saída padrão também criamos um handler para um arquivo, simplesmente informando seu caminho no construtor de StreamHandler na linha 6.

Registrando Logs em e-mail no Monolog

Alguns registro de logs são tão importantes, como os níveis CRITICAL e EMERGENCY, que devem ser visto o mais cedo possível, e uma das maneiras possíveis é enviar eles por e-mail. O Monolog possui um handler para isso, o SwiftMailerHandler, que utiliza a biblioteca swiftmailer, que pode ser instalada através do composer.

composer require swiftmailer/swiftmailer

Então vamos a um exemplo:

$logger = new Logger('app-name-log');

$transporter = new Swift_SmtpTransport('smtp.seudominio.com.br', 465, 'ssl');
$transporter->setUsername('sistema@botecodigital.dev.br');
$transporter->setPassword('*********');

$mailer = new Swift_Mailer($transporter);

$message = new Swift_Message('Um log CRITICAL foi adicionado.');
$message->setFrom(['sistema@botecodigital.dev.br' => 'Alerta de Sistema']);
$message->setTo(['rodrigo@botecodigital.dev.br' => 'Rodrigo']);

$handler = new SwiftMailerHandler($mailer, $message, Logger::CRITICAL);
$logger->pushHandler($handler);

$logger->critical('Um erro critico!');

Primeiro criamos nosso logger na linha 1.

Na linha 3 criamos o objeto de transport Swift_SmtpTransport para mailer utilizar para enviar a mensagem. Em seu construtor passamos o endereço do servidor smtp, a porta e a encriptação utilizada na conexão, no nosso caso utilizamos ssl. Na linha 4 configuramos o nome do usuário para autenticação e em seguida a senha.

Na linha 7 criamos o mailer, que irá enviar a mensagem. Em seu construtor passamos o objeto de transport $transporter.

Na linha 9 criamos o objeto de mensagem Swift_Message, em seu construtor passamos o assunto do e-mail que será enviado. Em seguida configuramos o remetente da mensagem e o destinatário.

Na linha 13 criamos o handler SwiftMailerHandler, passando o mailer que irá enviar a mensagem, a mensagem e o nível de log a partir do qual será registrado. Em seguida adicionamos esse handler ao nosso logger.

Exemplo de mensagem de log por email

Registrando Logs em sockets no Monolog

O registros de log do Monolog podem ser enviados através de um socket facilmente utilizando o handler SocketHandler, passando para seu construtor o endereço e a porta, seguido pelo nível de log a ser registrado.

Este recurso pode ser interessante para mandar os logs para um sistema de monitoramento externo por exemplo. Veja um exemplo simples:

$logger = new Logger('app-name-log');

$handlerSocket = new SocketHandler('localhost:6666', Logger::DEBUG);
$logger->pushHandler($handlerSocket);

while(true){
    sleep(2);
    $logger->alert('Um alerta');
}

Para testar o exemplo acima pode-se utilizar o seguinte script que simplesmente recebe informações via socket e mostra no console.

//socket_server.php
$server = stream_socket_server('tcp://localhost:6666', $errno, $errstr);
echo sprintf("Escutando em: %s\n", stream_socket_get_name($server, false));
while(true){
    $connection = stream_socket_accept($server, -1, $clientAddress);
    if($connection){
        echo "Cliente [{$clientAddress}] connected\n";
        while( $buffer = fread($connection, 2048)){
            if($buffer !== ''){
                echo "{$buffer}\n";
            }
        }
        fclose($connection);
    }
}

// para executar "php socket_server.php"

Registrando Logs no MongoDB no Monolog

Outro meio para armazenar os registros de logs é em um banco de dados, e o Monolog já possui um handler para o Mongodb que pode ser facilmente utilizado.

Para isso não se esqueça de habilitar a extensão do mongodb no seu PHP e instale a biblioteca mongodb/mongodb através do composer:

composer require mongodb/mongodb

Então vamos a um exemplo:

$logger = new Logger('app-name-log');

$client= new MongoDB\Client("mongodb+srv://<user>:<password>@cluster0.mvq1d.mongodb.net/app?retryWrites=true&w=majority");

$mongoHandler = new MongoDBHandler($client, "app", "logs"); 
$logger->pushHandler($mongoHandler);

$logger->alert('Um alerta');

O exemplo é bem simples, criamos um cliente para acessar o mongodb, neste exemplo usamos o Atlas mesmo. Com o cliente criamos o handler na linha 5, passando o objeto de cliente do mongodb, a database da nossa aplicação e em qual collection nossos logs serão armazenados. Depois simplesmente adicionamos o handler ao logger.

Exemplo de log registrado no mongodb

Processors

Podemos adicionar mais informações a um registro de log adicionando ao logger um processor. Adicionamos um processor passando uma função para o método pushProcessor. Esta função irá receber um array associativo contendo os dados que serão registrados no log. Este array possui as chaves: message, level, level_name, channel, datetime e extra, sendo na chave extra que devemos adicionar os dados adicionais que desejamos. Podemos por exemplo adicionar dados sobre o ambiente de execução de quando o log foi gerado por exemplo. No nosso exemplo adicionamos somente o uso de memória.

$logger = new Logger('app-name-log');
$handlerConsole = new StreamHandler('php://stdout', Logger::DEBUG);
$logger->pushHandler($handlerConsole);

$logger->pushProcessor(function ($entry) {
    $entry['extra']['memory'] = memory_get_usage();
    return $entry;
});

$logger->error('Deu algo estranho');

//saida
//[2021-03-25T15:14:12.307156-03:00] app-name-log.ERROR: Deu algo estranho [] {"memory":855864}

O Monolog é uma biblioteca muito interessante, que já vem em muitos frameworks populares como Laravel e Symfony. Vimos apenas uma parcela muito pequena de seus handlers que permitem a integração com diversas outras tecnologias interessantes.

Bom espero que tenha sido possível ter uma visão geral desta biblioteca. T++.