Logs em PHP 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, NOTICE, WARNING, 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.
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.
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++.