Mecanismo de evento global JavaScript

350

Eu gostaria de pegar todos os erros de função indefinidos lançados. Existe um recurso global de tratamento de erros em JavaScript? O caso de uso está capturando chamadas de função do flash que não estão definidas.

Brian Tompsett - 汤 莱恩
fonte
O que você deseja fazer com um erro depois de detectá-lo? Você só precisa registrá-lo para criar a função que está faltando ou deseja impedir que as exceções quebrem seu código?
Dan Herbert
2
Gostaria de obter o nome da função ausente chamado e com base na presença de alguma string, chamar minha própria função. Qualquer chamada para uma função com a string 'close' chamaria meu close () por exemplo. Eu também gostaria de capturar o erro nesse momento.
2
O exceptionsjs.com fornece essa funcionalidade e pode ser personalizado para capturar apenas erros relacionados à funcionalidade indefinida com sua funcionalidade "guarda".
Steven Wexler

Respostas:

192

Isso ajuda você a:

<script type="text/javascript">
window.onerror = function() {
    alert("Error caught");
};

xxx();
</script>

Não tenho certeza de como ele lida com erros do Flash ...

Atualização: não funciona no Opera, mas estou hackeando o Dragonfly agora para ver o que acontece. A sugestão sobre hackers Dragonfly veio desta pergunta:

Imitar janela. onerror no Opera usando javascript

Ionuț G. Stan
fonte
3
Com a adição dos parâmetros msg, file_loc, line_no, isso deve ser feito por mim. Obrigado!
11
Entendo corretamente que esse código substitui qualquer manipulador de erros existente?
Mars Robertson
@MarsRobertson No.
atilkan
@atilkan window.onerror = function() { alert(42) };agora o código na resposta: window.onerror = function() { alert("Error caught"); };não anulado, eu ainda não tenho certeza ..
Mars Robertson
@MarsRobertson eu entendo. Provavelmente substitui. Sim, apenas testado. Usar addEventListener seria melhor.
precisa saber é o seguinte
646

Como capturar erros de Javascript não manipulados

Atribua o window.onerrorevento a um manipulador de eventos como:

<script type="text/javascript">
window.onerror = function(msg, url, line, col, error) {
   // Note that col & error are new to the HTML 5 spec and may not be 
   // supported in every browser.  It worked for me in Chrome.
   var extra = !col ? '' : '\ncolumn: ' + col;
   extra += !error ? '' : '\nerror: ' + error;

   // You can view the information in an alert to see things working like this:
   alert("Error: " + msg + "\nurl: " + url + "\nline: " + line + extra);

   // TODO: Report this error via ajax so you can keep track
   //       of what pages have JS issues

   var suppressErrorAlert = true;
   // If you return true, then error alerts (like in older versions of 
   // Internet Explorer) will be suppressed.
   return suppressErrorAlert;
};
</script>

Conforme comentado no código, se o valor de retorno window.onerrorfor for true, o navegador deverá suprimir a exibição de uma caixa de diálogo de alerta.

Quando o evento window.onerror é acionado?

Em poucas palavras, o evento é gerado quando 1.) há uma exceção não capturada ou 2.) ocorre um erro no tempo de compilação.

exceções não capturadas

  • jogue "algumas mensagens"
  • call_something_undefined ();
  • cross_origin_iframe.contentWindow.document ;, uma exceção de segurança

erro de compilação

  • <script>{</script>
  • <script>for(;)</script>
  • <script>"oops</script>
  • setTimeout("{", 10);, ele tentará compilar o primeiro argumento como um script

Navegadores que suportam window.onerror

  • Chrome 13 ou superior
  • Firefox 6.0 ou superior
  • Internet Explorer 5.5 ou superior
  • Opera 11.60+
  • Safari 5.1+

Captura de tela:

Exemplo do código onerror acima em ação depois de adicioná-lo a uma página de teste:

<script type="text/javascript">
call_something_undefined();
</script>

Alerta Javascript mostrando informações de erro detalhadas pelo evento window.onerror

Exemplo para Relatório de Erros AJAX

var error_data = {
    url: document.location.href,
};

if(error != null) {
    error_data['name'] = error.name; // e.g. ReferenceError
    error_data['message'] = error.line;
    error_data['stack'] = error.stack;
} else {
    error_data['msg'] = msg;
    error_data['filename'] = filename;
    error_data['line'] = line;
    error_data['col'] = col;
}

var xhr = new XMLHttpRequest();

xhr.open('POST', '/ajax/log_javascript_error');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
    if (xhr.status === 200) {
        console.log('JS error logged');
    } else if (xhr.status !== 200) {
        console.error('Failed to log JS error.');
        console.error(xhr);
        console.error(xhr.status);
        console.error(xhr.responseText);
    }
};
xhr.send(JSON.stringify(error_data));

