Mecanismo de evento global de JavaScript

350

Me gustaría detectar cada error de función indefinido arrojado. ¿Existe una facilidad de manejo de errores global en JavaScript? El caso de uso es capturar llamadas de función desde flash que no están definidas.

Brian Tompsett - 汤 莱恩
fuente
¿Qué quieres hacer con un error una vez que lo detectas? ¿Solo necesita iniciar sesión para poder crear la función que falta o está buscando evitar que las excepciones rompan su código?
Dan Herbert
2
Me gustaría obtener el nombre de la función faltante llamada y en función de la presencia de alguna cadena llamar a mi propia función. Cualquier llamada a una función con la cadena 'close' llamaría a mi close (), por ejemplo. También me gustaría atrapar el error en ese punto.
2
exceptionsjs.com proporciona esta funcionalidad y se puede ajustar para detectar solo errores relacionados con una funcionalidad indefinida con su funcionalidad de "protección".
Steven Wexler

Respuestas:

193

¿Esto te ayuda a:

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

xxx();
</script>

Sin embargo, no estoy seguro de cómo maneja los errores de Flash ...

Actualización: no funciona en Opera, pero estoy pirateando Dragonfly en este momento para ver qué obtiene. La sugerencia sobre la piratería de Dragonfly provino de esta pregunta:

Mimic Window. onerror en Opera usando javascript

Ionuț G. Stan
fuente
3
Con la adición de msg, file_loc, line_no params, esto debería hacerlo por mí. ¡Gracias!
1
¿Entiendo correctamente que este código anula cualquier controlador de errores existente?
Mars Robertson el
@MarsRobertson No.
atilkan
@atilkan window.onerror = function() { alert(42) };ahora el código en la respuesta: window.onerror = function() { alert("Error caught"); };no anulado, todavía no estoy seguro ..
Mars Robertson
@MarsRobertson lo entiendo. Probablemente se sobrescribe. Sí, lo acaba de probar. Usar addEventListener sería mejor.
atilkan
646

Cómo detectar errores de Javascript no controlados

Asigne el window.onerrorevento a un controlador 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>

Como se comentó en el código, si el valor de retorno de window.onerrores trueentonces el navegador debería suprimir la visualización de un cuadro de diálogo de alerta.

¿Cuándo se activa el evento window.onerror?

En pocas palabras, el evento se genera cuando 1.) hay una excepción no detectada o 2.) se produce un error de tiempo de compilación.

excepciones no capturadas

  • lanzar "algunos mensajes"
  • call_something_undefined ();
  • cross_origin_iframe.contentWindow.document ;, una excepción de seguridad

