Monitorando erros em produção
LidoEsta aula ensina como monitorar erros em produção em aplicações JavaScript, cobrindo os eventos window.onerror e unhandledrejection, além de uma visão geral de ferramentas populares. Você aprenderá a capturar erros globais, tratar rejeições de promises não tratadas e integrar soluções como Sentry, LogRocket e Bugsnag.
Monitorar erros em produção é essencial para manter a qualidade de qualquer aplicação web. Diferente do ambiente de desenvolvimento, onde temos acesso ao console e podemos reproduzir erros facilmente, em produção os usuários encontram problemas que não conseguimos reproduzir localmente. Sem uma estratégia de monitoramento, erros passam despercebidos, levando a falhas silenciosas, perda de dados e experiência ruim. Nesta aula, vamos explorar técnicas para capturar erros globais em JavaScript, incluindo o evento window.onerror e unhandledrejection, e discutir ferramentas que facilitam o rastreamento e a análise de erros.
Vamos começar entendendo como o JavaScript lida com erros e como podemos interceptá-los antes que cheguem ao usuário. Em seguida, mergulharemos em exemplos práticos e boas práticas para implementar um monitoramento eficaz.
window.onerror
O evento window.onerror é um dos mecanismos mais antigos para capturar erros não tratados em JavaScript. Ele é disparado sempre que uma exceção não é capturada por nenhum bloco try/catch e propaga até o escopo global. A assinatura da função de callback recebe vários parâmetros que fornecem informações detalhadas sobre o erro.
Ao utilizar window.onerror, você pode registrar o erro em um serviço de monitoramento ou enviá-lo para um endpoint próprio. É importante notar que erros de sintaxe não são capturados por esse evento, pois eles ocorrem antes da execução do script. Além disso, erros em scripts de outros domínios (cross-origin) podem ser mascarados como 'Script error.' a menos que você configure o atributo crossorigin nos scripts e os servidores enviem os cabeçalhos CORS adequados.
// Exemplo de uso de window.onerror
window.onerror = function(message, source, lineno, colno, error) {
console.error('Erro capturado:', { message, source, lineno, colno, error });
// Enviar para serviço de monitoramento
sendErrorToMonitoring({ message, source, lineno, colno, error });
return true; // evita que o erro seja exibido no console padrão
};
// Simulando um erro não tratado
throw new Error('Este erro será capturado por window.onerror');No exemplo acima, a função sendErrorToMonitoring seria uma função que envia os dados para um backend ou serviço de terceiros. O retorno true indica que o erro foi tratado, impedindo que o console padrão exiba o erro (embora em alguns navegadores isso não funcione mais).
Outra forma de usar window.onerror é atribuí-lo via propriedade ou usar addEventListener. A diferença é que com addEventListener você pode ter múltiplos ouvintes, enquanto a propriedade sobrescreve qualquer ouvinte anterior.
// Usando addEventListener
window.addEventListener('error', (event) => {
const { message, filename, lineno, colno, error } = event;
console.log('Erro capturado via addEventListener');
// ...
});É importante notar que o evento error também é disparado para erros de recursos (como imagens ou scripts) que falham ao carregar. Para distinguir entre erros de execução e erros de recurso, você pode verificar se o alvo do evento é um elemento HTMLScriptElement, HTMLImageElement, etc. Erros de recurso não têm a propriedade error preenchida.
window.addEventListener('error', (event) => {
if (event.target) {
// Erro de recurso: evento de carregamento falhou
console.log('Erro de recurso:', event.target.src || event.target.href);
} else {
// Erro de execução
console.log('Erro de execução:', event.message);
}
});Na prática, é comum combinar window.onerror com outras estratégias, como capturar erros em promises e erros assíncronos, que veremos a seguir.
unhandledrejection
Promises são amplamente utilizadas em JavaScript moderno para lidar com operações assíncronas. Quando uma promise é rejeitada e não há um catch ou segundo argumento para then, o erro pode passar despercebido. O evento unhandledrejection é disparado sempre que uma promise é rejeitada sem um tratador de rejeição. Isso permite capturar esses erros e tratá-los de forma centralizada.
O evento unhandledrejection fornece um objeto PromiseRejectionEvent com duas propriedades principais: promise (a promise que foi rejeitada) e reason (o valor da rejeição, geralmente um erro). É importante notar que esse evento não é disparado para rejeições que são tratadas posteriormente (por exemplo, se você adicionar um catch antes do final da microtask).
// Capturando rejeições não tratadas
window.addEventListener('unhandledrejection', (event) => {
console.warn('Rejeição não tratada:', event.reason);
// Enviar para monitoramento
sendErrorToMonitoring(event.reason);
event.preventDefault(); // evita que o erro seja logado no console como erro
});
// Simulando uma promise rejeitada sem catch
Promise.reject(new Error('Falha assíncrona'));O método event.preventDefault() impede que o navegador exiba o erro no console (em alguns navegadores, isso pode não funcionar). É uma boa prática chamá-lo se você já está tratando o erro.
Uma nuance importante é que o evento unhandledrejection é disparado após a microtask, então se você adicionar um catch no próximo tick (por exemplo, usando setTimeout), o evento pode não ser disparado. Para evitar falsos positivos, você pode usar a propriedade event.promise para verificar se a promise ainda está pendente.
// Exemplo mais robusto
window.addEventListener('unhandledrejection', (event) => {
// Verifica se a promise ainda não foi tratada
if (event.promise && event.promise.status === 'pending') {
console.log('Rejeição não tratada:', event.reason);
event.preventDefault();
}
});Além de capturar rejeições não tratadas, você também pode usar o evento rejectionhandled, que é disparado quando uma rejeição que antes era não tratada recebe um tratador posteriormente. Isso é útil para rastrear erros que foram temporariamente não tratados.
window.addEventListener('rejectionhandled', (event) => {
console.log('Rejeição tratada tardiamente:', event.reason);
});Combinando window.onerror e unhandledrejection, você pode cobrir a maioria dos erros não capturados em sua aplicação. No entanto, é importante também capturar erros em chamadas assíncronas antigas, como callbacks, que não são cobertas por esses eventos. Nesse caso, você pode envolver suas funções com try/catch ou usar utilitários de captura.
Visão geral de ferramentas
Implementar um monitoramento manual é possível, mas em aplicações reais é mais eficiente usar ferramentas especializadas que oferecem coleta, agregação, alertas e análise de erros. Essas ferramentas geralmente fornecem SDKs que integram facilmente com seu código e capturam erros automaticamente, além de oferecerem recursos como mapas de origem (source maps) para depurar código minificado.
Algumas das ferramentas mais populares são:
- Sentry: Uma das mais usadas, oferece rastreamento de erros em tempo real, suporte a várias linguagens e integração com frameworks. O SDK do Sentry para JavaScript captura exceções, promises rejeitadas e erros de recursos automaticamente.
- LogRocket: Focada em gravação de sessões, permite ver o que o usuário fez antes do erro, incluindo logs, rede e estado da aplicação. Útil para reproduzir problemas.
- Bugsnag: Oferece monitoramento de erros e estabilidade, com alertas e relatórios de impacto. Possui SDK para JavaScript e integrações com frameworks populares.
- Rollbar: Similar ao Sentry, com foco em deploy e versionamento, permitindo associar erros a releases específicos.
- TrackJS: Ferramenta leve, focada em capturar erros no navegador, com análise de pilha e contexto.
Para usar uma ferramenta como o Sentry, você normalmente instala o pacote via npm e inicializa com seu DSN (Data Source Name). Exemplo:
// Instalar: npm install @sentry/browser
import * as Sentry from "@sentry/browser";
Sentry.init({
dsn: "https://seu-dsn@sentry.io/seu-projeto",
environment: "production",
release: "meu-projeto@1.0.0",
});
// A partir daqui, erros são capturados automaticamente
// Você também pode capturar manualmente:
try {
// código que pode lançar erro
} catch (error) {
Sentry.captureException(error);
}Além de capturar erros, essas ferramentas geralmente permitem registrar eventos personalizados, breadcrumbs (passos do usuário) e até mesmo enviar informações de contexto, como usuário autenticado, para ajudar na depuração.
É importante lembrar que, ao usar um serviço de terceiros, você deve considerar a privacidade e a segurança dos dados. Evite enviar informações sensíveis, como senhas ou tokens, nos relatórios de erro. Configure a ferramenta para mascarar dados confidenciais.
Boas práticas e observações finais
Para um monitoramento eficaz, considere as seguintes práticas:
- Capture erros tanto em
window.onerrorquanto emunhandledrejectionpara cobrir diferentes tipos de falhas. - Envie informações contextuais, como URL, user agent, estado da aplicação e stack trace, para facilitar a reprodução.
- Use source maps em produção para que os stack traces apontem para o código original, não o minificado.
- Defina alertas para erros críticos, mas evite sobrecarga de notificações.
- Teste seu sistema de monitoramento em produção, simulando erros para garantir que os eventos estão sendo capturados corretamente.
- Lembre-se de que nem todos os erros são bugs: erros de rede, por exemplo, podem ser temporários. Considere agrupar erros semelhantes.
Monitorar erros em produção é um processo contínuo. As ferramentas e técnicas apresentadas nesta aula fornecem a base para identificar e corrigir problemas antes que afetem muitos usuários.
Referências
- MDN: window.onerror
- MDN: unhandledrejection event
- Sentry JavaScript Documentation
- LogRocket Documentation
- Bugsnag JavaScript Documentation
- Rollbar JavaScript Documentation
- TrackJS Documentation
Exercícios
Implemente um monitoramento simples usando
window.onerrorque exiba a mensagem do erro e a linha em que ocorreu. Teste com um erro intencional.✓ Resposta:window.onerror = function(message, source, lineno, colno, error) { console.log(`Erro: ${message} na linha ${lineno} do arquivo ${source}`); }; throw new Error('Erro de teste');Crie um evento
unhandledrejectionque envie a razão da rejeição para um serviço de log fictício (apenas exiba no console). Teste com uma promise rejeitada.✓ Resposta:window.addEventListener('unhandledrejection', (event) => { console.log('Rejeição não tratada:', event.reason); event.preventDefault(); }); Promise.reject(new Error('Falha na promise'));Diferencie erros de execução de erros de recurso no evento
error. Crie um manipulador que logue o tipo de erro.✓ Resposta:window.addEventListener('error', (event) => { if (event.target) { console.log('Erro de recurso:', event.target.src || event.target.href); } else { console.log('Erro de execução:', event.message); } }); // Teste: adicione uma imagem que não existe no HTML // <img src="nao-existe.png">Explique por que
window.onerrornão captura erros de promises e como ounhandledrejectionresolve isso. Dê um exemplo.✓ Resposta:window.onerroré acionado apenas para exceções síncronas não tratadas. Erros em promises são tratados pelo mecanismo de microtasks, e rejeições não tratadas não são lançadas como exceções no escopo global. O eventounhandledrejectionfoi criado para capturar essas rejeições. Exemplo:// window.onerror não captura Promise.reject(new Error('Erro assíncrono')); // unhandledrejection captura window.addEventListener('unhandledrejection', (event) => console.log(event.reason));Pesquise sobre uma ferramenta de monitoramento (ex.: Sentry) e descreva como você integraria em um projeto JavaScript. Inclua o código de inicialização.
✓ Resposta:Exemplo com Sentry:
// npm install @sentry/browser import * as Sentry from "@sentry/browser"; Sentry.init({ dsn: "https://example@sentry.io/12345", environment: "production", release: "v1.0.0", }); // Erros não tratados são capturados automaticamente. // Para capturar manualmente: try { // código propenso a erro } catch (error) { Sentry.captureException(error); }