JSFiddle:

https://jsfiddle.net/nzfvm44d/

Referências:

Sam
fonte
3
vale a pena mencionar o firefox não devolve a mensagem de erro quando throwé feita manualmente. stackoverflow.com/questions/15036165/…
Sebas 30/03
2
Ótima resposta. Você pode implementar o "Relatar este erro via ajax" com um pacote como JSNLog, que faz o log do lado do servidor e do ajax para você.
user1147862
4
Além desta resposta, adicionei o objeto err para que eu pudesse obter o rastreamento da pilha. stackoverflow.com/a/20972210/511438 . Agora posso desenvolver com mais feedback, porque meus erros de desenvolvedor aparecem como uma caixa na parte superior da página (como eu o criei).
Valamas 23/06
Não seria melhor compactar sua implementação de onerror em um bloco try-catch (ignore), impedindo que seu onerror () gere ainda mais erros? (não que é o caso aqui, mas só para ter certeza)
Pieter De Bie
11
@ super1ha1 Você pode fornecer um jsfiddle? Eu não acho que os navegadores introduziriam uma mudança de quebra, onde o manipulador de eventos onerror para de disparar eventos. Você pode tentar este jsfiddle para ver o manipulador onerror funcionando: jsfiddle.net/nzfvm44d Isso ainda funciona para mim na versão 62.0.3202.94 do Chrome (versão oficial) (64 bits).
Sam
38

tratamento sofisticado de erros

Se o tratamento de erros for muito sofisticado e, portanto, puder gerar um erro, é útil adicionar um sinalizador indicando se você já está no "errorHandling-Mode". Igual a:

var appIsHandlingError = false;

window.onerror = function() {
    if (!appIsHandlingError) {
        appIsHandlingError = true;
        handleError();
    }
};

function handleError() {
    // graceful error handling
    // if successful: appIsHandlingError = false;
}

Caso contrário, você poderá se encontrar em um loop infinito.

SunnyRed
fonte
29
Ou uma maneira mais à prova de falhas seria usar o try-catch em torno do handleErrormétodo.
Aidiakapi
8
Você não pode usar o try catch se tiver uma chamada assíncrona no tratamento de erros. Então solução que ele de permanecer solução e boa
Emrys Myrooin
@EmrysMyrooin Ainda assim, causará uma sincronização ou um loop assíncrono ao lidar com erros e provavelmente travará sem informações de pilha utilizáveis.
Inf3rno
11
Eu acho que a bandeira deve ser redefinida em algum momento, certo? Acredito que precisamos de uma tentativa / captura se for grande se, e certifique-se de que o sinalizador seja redefinido logo antes de sair do método onerror. Caso contrário, apenas um erro será tratado.
Seb
23

Experimente o Atatus, que fornece rastreamento avançado de erros e monitoramento real de usuários para aplicativos da web modernos.

https://www.atatus.com/

Deixe-me explicar como obter rastreamentos de pilha razoavelmente completos em todos os navegadores.

Tratamento de erros em JavaScript

O Chrome e o Opera modernos são totalmente compatíveis com as especificações de rascunho do HTML 5 para ErrorEvent e window.onerror. Nos dois navegadores, você pode usar window.onerrorou vincular o evento 'error' corretamente:

// Only Chrome & Opera pass the error object.
window.onerror = function (message, file, line, col, error) {
    console.log(message, "from", error.stack);
    // You can send data to your server
    // sendError(data);
};
// Only Chrome & Opera have an error attribute on the event.
window.addEventListener("error", function (e) {
    console.log(e.error.message, "from", e.error.stack);
    // You can send data to your server
    // sendError(data);
})

Infelizmente, o Firefox, o Safari e o IE ainda estão por aí e também precisamos apoiá-los. Como o stacktrace não está disponível window.onerror, precisamos trabalhar um pouco mais.

Acontece que a única coisa que podemos fazer para obter rastreamentos de pilha de erros é agrupar todo o nosso código em um try{ }catch(e){ }bloco e depois examinar e.stack. Podemos tornar o processo um pouco mais fácil com uma função chamada wrap que pega uma função e retorna uma nova função com boa manipulação de erros.

