Monitorando erros de JavaScript com window.onerror

O material, cuja tradução publicamos hoje, é dedicado ao tratamento de erros de JS usando window.onerror . Este é um evento especial do navegador que é acionado quando ocorrem erros não detectados. Aqui, falaremos sobre como detectar erros usando o onerror eventos onerror e como enviar informações sobre eles ao servidor do desenvolvedor do site. Esse manipulador pode ser usado como base do seu próprio sistema para coletar e analisar informações de erro. Além disso, é um dos mecanismos mais importantes usados ​​em bibliotecas orientadas a erros, como raven-js .

imagem

Escutando o evento window.onerror


Você pode ouvir o evento onerror atribuindo window.onerror função que desempenha o papel de um manipulador de erros:

 window.onerror = function(msg, url, lineNo, columnNo, error) { // ...   ... return false; } 

Essa função é chamada quando ocorre um erro; os seguintes argumentos são passados ​​para ela:

  • msg - mensagem de erro. Por exemplo, Uncaught ReferenceError: foo is not defined .
  • url - o endereço do script ou documento em que o erro ocorreu. Por exemplo, /dist/app.js .
  • lineNo - número da linha onde ocorreu o erro (se suportado).
  • columnNo - o número da coluna da linha (se suportado).
  • error - o objeto de error (se suportado).

Os quatro primeiros argumentos informam ao desenvolvedor qual script, em qual linha e em qual coluna dessa linha ocorreu um erro. O argumento final, um objeto do tipo Error , é talvez o mais importante de todos os argumentos. Vamos conversar sobre isso.

Objeto de erro e propriedade Error.prototype.stack


À primeira vista, o objeto Error não é nada de especial. Ele contém três propriedades bastante padrão - message , fileName e lineNumber . Esses dados, dadas as informações transmitidas ao manipulador de eventos window.onerror , podem ser considerados redundantes.

O valor real nesse caso é a propriedade não padrão Error.prototype.stack . Essa propriedade fornece acesso à pilha de chamadas (pilha de erros), permite descobrir o que estava acontecendo no programa no momento em que ocorreu o erro, cuja chamada de função precedeu sua aparência. O rastreamento da pilha de chamadas pode ser uma parte crítica do processo de depuração. E, apesar de a propriedade stack não ser padrão, ela está disponível em todos os navegadores modernos.

É assim que a propriedade stack do objeto de erro se parece no Chrome 46.

 "Error: foobar\n    at new bar (<anonymous>:241:11)\n    at foo (<anonymous>:245:5)\n at <anonymous>:250:5\n    at <anonymous>:251:3\n at <anonymous>:267:4\n at callFunction (<anonymous>:229:33)\n    at <anonymous>:239:23\n at <anonymous>:240:3\n at Object.InjectedScript.\_evaluateOn (<anonymous>:875:140)\n    at Object.InjectedScript.\_evaluateAndWrap (<anonymous>:808:34)" 

À nossa frente está uma string não formatada. Quando o conteúdo desta propriedade é apresentado neste formulário, é inconveniente trabalhar com ela. Veja como o mesmo ficará após a formatação.

 Error: foobar at new bar (<anonymous>:241:11) at foo (<anonymous>:245:5) at callFunction (<anonymous>:229:33) at Object.InjectedScript._evaluateOn (<anonymous>:875:140) at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34) 

Agora, após a formatação, a pilha de erros parece muito mais clara, imediatamente fica claro por que a propriedade da stack é muito importante ao depurar erros.

No entanto, aqui tudo não está indo bem. A propriedade de stack não é padronizada, é implementada de maneira diferente em navegadores diferentes. Aqui está, por exemplo, a aparência da pilha de erros no Internet Explorer 11.

 Error: foobar at bar (Unknown script code:2:5) at foo (Unknown script code:6:5) at Anonymous function (Unknown script code:11:5) at Anonymous function (Unknown script code:10:2) at Anonymous function (Unknown script code:1:73) 

