Механизм глобальных событий JavaScript

350

Я хотел бы отлавливать каждую неопределенную ошибку в функции. Есть ли глобальная возможность обработки ошибок в JavaScript? Вариант использования - перехват вызовов функций из flash, которые не определены.

Брайан Томпсетт - 汤 莱恩
источник
Что вы хотите сделать с ошибкой после ее обнаружения? Вам просто нужно зарегистрировать его, чтобы создать отсутствующую функцию, или вы хотите, чтобы исключения не нарушали ваш код?
Дэн Герберт
2
Я хотел бы получить имя вызываемой отсутствующей функции, основываясь на наличии какой-либо строки вызова моей собственной функции. Например, любой вызов функции со строкой 'close' вызвал бы мой метод close (). Я также хотел бы поймать ошибку на этом этапе.
2
exceptionsjs.com предоставляет эту функциональность и может быть выделен для отслеживания ошибок, связанных с неопределенными функциями, с помощью функции «сторож».
Стивен Векслер

Ответы:

193

Помогает ли это вам:

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

xxx();
</script>

Я не уверен, как он обрабатывает ошибки Flash, хотя ...

Обновление: в Opera это не работает, но я сейчас взламываю Dragonfly, чтобы посмотреть, что получится. Предложение о взломе Dragonfly пришло из этого вопроса:

Мимика Окно. Ошибка в Opera с использованием JavaScript

Ionuț G. Stan
источник
3
С добавлением параметров msg, file_loc, line_no это должно сделать это для меня. Спасибо!
1
Правильно ли я понимаю, что этот код переопределяет любые существующие обработчики ошибок?
Марс Робертсон
@MarsRobertson Нет.
Атилкан
@atilkan window.onerror = function() { alert(42) };теперь код в ответе: window.onerror = function() { alert("Error caught"); };не переопределить, я все еще не уверен ..
Марс Робертсон
@MarsRobertson я понимаю. Это, вероятно, перезаписывает. Да, это так, только что проверил. Использование addEventListener было бы лучше.
atilkan
646

Как поймать необработанные ошибки Javascript

Назначьте window.onerrorсобытие обработчику событий, например:

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

Как прокомментировано в коде, если возвращаемое значение window.onerrorравно, trueто браузер должен отключить отображение диалогового окна с предупреждением.

Когда срабатывает событие window.onerror?

В двух словах, событие возникает, когда либо 1.) существует неперехваченное исключение, либо 2.) происходит ошибка времени компиляции.

неучтенные исключения

  • киньте "несколько сообщений"
  • call_something_undefined ();
  • cross_origin_iframe.contentWindow.document ;, исключение безопасности