function wrap(func) {
    // Ensure we only wrap the function once.
    if (!func._wrapped) {
        func._wrapped = function () {
            try{
                func.apply(this, arguments);
            } catch(e) {
                console.log(e.message, "from", e.stack);
                // You can send data to your server
                // sendError(data);
                throw e;
            }
        }
    }
    return func._wrapped;
};

Isso funciona. Qualquer função que você agrupar manualmente terá um bom tratamento de erros, mas na verdade podemos fazer isso automaticamente na maioria dos casos.

Alterando a definição global de addEventListenerpara que ele agrupe automaticamente o retorno de chamada, podemos inserir automaticamente a try{ }catch(e){ }maioria dos códigos. Isso permite que o código existente continue a funcionar, mas adiciona o rastreamento de exceções de alta qualidade.

var addEventListener = window.EventTarget.prototype.addEventListener;
window.EventTarget.prototype.addEventListener = function (event, callback, bubble) {
    addEventListener.call(this, event, wrap(callback), bubble);
}

Também precisamos garantir que isso removeEventListenercontinue funcionando. No momento, não será porque o argumento para addEventListenerfoi alterado. Novamente, só precisamos consertar isso no prototypeobjeto:

var removeEventListener = window.EventTarget.prototype.removeEventListener;
window.EventTarget.prototype.removeEventListener = function (event, callback, bubble) {
    removeEventListener.call(this, event, callback._wrapped || callback, bubble);
}

Transmitir dados de erro para o seu back-end

Você pode enviar dados de erro usando a tag de imagem da seguinte maneira

function sendError(data) {
    var img = newImage(),
        src = 'http://yourserver.com/jserror&data=' + encodeURIComponent(JSON.stringify(data));

    img.crossOrigin = 'anonymous';
    img.onload = function success() {
        console.log('success', data);
    };
    img.onerror = img.onabort = function failure() {
        console.error('failure', data);
    };
    img.src = src;
}

Isenção de responsabilidade: Sou desenvolvedor da Web em https://www.atatus.com/ .