Você pode ver, em comparação com o exemplo anterior, que aqui não apenas um formato diferente para representar quadros de pilha é usado, mas também que há menos dados para cada quadro. Por exemplo, o Chrome identifica instâncias do uso da new palavra-chave e fornece informações mais detalhadas sobre outros eventos (em particular, sobre chamadas de função. _evaluateOn e. _evaluateAndWrap ). Ao mesmo tempo, aqui comparamos apenas o que o IE e o Chrome divulgam. Outros navegadores usam suas próprias abordagens para exibir dados sobre a pilha e selecionar as informações incluídas nesses dados.

Para trazer tudo isso para uma aparência uniforme, você pode usar ferramentas de terceiros. Por exemplo, o raven-js usa o TraceKit para isso. Stacktrace.js e alguns outros projetos atendem ao mesmo objetivo.

Recursos do suporte window.onerror por vários navegadores


O evento windows.onerror existe nos navegadores há algum tempo. Em particular, ele pode ser encontrado no IE6 e no Firefox 2. O problema aqui é que todos os navegadores implementam o windows.onerror maneiras diferentes. Por exemplo, isso diz respeito ao número e estrutura dos argumentos transmitidos aos manipuladores deste evento.

Aqui está uma tabela que onerror argumentos passados ​​para o manipulador onerror nos principais navegadores.
Navegador
mensagem
url
lineNo
colNo
errorObj
Firefox
Existe
Existe
Existe
Existe
Existe
Chrome
Existe
Existe
Existe
Existe
Existe
Edge
Existe
Existe
Existe
Existe
Existe
IE 11
Existe
Existe
Existe
Existe
Existe
IE10
Existe
Existe
Existe
Existe
Não
IE 9.8
Existe
Existe
Existe
Não
Não
Safari 10 e superior
Existe
Existe
Existe
Existe
Existe
Safari 9
Existe
Existe
Existe
Existe
Não
Navegador Android 4.4
Existe
Existe
Existe
Existe
Não

Provavelmente, não é de surpreender que o Internet Explorer 8, 9 e 10 tenham suporte limitado ao onerror . No entanto, pode parecer incomum que no navegador Safari o suporte para o objeto de erro apareça apenas na 10ª versão, lançada em 2016. Além disso, existem dispositivos móveis herdados que usam o navegador Android padrão, que também não suporta o objeto de erro. Nas versões modernas do Android, este navegador foi substituído pelo Chrome Mobile.

Se não houver nenhum objeto de erro à nossa disposição, não haverá dados sobre o rastreamento da pilha. Isso significa que navegadores que não oferecem suporte ao objeto do erro não fornecem informações de pilha no script padrão para usar o manipulador onerror . E isso, como dissemos, é muito importante.

Desenvolvimento de polyfill para window.onerror usando a construção try / catch


Para obter informações sobre a pilha em navegadores que não oferecem suporte à passagem de um objeto de onerror para o manipulador onerror , você pode usar o seguinte truque. Você pode agrupar o código em uma construção try/catch e capturar os erros você mesmo. O objeto de erro resultante conterá, em todos os navegadores modernos, o que precisamos é da propriedade stack .
Dê uma olhada no código do método auxiliar invoke() , que chama o método especificado do objeto, passando a ele uma matriz de argumentos.

 function invoke(obj, method, args) { return obj[method].apply(this,args); } 

Veja como usá-lo.

 invoke(Math, 'max', [1,2]); //  2 

