Arquivo de log customizado no Magento 2 com Monolog (sem classes extras)
Toda integração no Magento acaba precisando do seu próprio log. Quando uma exportação para o ERP falha às 3 da manhã, procurar as linhas certas em um system.log compartilhado por todos os módulos é lento. Um arquivo dedicado, com mensagens claras e contexto, transforma isso em uma checagem de um minuto.
O Magento 2 usa o Monolog por trás da LoggerInterface do PSR-3. Graças à dependency injection, você pode dar a uma classe o seu próprio arquivo de log sem escrever uma única classe PHP nova. Este artigo mostra primeiro essa abordagem, depois uma classe de handler customizada para casos que pedem mais controle, e as práticas que tornam os logs úteis.
Como o log é ligado no Magento
Quando uma classe pede Psr\Log\LoggerInterface no constructor, o di.xml do core entrega o Magento\Framework\Logger\Monolog, que estende o logger do Monolog. Esse logger tem handlers para var/log/system.log, var/log/debug.log e var/log/exception.log, entre outros.
O handler de arquivos é o Magento\Framework\Logger\Handler\Base. Ele recebe um nome de arquivo, grava pelo filesystem driver do Magento e tem um nível mínimo. Como tanto o logger quanto o handler são classes normais criadas pelo object manager, você pode montar novas combinações delas no di.xml.
Um arquivo de log customizado com virtual types
Para um módulo Vendor_Shipping que chama uma API externa de tarifas, adicione isto ao etc/di.xml:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<virtualType name="Vendor\Shipping\Logger\Handler" type="Magento\Framework\Logger\Handler\Base">
<arguments>
<argument name="fileName" xsi:type="string">/var/log/vendor_shipping.log</argument>
</arguments>
</virtualType>
<virtualType name="Vendor\Shipping\Logger\Logger" type="Magento\Framework\Logger\Monolog">
<arguments>
<argument name="name" xsi:type="string">vendor_shipping</argument>
<argument name="handlers" xsi:type="array">
<item name="file" xsi:type="object">Vendor\Shipping\Logger\Handler</item>
</argument>
</arguments>
</virtualType>
<type name="Vendor\Shipping\Model\RateClient">
<arguments>
<argument name="logger" xsi:type="object">Vendor\Shipping\Logger\Logger</argument>
</arguments>
</type>
</config>
O nome do arquivo é relativo à raiz do Magento, então /var/log/vendor_shipping.log acaba no var/log da instalação, e não no /var/log do sistema. O argumento name é o channel, que aparece em cada linha de log e ajuda quando vários loggers gravam no mesmo lugar.
A classe em si continua completamente padrão:
<?php
declare(strict_types=1);
namespace Vendor\Shipping\Model;
use Magento\Framework\HTTP\Client\Curl;
use Psr\Log\LoggerInterface;
class RateClient
{
public function __construct(
private readonly Curl $curl,
private readonly LoggerInterface $logger
) {
}
public function fetchRates(string $postcode, float $weight): array
{
$this->logger->info('Requesting rates', ['postcode' => $postcode, 'weight' => $weight]);
try {
$this->curl->setTimeout(5);
$this->curl->get('https://rates.example.com/v1/quote?' . http_build_query([
'postcode' => $postcode,
'weight' => $weight,
]));
} catch (\Throwable $e) {
$this->logger->error('Rates request failed', ['exception' => $e]);
return [];
}
if ($this->curl->getStatus() !== 200) {
$this->logger->warning('Unexpected rates response', [
'status' => $this->curl->getStatus(),
'body' => mb_substr($this->curl->getBody(), 0, 500),
]);
return [];
}
return json_decode($this->curl->getBody(), true) ?: [];
}
}
Ela depende da LoggerInterface, como qualquer classe deve. Em qual arquivo ela grava é uma decisão de DI. Em um teste unitário você passa um logger mock e nada mais muda. As ideias por trás disso estão explicadas em dependency injection e virtual types.
Depois de mudar o di.xml, limpe os metadados gerados. Em modo developer, um cache:flush geralmente basta. Em modo production, rode setup:di:compile como parte do deploy.
Conferindo se funciona
Se nada aparece no arquivo novo, confira nesta ordem. Primeiro, se a classe realmente recebe o seu logger: um plugin ou uma preference na classe pode criá-la por outro caminho, e um erro de digitação no nome da classe no nó type falha em silêncio. Segundo, se o código gerado foi atualizado depois da mudança no di.xml. Terceiro, se o usuário do PHP consegue escrever em var/log. Um jeito rápido de confirmar a ligação é chamar a classe por um pequeno comando CLI, rodar e olhar o arquivo.
Compartilhando o logger entre classes
Adicione um bloco type para cada classe que deve usar o log do módulo. Se muitas classes precisam dele, essa lista fica longa, e tudo bem. É explícita e fácil de buscar. Evite uma preference para LoggerInterface restrita ao seu módulo, já que preferences são globais e redirecionariam os logs de todos os módulos.
Uma classe de handler quando você precisa de mais
O virtual type não consegue mudar o nível mínimo, porque o Base o define em uma propriedade protected. Se você quer um arquivo que registre só warnings e acima, ou quer um formatter diferente, crie uma pequena classe de handler:
<?php
declare(strict_types=1);
namespace Vendor\Shipping\Logger;
use Magento\Framework\Logger\Handler\Base;
use Monolog\Logger;
class WarningHandler extends Base
{
protected $fileName = '/var/log/vendor_shipping_errors.log';
protected $loggerType = Logger::WARNING;
}
Depois use-a como um item extra no array handlers do virtual type do seu logger. Um registro vai para todo handler que aceita o nível dele, então você tem o log completo em um arquivo e só os problemas em outro.
O Magento 2.4.8 traz o Monolog 3, em que os níveis também estão disponíveis como o enum Monolog\Level. As constantes de nível da classe Logger continuam existindo por compatibilidade e são as que os handlers do core usam, então a classe acima funciona. Confira os handlers do core em lib/internal/Magento/Framework/Logger/Handler da sua versão se for escrever algo mais avançado.
Níveis de log na prática
- debug: traces detalhados para desenvolvimento. Barulhento, não deve ficar ligado em produção por muito tempo.
- info: eventos normais que vale guardar, como um pedido exportado com o ID externo.
- warning: algo inesperado que o código tratou, como um retry ou um campo opcional ausente.
- error: uma operação falhou e precisa de atenção.
- critical e acima: o módulo ou a loja não consegue funcionar. Combine com alertas.
O handler padrão do debug.log só grava quando o debug logging está habilitado, o que vem desligado por padrão em modo production. Dá para ligar com bin/magento setup:config:set --enable-debug-logging=true, mas em produção um arquivo dedicado em nível info é mais fácil de administrar.
Contexto, não montagem de string
Passe os dados como array de contexto em vez de concatená-los na mensagem:
$this->logger->info('Order exported', ['increment_id' => $incrementId, 'erp_id' => $erpId]);
A mensagem fica constante, então você consegue buscar e contar. O contexto é gravado como JSON depois da mensagem pelo line formatter padrão, o que deixa os valores legíveis e fáceis de achar com grep.
Para exceções, passe o objeto da exceção na chave exception. O Monolog então grava a classe, a mensagem, o arquivo, a linha e o trace. Com o system logger padrão, registros com uma exceção no contexto vão para o exception.log. Com o seu próprio logger eles vão para o seu arquivo, que geralmente é o que você quer em uma integração.
O que não logar
- Dados de cartão, CVV, senhas, tokens OAuth ou chaves de API. Mascare ou deixe de fora.
- Cadastros completos de clientes e endereços, a menos que haja uma necessidade clara e uma política de retenção.
- Corpos inteiros de request e response de APIs grandes em nível info. Registre um corpo truncado só nos erros.
Logs são copiados para backups, compartilhados em tickets e lidos por pessoas que não deveriam ver dados pessoais. Trate-os como dados que vão vazar um dia.
Rotação e tamanho
O Magento não faz rotação dos seus arquivos de log. Uma integração tagarela pode encher um disco em semanas. Use o logrotate do sistema:
/var/www/shop/shared/var/log/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
copytruncate
}
O copytruncate deixa o PHP continuar gravando no mesmo arquivo aberto. Ajuste o caminho para o seu layout. Em um deploy baseado em releases, os logs ficam na pasta var compartilhada.
Lendo logs durante o debug
Um arquivo dedicado facilita acompanhar uma integração em tempo real com tail -f var/log/vendor_shipping.log, ou filtrar por valores do contexto com grep. Quando os logs não bastam, percorra o código com um debugger. A configuração está em debug com Xdebug no Magento 2. Para integrações com sistemas externos, veja também padrões de integração com ERP, onde log e retries andam juntos.
FAQ
Como logo em um arquivo customizado no Magento 2 sem criar classes?
Declare um virtual type de Magento\Framework\Logger\Handler\Base com um argumento fileName, um virtual type de Magento\Framework\Logger\Monolog que o use e injete esse logger na sua classe pelo di.xml.
Por que minha mensagem de debug não está no debug.log?
O debug logging vem desabilitado por padrão em modo production. Dá para habilitar com setup:config:set --enable-debug-logging=true, mas em produção prefira gravar no seu próprio arquivo de log em nível info ou acima.
Posso usar o ObjectManager para criar um logger rapidamente?
Funciona, mas é o hábito errado. Injete Psr\Log\LoggerInterface no constructor e aponte para o seu logger no di.xml. A classe continua testável e o destino do log pode mudar sem mexer no código.
Para onde vão as exceções registradas com o logger padrão?
Com o system logger padrão, registros que carregam uma exceção no contexto são gravados em var/log/exception.log. Os outros erros vão para var/log/system.log.
Atendo como freelancer em Magento e Adobe Commerce, incluindo integrações que precisam de bom logging. Se você precisa de ajuda com isso, fale comigo.
Artigos relacionados
Magento 2: plugins, observers e preferences, quando usar cada um
Como plugins, observers e preferences funcionam no Magento 2.4.8, o que cada um não faz e um checklist para escolher o que sobrevive a upgrades e extensões.
Dependency injection e virtual types no Magento 2: guia prático
Guia prático de dependency injection no Magento 2: argumentos do di.xml, preferences, virtual types, escopo por area, shared instances e como fazer debug.
Erros comuns do Magento 2 e como corrigir
Correções para erros clássicos do Magento 2: Area code is not set, Class does not exist, Invalid Form Key, indexers travados, unserialize e 404 em estáticos.
Debugando o Magento 2 com Xdebug 3 e PhpStorm
Configure o Xdebug 3 para o Magento 2 com PhpStorm no Docker: php.ini, path mappings, debug de CLI e cron e a armadilha do full page cache.