ошибка компиляции

  • <script>{</script>
  • <script>for(;)</script>
  • <script>"oops</script>
  • setTimeout("{", 10);, он попытается скомпилировать первый аргумент как скрипт

Браузеры, поддерживающие window.onerror

  • Chrome 13+
  • Firefox 6.0+
  • Internet Explorer 5.5+
  • Опера 11.60+
  • Safari 5.1+

Скриншот:

Пример приведенного выше кода ошибки в действии после добавления этого на тестовую страницу:

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

Предупреждение Javascript, показывающее информацию об ошибке, детализированную событием window.onerror

Пример для сообщения об ошибках 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/

Ссылки:

Сэм
источник
3
Стоит отметить, что Firefox не возвращает сообщение об ошибке, когда throwсделано вручную. stackoverflow.com/questions/15036165/…
Себас
2
Отличный ответ. Вы можете реализовать «Сообщить об ошибке через ajax» с помощью пакета, такого как JSNLog, который выполняет ведение журналов на стороне ajax и на стороне сервера.
user1147862
4
В дополнение к этому ответу я добавил объект err, чтобы получить трассировку стека. stackoverflow.com/a/20972210/511438 . Теперь я могу разрабатывать больше отзывов, потому что мои ошибки разработчика отображаются в виде прямоугольника в верхней части страницы (как я его создал).
Валамас
Не лучше ли упаковать вашу реализацию onerror в блок try-catch (ignore), чтобы ваш onerror () выдавал еще больше ошибок? (не в этом дело, но просто чтобы быть уверенным)
Питер Де Би
1
@ super1ha1 Можете ли вы предоставить jsfiddle? Я не думаю, что браузеры внесут критические изменения, когда обработчик события onerror прекращает запуск событий. Вы можете попробовать этот jsfiddle, чтобы увидеть, как работает обработчик onerror: jsfiddle.net/nzfvm44d Это все еще работает для меня в Chrome версии 62.0.3202.94 (Официальная сборка) (64-битная версия).
Сэм
38

сложная обработка ошибок

Если ваша обработка ошибок очень сложна и, следовательно, может выдать саму ошибку, полезно добавить флаг, указывающий, что вы уже находитесь в «errorHandling-Mode». Вот так:

var appIsHandlingError = false;

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

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

В противном случае вы можете оказаться в бесконечном цикле.

SunnyRed
источник
29
Или более надежный способ - использовать try-catch вокруг handleErrorметода.
Aidiakapi
8
Вы не можете использовать try catch, если у вас есть асинхронный вызов при обработке ошибок. Таким образом, он остается хорошим решением
Эмрис Мирооин
@EmrysMyrooin Тем не менее это приведет к синхронизации или асинхронному циклу при обработке ошибок и, возможно, к сбою без полезной информации стека.
inf3rno
1
Я думаю, что флаг должен быть сброшен в какой-то момент, верно? Я считаю, что нам нужно попробовать / поймать, если большое, и убедиться, что флаг сброшен прямо перед выходом из метода onerror. В противном случае будет обработана только одна ошибка.
Себ
23

Попробуйте Atatus, который обеспечивает расширенное отслеживание ошибок и мониторинг реальных пользователей для современных веб-приложений.

https://www.atatus.com/

Позвольте мне объяснить, как получить трассировки стека, которые достаточно полны во всех браузерах.

Обработка ошибок в JavaScript

Современные Chrome и Opera полностью поддерживают проект спецификации HTML 5 для ErrorEvent и window.onerror. В обоих этих браузерах вы можете либо использовать window.onerror, либо правильно связать с событием 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);
})

К сожалению, Firefox, Safari и IE все еще существуют, и мы также должны их поддерживать. Поскольку трассировка стека недоступна, window.onerrorнам нужно проделать немного больше работы.

Оказывается, что единственное, что мы можем сделать, чтобы получить трассировки стека от ошибок, - это обернуть весь наш код в try{ }catch(e){ }блок, а затем посмотреть e.stack. Мы можем несколько упростить процесс с помощью функции wrap, которая принимает функцию и возвращает новую функцию с хорошей обработкой ошибок.

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

Это работает. Любая функция, которую вы переносите вручную, будет иметь хорошую обработку ошибок, но в большинстве случаев оказывается, что мы можем сделать это автоматически.

Изменяя глобальное определение addEventListenerтак, чтобы оно автоматически оборачивало обратный вызов, мы можем автоматически вставлять try{ }catch(e){ }вокруг большей части кода. Это позволяет существующему коду продолжать работать, но добавляет высококачественное отслеживание исключений.

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

Нам также нужно убедиться, что removeEventListenerпродолжает работать. На данный момент это не так, потому что аргумент к addEventListenerизменен. Опять же, нам нужно только исправить это на prototypeобъекте:

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

Передайте данные об ошибках в свой бэкэнд

Вы можете отправить данные об ошибке с помощью тега изображения следующим образом

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

Отказ от ответственности: я веб-разработчик на https://www.atatus.com/ .

Физер Хан
источник
Что такое yourserver.com/jserror ? REST, веб-сервис, сервис Wcf ? Что-нибудь простое о бэкенде?
Kiquenet,
Если вы хотите отправить js ошибки из браузера пользователя на ваш сервер. Вы должны написать свой backend ( http://yourserver.com) для получения и хранения. Если вы выбираете atatus.com , вам не нужно ничего делать. Просто включите две строки сценария на своей странице.
Физер Хан
18

Кажется, что window.onerrorне обеспечивает доступ ко всем возможным ошибкам. В частности, он игнорирует:

  1. <img> ошибки загрузки (ответ> = 400).
  2. <script> ошибки загрузки (ответ> = 400).
  3. глобальные ошибки, если в вашем приложении много других библиотек, также манипулирующих window.onerrorнеизвестным образом (jquery, angular и т. д.).
  4. вероятно, во многих случаях, с которыми я не сталкивался после изучения этого сейчас (iframes, переполнение стека и т. д.).

Вот начало сценария, который улавливает многие из этих ошибок, так что вы можете добавить более надежную отладку в ваше приложение во время разработки.

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

})();