Aqui está o mesmo invoke() , mas agora a chamada do método é agrupada em try/catch , o que permite capturar possíveis erros.

 function invoke(obj, method, args) { try {   return obj[method].apply(this,args); } catch(e) {   captureError(e);//      throw e;//      } } invoke(Math,'highest',[1,2]); //  ,     Math.highest 

Obviamente, é muito caro adicionar essas estruturas manualmente a todos os locais onde elas possam ser necessárias. Essa tarefa pode ser simplificada criando uma função auxiliar universal.

 function wrapErrors(fn) { //      if(!fn.__wrapped__) {   fn.__wrapped__ = function() {     try{       return fn.apply(this,arguments);     }catch(e){       captureError(e);//          throw e;//          }   }; } return fn.__wrapped__; } var invoke = wrapErrors(function(obj, method, args) { returnobj[method].apply(this,args); }); invoke(Math,'highest',[1,2]);//,   Math.highest 

Como o JavaScript usa um modelo de execução de código de thread único, esse wrapper deve ser usado apenas com as chamadas de função que estão no início de novas pilhas. Não há necessidade de agrupar todas as chamadas de função nele.

Como resultado, verifica-se que essa função precisa ser usada nos seguintes locais:

  • Onde o aplicativo é iniciado (por exemplo, ao usar jQuery, na função $(document).ready )
  • Nos manipuladores de eventos (por exemplo, em addEventListener ou em construções no formato $.fn.click )
  • Nos retornos de chamada chamados por eventos de timer (por exemplo, é setTimeout ou requestAnimationFrame )

Aqui está um exemplo usando a função wrapErrors .

 $(wrapErrors(function () {//    doSynchronousStuff1();//     setTimeout(wrapErrors(function () {   doSynchronousStuff2();//      })); $('.foo').click(wrapErrors(function () {   doSynchronousStuff3();//     })); })); 

Essas construções podem ser adicionadas ao código você mesmo, mas isso é uma tarefa muito demorada. Como alternativa conveniente nessas situações, você pode considerar as bibliotecas para trabalhar com erros, que, por exemplo, possuem mecanismos que addEventListener e setTimeout ferramentas para addEventListener erros.

Erro na transferência para o servidor


Portanto, agora temos à nossa disposição meios de interceptar informações de erro usando o windows.onerror ou funções auxiliares baseadas em try/catch . Esses erros ocorrem no lado do cliente e, após sua interceptação, gostaríamos de lidar com eles e tomar medidas para eliminá-los. Para fazer isso, eles precisam ser transferidos para o nosso servidor. Para fazer isso, você precisa preparar um serviço da Web que receba informações sobre erros via HTTP e, de alguma forma, salvá-los para processamento adicional, digamos - gravaria em um arquivo de log ou banco de dados.

Se este serviço da web estiver localizado no mesmo domínio que o aplicativo da web, XMLHttpRequest será suficiente. O exemplo a seguir mostra como usar uma função para executar consultas AJAX do jQuery para transferir dados para um servidor.

 function captureError(ex){ var errorData = {   name:ex.name,// : ReferenceError   message:ex.line,// : x is undefined   url:document.location.href,   stack:ex.stack//   ; ,     ! }; $.post('/logger/js/',{   data:errorData }); } 

Lembre-se de que, se você precisar enviar solicitações entre domínios para enviar informações sobre erros ao servidor, será necessário dar suporte a essas solicitações.

Sumário


Você aprendeu o básico da criação de um serviço para capturar erros e enviar informações sobre eles ao servidor. Em particular, aqui examinamos os seguintes problemas:

  • Recursos do evento onerror e seu suporte em vários navegadores.
  • Usando o mecanismo try/catch para obter informações sobre a pilha de chamadas nos casos em que o onerror não oferece suporte ao trabalho com o objeto de erro.
  • Transfira os dados do erro para o servidor do desenvolvedor.

Tendo aprendido sobre como os mecanismos acima funcionam, você adquiriu conhecimentos básicos que lhe permitirão começar a criar seu próprio sistema para trabalhar com erros, esclarecendo detalhes adicionais durante o trabalho. Talvez esse cenário seja especialmente relevante para os casos em que se trata de um aplicativo em que, digamos, por motivos de segurança, não está planejado o uso de bibliotecas de terceiros. Se o seu aplicativo permitir o uso de código de terceiros, você poderá encontrar muito bem a ferramenta certa para monitorar erros de JS. Entre essas ferramentas estão Sentry , Rollbar , TrackJS e outros projetos similares.

Caros leitores! Quais ferramentas para monitorar erros de JS você usa?

Source: https://habr.com/ru/post/pt413173/


All Articles