Fizer Khan
fonte
O que é yourserver.com/jserror ? REST, serviço da Web, serviço Wcf ? Qualquer simples sobre back-end?
Kiquenet 24/09/2015
Se você deseja enviar erros js do navegador do usuário para o seu servidor. você precisa escrever seu back-end ( http://yourserver.com) para receber e armazenar. Se você escolher atatus.com , não precisará fazer nada. Basta incluir duas linhas de script em sua página.
Fizer Khan
18

Parece que window.onerrornão fornece acesso a todos os erros possíveis. Ignora especificamente:

  1. <img> erros de carregamento (resposta> = 400).
  2. <script> erros de carregamento (resposta> = 400).
  3. erros globais se você tiver muitas outras bibliotecas em seu aplicativo e também manipular de window.onerrormaneira desconhecida (jquery, angular etc.).
  4. provavelmente muitos casos em que não encontrei depois de explorar isso agora (iframes, estouro de pilha etc.).

Aqui está o início de um script que captura muitos desses erros, para que você possa adicionar uma depuração mais robusta ao seu aplicativo durante o desenvolvimento.

(function(){

/**
 * Capture error data for debugging in web console.
 */

var captures = [];

/**
 * Wait until `window.onload`, so any external scripts
 * you might load have a chance to set their own error handlers,
 * which we don't want to override.
 */

window.addEventListener('load', onload);

/**
 * Custom global function to standardize 
 * window.onerror so it works like you'd think.
 *
 * @see http://www.quirksmode.org/dom/events/error.html
 */

window.onanyerror = window.onanyerror || onanyerrorx;

/**
 * Hook up all error handlers after window loads.
 */

function onload() {
  handleGlobal();
  handleXMLHttp();
  handleImage();
  handleScript();
  handleEvents();
}

/**
 * Handle global window events.
 */

function handleGlobal() {
  var onerrorx = window.onerror;
  window.addEventListener('error', onerror);

  function onerror(msg, url, line, col, error) {
    window.onanyerror.apply(this, arguments);
    if (onerrorx) return onerrorx.apply(null, arguments);
  }
}

/**
 * Handle ajax request errors.
 */

function handleXMLHttp() {
  var sendx = XMLHttpRequest.prototype.send;
  window.XMLHttpRequest.prototype.send = function(){
    handleAsync(this);
    return sendx.apply(this, arguments);
  };
}

/**
 * Handle image errors.
 */

function handleImage() {
  var ImageOriginal = window.Image;
  window.Image = ImageOverride;

  /**
   * New `Image` constructor. Might cause some problems,
   * but not sure yet. This is at least a start, and works on chrome.
   */

  function ImageOverride() {
    var img = new ImageOriginal;
    onnext(function(){ handleAsync(img); });
    return img;
  }
}

/**
 * Handle script errors.
 */

function handleScript() {
  var HTMLScriptElementOriginal = window.HTMLScriptElement;
  window.HTMLScriptElement = HTMLScriptElementOverride;

  /**
   * New `HTMLScriptElement` constructor.
   *
   * Allows us to globally override onload.
   * Not ideal to override stuff, but it helps with debugging.
   */

  function HTMLScriptElementOverride() {
    var script = new HTMLScriptElement;
    onnext(function(){ handleAsync(script); });
    return script;
  }
}

/**
 * Handle errors in events.
 *
 * @see http://stackoverflow.com/questions/951791/javascript-global-error-handling/31750604#31750604
 */

function handleEvents() {
  var addEventListenerx = window.EventTarget.prototype.addEventListener;
  window.EventTarget.prototype.addEventListener = addEventListener;
  var removeEventListenerx = window.EventTarget.prototype.removeEventListener;
  window.EventTarget.prototype.removeEventListener = removeEventListener;

  function addEventListener(event, handler, bubble) {
    var handlerx = wrap(handler);
    return addEventListenerx.call(this, event, handlerx, bubble);
  }

  function removeEventListener(event, handler, bubble) {
    handler = handler._witherror || handler;
    removeEventListenerx.call(this, event, handler, bubble);
  }

  function wrap(fn) {
    fn._witherror = witherror;

    function witherror() {
      try {
        fn.apply(this, arguments);
      } catch(e) {
        window.onanyerror.apply(this, e);
        throw e;
      }
    }
    return fn;
  }
}

/**
 * Handle image/ajax request errors generically.
 */

function handleAsync(obj) {
  var onerrorx = obj.onerror;
  obj.onerror = onerror;
  var onabortx = obj.onabort;
  obj.onabort = onabort;
  var onloadx = obj.onload;
  obj.onload = onload;

  /**
   * Handle `onerror`.
   */

  function onerror(error) {
    window.onanyerror.call(this, error);
    if (onerrorx) return onerrorx.apply(this, arguments);
  };

  /**
   * Handle `onabort`.
   */

  function onabort(error) {
    window.onanyerror.call(this, error);
    if (onabortx) return onabortx.apply(this, arguments);
  };

  /**
   * Handle `onload`.
   *
   * For images, you can get a 403 response error,
   * but this isn't triggered as a global on error.
   * This sort of standardizes it.
   *
   * "there is no way to get the HTTP status from a 
   * request made by an img tag in JavaScript."
   * @see http://stackoverflow.com/questions/8108636/how-to-get-http-status-code-of-img-tags/8108646#8108646
   */

  function onload(request) {
    if (request.status && request.status >= 400) {
      window.onanyerror.call(this, request);
    }
    if (onloadx) return onloadx.apply(this, arguments);
  }
}

/**
 * Generic error handler.
 *
 * This shows the basic implementation, 
 * which you could override in your app.
 */

function onanyerrorx(entity) {
  var display = entity;

  // ajax request
  if (entity instanceof XMLHttpRequest) {
    // 400: http://example.com/image.png
    display = entity.status + ' ' + entity.responseURL;
  } else if (entity instanceof Event) {
    // global window events, or image events
    var target = entity.currentTarget;
    display = target;
  } else {
    // not sure if there are others
  }

  capture(entity);
  console.log('[onanyerror]', display, entity);
}

/**
 * Capture stuff for debugging purposes.
 *
 * Keep them in memory so you can reference them
 * in the chrome debugger as `onanyerror0` up to `onanyerror99`.
 */

function capture(entity) {
  captures.push(entity);
  if (captures.length > 100) captures.unshift();

  // keep the last ones around
  var i = captures.length;
  while (--i) {
    var x = captures[i];
    window['onanyerror' + i] = x;
  }
}

/**
 * Wait til next code execution cycle as fast as possible.
 */

function onnext(fn) {
  setTimeout(fn, 0);
}

})();

Pode ser usado assim:

window.onanyerror = function(entity){
  console.log('some error', entity);
};

O script completo possui uma implementação padrão que tenta imprimir uma versão "display" semi-legível da entidade / erro que ele recebe. Pode ser usado como inspiração para um manipulador de erros específico do aplicativo. A implementação padrão também mantém uma referência às 100 últimas entidades de erro, para que você possa inspecioná-las no console da Web após elas ocorrerem como:

window.onanyerror0
window.onanyerror1
...
window.onanyerror99

Nota: Isso funciona substituindo métodos em vários navegadores / construtores nativos. Isso pode ter efeitos colaterais não intencionais. No entanto, tem sido útil usá-lo durante o desenvolvimento, descobrir onde os erros estão ocorrendo, enviar logs para serviços como NewRelic ou Sentry durante o desenvolvimento para que possamos medir erros durante o desenvolvimento e na preparação para que possamos depurar o que está acontecendo em um nível mais profundo. Em seguida, ele pode ser desativado na produção.

Espero que isto ajude.

Lance Pollard
fonte
11
Aparentemente imagens disparar o evento de erro: stackoverflow.com/a/18152753/607033
inf3rno
6
// display error messages for a page, but never more than 3 errors
window.onerror = function(msg, url, line) {
if (onerror.num++ < onerror.max) {
alert("ERROR: " + msg + "\n" + url + ":" + line);
return true;
}
}
onerror.max = 3;
onerror.num = 0;
GibboK
fonte
6

Deve-se preservar também o retorno de chamada onerror associado anteriormente

<script type="text/javascript">

(function() {
    var errorCallback = window.onerror;
    window.onerror = function () {
        // handle error condition
        errorCallback && errorCallback.apply(this, arguments);
    };
})();

</script>
Apoorv Saxena
fonte
3

Se você deseja uma maneira unificada de lidar com erros não capturados e rejeições de promessa não tratadas, consulte a biblioteca não capturada .

EDITAR

<script type="text/javascript" src=".../uncaught/lib/index.js"></script>

<script type="text/javascript">
    uncaught.start();
    uncaught.addListener(function (error) {
        console.log('Uncaught error or rejection: ', error.message);
    });
</script>

Ouve janela. rejeição não tratada além de window.onerror.

Aleksandr Oleynikov
fonte
2
Lance Pollard na resposta acima mencionou alguns erros, que o onerror normal não lida. Sua biblioteca lida com eles? Se alguns deles, especifique qual?
Michael Freidgeim
@ MiFreidgeimSO-stopbeingevil Acabei de verificar o código fonte - infelizmente não. Aleksandr Oleynikov: Seria incrível se você pudesse apoiar isso - então vou transformar meu
voto negativo
2

Eu recomendaria experimentar o Trackjs .

É log de erro como um serviço.

É incrivelmente simples de configurar. Basta adicionar uma linha <script> a cada página e pronto. Isso também significa que será incrivelmente simples de remover se você decidir que não gosta.

Existem outros serviços como o Sentry (que é de código aberto se você pode hospedar seu próprio servidor), mas não faz o que o Trackjs faz. O Trackjs registra a interação do usuário entre o navegador e o servidor da web, para que você possa rastrear as etapas do usuário que levaram ao erro, em vez de apenas uma referência de arquivo e número de linha (e talvez rastreamento de pilha).

kane
fonte
2
O TrackJS parece não ter mais um nível gratuito , embora exista um teste gratuito.
pkaeding
Isso é bom para rastrear e ser alertado sobre erros, mas na verdade não resolve a parte de manipulação. Idealmente, acho que o solicitante está procurando uma maneira de lidar com esses erros, para que o restante do código ainda seja executado.
Wgp
E se você capturar o evento de erro e adicionar uma chamada xhr ao criador de logs com o rastreamento de pilha e o estado do aplicativo? Como o trackjs é melhor?
Inf3rno
2
@ inf3rno é mais do que um rastreamento de pilha, que apenas rastreia o início do erro. O TrackJS rastreará toda a sessão do usuário para que você possa ver o que levou a ela. Aqui está uma imagem como um exemplo trackjs.com/assets/images/screenshot.png
kane
1

Você ouve o evento onerror atribuindo uma função a window.onerror:

 window.onerror = function (msg, url, lineNo, columnNo, error) {
        var string = msg.toLowerCase();
        var substring = "script error";
        if (string.indexOf(substring) > -1){
            alert('Script Error: See Browser Console for Detail');
        } else {
            alert(msg, url, lineNo, columnNo, error);
        }   
      return false; 
  };
Ali Azhar
fonte