Это можно использовать так:

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

Полный сценарий имеет реализацию по умолчанию, которая пытается распечатать получитаемую «отображаемую» версию объекта / ошибки, которую он получает. Может быть использован для вдохновения для обработчика ошибок для конкретного приложения. Реализация по умолчанию также сохраняет ссылку на последние 100 объектов ошибок, поэтому вы можете просмотреть их в веб-консоли после того, как они произошли, например:

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

Примечание. Это работает путем переопределения методов в нескольких браузерах / собственных конструкторах. Это может иметь непредвиденные побочные эффекты. Тем не менее, его было полезно использовать во время разработки, чтобы выяснить, где происходят ошибки, для отправки журналов в сервисы, такие как NewRelic или Sentry, во время разработки, чтобы мы могли измерять ошибки во время разработки и при подготовке, чтобы мы могли отлаживать то, что происходит в более глубокий уровень. Затем он может быть отключен в производстве.

Надеюсь это поможет.

Лэнс Поллард
источник
1
Видимо, изображения вызывают событие ошибки: 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
источник
6

Следует также сохранить ранее связанный обратный вызов onerror

<script type="text/javascript">

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

</script>
Апурв Саксена
источник
3

Если вам нужен унифицированный способ обработки как необработанных ошибок, так и необработанных отклонений обещаний, вы можете взглянуть на необработанную библиотеку .

РЕДАКТИРОВАТЬ

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

Слушает окно. необработанный отказ в дополнение к window.onerror.

Александр Олейников
источник
2
Ланс Поллард в ответе выше упомянул несколько ошибок, с которыми не может справиться нормальная ошибка. Ваша библиотека справляется с ними? Если некоторые из них, пожалуйста, укажите, какие?
Майкл
@ MiFreidgeimSO-stopbeingevil Только что проверил исходный код - нет, к сожалению, это не так. Александр Олейников: Было бы замечательно, если бы вы могли поддержать это - тогда я превращу свое отрицательное голосование в повышенное ;-)
Brillout
2

Я бы порекомендовал дать Trackjs попробовать.

Это регистрация ошибок как сервис.

Это удивительно просто настроить. Просто добавьте одну строку <script> на каждую страницу и все. Это также означает, что удалить его будет невероятно просто, если вы решите, что он вам не нравится.

Есть и другие сервисы, такие как Sentry (с открытым исходным кодом, если вы можете разместить свой собственный сервер), но он не делает то, что делает Trackjs. Trackjs записывает взаимодействие пользователя между его браузером и вашим веб-сервером, так что вы можете фактически отслеживать шаги пользователя, которые привели к ошибке, а не просто ссылку на файл и номер строки (и, возможно, трассировку стека).

Kane
источник
2
У TrackJS , похоже, больше нет бесплатного уровня , хотя есть бесплатная пробная версия .
pkaeding
Это хорошо для отслеживания и оповещения об ошибках, но на самом деле не решает часть обработки. В идеале я думаю, что спрашивающий ищет способ справиться с этими ошибками, чтобы остальная часть кода все еще работала.
WGP
Что если вы перехватите событие ошибки и добавите в регистратор вызов xhr с трассировкой стека и состоянием приложения? Чем треки лучше?
inf3rno
2
@ inf3rno это больше, чем трассировка стека, которая просто отслеживает начало ошибки. TrackJS будет отслеживать весь пользовательский сеанс, чтобы вы могли видеть, что привело к нему. Вот скриншот в качестве примера: trackjs.com/assets/images/screenshot.png
Кейн
1

Вы слушаете событие onerror, назначая функцию для 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; 
  };
Али Азар
источник