Error de compilación

  • <script>{</script>
  • <script>for(;)</script>
  • <script>"oops</script>
  • setTimeout("{", 10);, intentará compilar el primer argumento como un script

Navegadores compatibles con window.onerror

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

Captura de pantalla:

Ejemplo del código onerror anterior en acción después de agregar esto a una página de prueba:

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

Alerta Javascript que muestra información de error detallada por el evento window.onerror

Ejemplo de informe de errores 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/

Referencias

Sam
fuente
3
Vale la pena mencionar que Firefox no devuelve el mensaje de error cuando throwse hace manualmente. stackoverflow.com/questions/15036165/…
Sebas
2
Gran respuesta. Puede implementar el "Informe de este error a través de ajax" con un paquete como JSNLog, que realiza el ajax y el registro del lado del servidor por usted.
user1147862
44
Además de esta respuesta, agregué el objeto err para poder obtener el seguimiento de la pila. stackoverflow.com/a/20972210/511438 . Ahora puedo desarrollar con más comentarios porque mis errores de desarrollo aparecen como un cuadro en la parte superior de la página (tal como lo he creado).
Valamas
¿No sería mejor empacar su implementación de onerror en un bloque try-catch (ignore), evitando que su onerror () arroje aún más errores? (no es que sea el caso aquí, pero solo para estar seguro)
Pieter De Bie
1
@ super1ha1 ¿Puede proporcionar un jsfiddle? No creo que los navegadores introduzcan un cambio importante donde el controlador de eventos onerror deje de disparar eventos. Puede probar este jsfiddle para ver el controlador onerror funcionando: jsfiddle.net/nzfvm44d Esto todavía funciona para mí en la versión 62.0.3202.94 de Chrome (compilación oficial) (64 bits).
Sam
38

manejo sofisticado de errores

Si su manejo de errores es muy sofisticado y, por lo tanto, puede arrojar un error en sí mismo, es útil agregar un indicador que indique si ya está en "errorHandling-Mode". Al igual que:

var appIsHandlingError = false;

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

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

De lo contrario, podría encontrarse en un bucle infinito.

SunnyRed
fuente
29
O una forma más segura sería utilizar try-catch alrededor del handleErrormétodo.
Aidiakapi
8
No puede usar try catch si tiene una llamada ansincrónica en su manejo de errores. Así que la solución sigue siendo una buena solución
Emrys Myrooin
@EmrysMyrooin Aún causará una sincronización o un bucle asíncrono por manejo de errores y probablemente se bloqueará sin información de pila utilizable.
inf3rno
1
Creo que la bandera debería reiniciarse en algún momento, ¿verdad? Creo que necesitamos un try / catch around si es grande if, y asegúrese de que la bandera se reinicie justo antes de abandonar el método onerror. De lo contrario, solo se manejará un error.
Seb
23

Pruebe Atatus, que proporciona seguimiento avanzado de errores y monitoreo de usuarios reales para aplicaciones web modernas.

https://www.atatus.com/

Permítanme explicar cómo obtener stacktraces que estén razonablemente completos en todos los navegadores.

Manejo de errores en JavaScript

Modern Chrome y Opera son totalmente compatibles con las especificaciones de borrador HTML 5 para ErrorEvent y window.onerror. En ambos navegadores, puede usar window.onerroro enlazar correctamente al evento 'error':

// 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);
})

Lamentablemente, Firefox, Safari e IE todavía existen y tenemos que apoyarlos también. Como el stacktrace no está disponible window.onerror, tenemos que trabajar un poco más.

Resulta que lo único que podemos hacer para obtener stacktraces de los errores es envolver todo nuestro código en un try{ }catch(e){ }bloque y luego mirarlo e.stack. Podemos hacer el proceso algo más fácil con una función llamada wrap que toma una función y devuelve una nueva función con un buen manejo de errores.

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;
};

Esto funciona. Cualquier función que ajuste manualmente tendrá un buen manejo de errores, pero resulta que en realidad podemos hacerlo automáticamente en la mayoría de los casos.

Al cambiar la definición global de addEventListenerpara que envuelva automáticamente la devolución de llamada, podemos insertar automáticamente try{ }catch(e){ }alrededor de la mayoría del código. Esto permite que el código existente continúe funcionando, pero agrega un seguimiento de excepciones de alta calidad.

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

También debemos asegurarnos de que removeEventListenersiga funcionando. Por el momento no lo hará porque el argumento de addEventListenerha cambiado. De nuevo, solo tenemos que arreglar esto en el prototypeobjeto:

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

Transmita datos de error a su backend

Puede enviar datos de error utilizando la etiqueta de imagen de la siguiente manera

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;
}

Descargo de responsabilidad: soy desarrollador web en https://www.atatus.com/ .

