Laravel com Datadog: A melhor forma de tratar seus logs
Quando estamos trabalhando com uma aplicação ou sistema que sejam utilizadas em produção, sempre alcançamos um nível de uso ou complexidade em que se faz necessário o uso de uma ferramenta de APM (Application Performance Monitoring ou Aplicação de Monitoramento de Performance). Uma dessas ferramentas é o Datadog, que possui um conjunto de ferramentas que permitem monitorar a sua aplicação.
Um dos principais itens quando se está analisando um problema em sua aplicação é o Log que ela gerou no momento do erro, o dado gerado pode ser captura de diversas formas e uma delas é através do stderr
(Standard Error) da máquina, onde as ferramentas de captura de logs podem ter acesso mais detalhado aos dados relacionados ao erro.
Como o Laravel faz o log de erros?
O Laravel é um famoso framework de PHP que possui uma robusta estrutura de Logging, o que nos permite fazer de forma facilitada diversas modificações em sua forma de geração de logs, como são salvas, onde serão salvos, quais informações foram incluídas, além de possibilitar o rastreio dos bugs que ocorrerem na aplicação.
Por padrão o Laravel vem configurado para apontar o salvamento dos logs de erros para a canal de stack
que normalmente escreve no arquivo padrão no caminho storage/logs/laravel.log
em que podemos alterar para uma das diversas opções disponíveis como papertrail
, slack
, syslog
entre outras opções, mas para nosso uso com o Datadog iremos utilizar o canal do stderr
.
Como o Log do Datadog funciona?
O Datadog é uma ferramenta de APM que utiliza a padronização de comunicação do OpenTelemetry como forma de acompanhar as métricas da aplicação, com os dados gerados pelo monitoramento das aplicações é gerada toda a estrutura dos produtos disponibilizados pela Datadog.
Em nosso caso os principais itens que vamos olhar serão Logs, Error Tracking e Trace que formam o conjunto necessário de ferramentas para que possamos fazer um acompanhamento apropriado do Ciclo de Vida de um Bug, como pode ser visto na imagem exemplo abaixo:
Como é possível ver na imagem, temos um conjunto de informações que nos permite analisar de forma mais detalhada como aconteceu o erro, qual usuário foi atingido pelo erro, e todos os dados necessários para que se possa reproduzir com precisão o cenário em que ocasionou o erro.
Melhorando os logs do Laravel para o Datadog
A configuração padrão que encontramos no Laravel é capaz de nos trazer algumas das informações minímas necessárias para que possamos destrinchar o nosso bug, mas para que ele possa se integrar ao serviço do Datadog da melhor forma possível precisamos realizar algumas pequenas mudanças em nossas configurações no arquivo logging.php
onde vamos adequar algumas opções no nosso canal do stderr
conforme o exemplo abaixo:
<?php
use App\Logging\CustomizeOutputFormatter;
use Monolog\Formatter\JsonFormatter;
use Monolog\Handler\StreamHandler;
return [
'default' => env('LOG_CHANNEL', 'stderr'),
'deprecations' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'channels' => [
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'formatter' => JsonFormatter::class,
'with' => [
'stream' => 'php://stderr',
],
'tap' => [
CustomizeOutputFormatter::class,
],
],
],
];
No bloco de código acima configuramos nosso canal do stderr
para formatar (formatter
) nosso log para JSON utilizando o formatador padrão disponibilizado pelo Monolog, o que nos permite ter uma leitura mais assertiva das informações que adicionarmos em nossos logs pelas ferramentas da Datadog.
Outro item que adicionamos também é o tap
que atua como formatador customizado que é capaz de adicionar mais dados para o log que estamos enviando para o stderr
e além disso alimentar o nosso log com mais detalhes ou até mesmo alterar como apresentamos os dados dentro do log que é algo que vemos no bloco de código abaixo:
<?php
declare(strict_types=1);
namespace App\Logging;
class CustomizeOutputFormatter
{
/**
* Customize the given logger instance.
*
* @param \Illuminate\Log\Logger $logger
* @return void
*/
public function __invoke($logger)
{
$appName = config('app.name');
foreach ($logger->getHandlers() as $handler) {
$handler->pushProcessor(function ($record) use ($appName) {
$context = $this->getTraceContext();
return [
'app' => $appName,
'time' => $record['datetime'],
'channel' => $record['channel'],
'level' => $record['level_name'],
'message' => $record['message'],
'context' => $record['context'],
'extra' => $record['extra'],
'dd' => [
'trace_id' => $context['trace_id'] ?? null,
'span_id' => $context['span_id'] ?? null,
],
];
});
}
}
private function getTraceContext(): array
{
if (! function_exists('\DDTrace\current_context')) {
return [];
}
return \DDTrace\current_context();
}
}
No código acima, foram feitas poucas alterações a estrutura original de logs do Laravel, foram alterados as chaves para os campos datetime
e level_name
em que foram ajustados para time
e level
, respectivamente. E fizemos a adição de outro campo em nosso Log em que trazemos o dd.trace_id
e dd.span_id
, utilizando o pacote de instrumentação automática disponibilizado pela Datadog, estes campos que são os identificadores utilizados pela ferramenta de Trace.
Está configuração nos permite ter o cenário que encontramos abaixo em que somos capazes de vincular um Trace automatizado do fluxo da aplicação com o Log do erro que ocorreu de forma a trazer para nós mais informações quanto ao problema que está acontecendo e nos permitir uma analise mais assertiva.
Somente com estas pequenas configurações você será capaz de tornar a integração com os serviços da Datadog e sua aplicação em Laravel mais eficazes quando precisar realizar manutenções ou até mesmo melhorias em etapas do código em que somente a resposta de erro tornava a situação obscura.