Fizer Khan
fuente
¿Qué es yourserver.com/jserror ? REST, Servicio web, Servicio Wcf ? ¿Algún simple sobre el backend?
Kiquenet
Si desea enviar errores js desde el navegador del usuario a su servidor. tienes que escribir tu backend ( http://yourserver.com) para recibir y almacenar. Si elige atatus.com , no necesita hacer nada. Solo incluye dos líneas de script en tu página.
Fizer Khan
18

Parece que window.onerrorno proporciona acceso a todos los errores posibles. Específicamente ignora:

  1. <img> errores de carga (respuesta> = 400).
  2. <script> errores de carga (respuesta> = 400).
  3. errores globales si tiene muchas otras bibliotecas en su aplicación que también manipulan window.onerrorde manera desconocida (jquery, angular, etc.).
  4. probablemente muchos casos con los que no me he encontrado después de explorar esto ahora (iframes, desbordamiento de pila, etc.).

Aquí está el comienzo de un script que detecta muchos de estos errores, para que pueda agregar una depuración más sólida a su aplicación durante el desarrollo.

(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);
}

})();

Se podría usar así:

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

El script completo tiene una implementación predeterminada que intenta imprimir una versión de "visualización" semi legible de la entidad / error que recibe. Se puede usar como inspiración para un controlador de errores específico de la aplicación. La implementación predeterminada también mantiene una referencia a las últimas 100 entidades de error, por lo que puede inspeccionarlas en la consola web después de que ocurran como:

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

Nota: Esto funciona anulando métodos en varios navegadores / constructores nativos. Esto puede tener efectos secundarios no deseados. Sin embargo, ha sido útil usar durante el desarrollo, para descubrir dónde se producen los errores, para enviar registros a servicios como NewRelic o Sentry durante el desarrollo para que podamos medir los errores durante el desarrollo y en la puesta en escena para que podamos depurar lo que está sucediendo en Un nivel más profundo. Luego se puede apagar en producción.

Espero que esto ayude.

Lance Pollard
fuente
1
Al parecer, las imágenes desencadenan el evento de error: 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
fuente
6

También se debe preservar la devolución de llamada onerror previamente asociada

<script type="text/javascript">

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

</script>
Apoorv Saxena
fuente
3

Si desea una forma unificada de manejar tanto los errores no detectados como los rechazos de promesas no controlados, puede echar un vistazo a la biblioteca no 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>

Escucha ventana. rechazo no controlado además de window.onerror.

Aleksandr Oleynikov
fuente
2
Lance Pollard en la respuesta anterior mencionó algunos errores, que el error normal no maneja. ¿Tu biblioteca los maneja? Si alguno de ellos, especifique cuál?
Michael Freidgeim
@ MiFreidgeimSO-stopbeingevil Acabo de comprobar el código fuente, desafortunadamente no. Aleksandr Oleynikov: Sería increíble si pudieras apoyar esto. Luego convertiré mi voto a
favor
2

Recomendaría probar Trackjs .

Es un error al iniciar sesión como un servicio.

Es increíblemente simple de configurar. Simplemente agregue una línea <script> a cada página y listo. Esto también significa que será increíblemente fácil de eliminar si decides que no te gusta.

Hay otros servicios como Sentry (que es de código abierto si puede alojar su propio servidor), pero no hace lo que hace Trackjs. Trackjs registra la interacción del usuario entre su navegador y su servidor web para que pueda rastrear los pasos del usuario que condujeron al error, en lugar de solo una referencia de archivo y número de línea (y tal vez el seguimiento de la pila).

kane
fuente
2
TrackJS ya no parece tener un nivel gratuito , aunque hay una versión de prueba gratuita.
pkaeding
Esto está bien para rastrear y recibir alertas de errores, pero realmente no resuelve la parte de manejo. Idealmente, creo que el autor de la pregunta está buscando una forma de manejar estos errores para que el resto del código aún se ejecute.
wgp
¿Qué sucede si detecta el evento de error y agrega una llamada xhr al registrador con el seguimiento de la pila y el estado de la aplicación? ¿Cómo es mejor trackjs?
inf3rno
2
@ inf3rno es más que un seguimiento de la pila, que solo rastrea el comienzo del error. TrackJS rastreará toda la sesión del usuario para que pueda ver lo que condujo a ella. Aquí hay una captura de pantalla como ejemplo trackjs.com/assets/images/screenshot.png
kane
1

Escuchas el evento onerror asignando una función 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
fuente