Статьи

Захват и сообщить об ошибках JavaScript с помощью window.onerror

Эта статья была создана в сотрудничестве с Sentry . Спасибо за поддержку партнеров, которые делают возможным использование SitePoint.

onerrorошибки JavaScript . Это один из самых простых способов регистрировать ошибки на стороне клиента и сообщать о них на серверы. Это также один из основных механизмов, с помощью которого работает клиентская интеграция Sentry с JavaScript (raven-js).

Вы слушаете событие onerror, назначая функцию для window.onerror

 window.onerror = function (msg, url, lineNo, columnNo, error) {
  // ... handle error ...

  return false;
}

Когда выдается ошибка, в функцию передаются следующие аргументы:

  • msg — сообщение, связанное с ошибкой, например, «Uncaught ReferenceError: foo не определено»
  • url — URL-адрес скрипта или документа, связанного с ошибкой, например, «/dist/app.js»
  • lineNo — номер строки (если есть)
  • columnNo — номер столбца (если имеется)
  • error — объект Error, связанный с этой ошибкой (если имеется)

Первые четыре аргумента указывают, в каком скрипте, строке и столбце произошла ошибка. Последний аргумент, объект Error, является, пожалуй, самым ценным. Давайте узнаем почему.

Объект Error и error.stack

На первый взгляд объект Error не очень особенный. Он содержит 3 стандартизированных свойства: message , fileName и lineNumber . Избыточные значения, которые уже предоставлены вам через window.onerror

Ценной частью является нестандартное свойство: Error.prototype.stack Это свойство стека сообщает вам, в каком месте источника находился каждый кадр программы, когда произошла ошибка. Трассировка стека ошибок может быть важной частью отладки. И, несмотря на нестандартность, это свойство доступно в любом современном браузере.

Вот пример свойства стека объекта Error в 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)"

Трудно читать, верно? Свойство стека на самом деле просто неформатированная строка.

Вот как это выглядит в формате:

 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)

После того, как он был отформатирован, легко увидеть, как свойство стека может иметь решающее значение для отладки ошибки.

Есть только одна загвоздка: свойство стека нестандартно, и его реализация отличается в разных браузерах. Например, вот та же трассировка стека из 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)

Мало того, что формат каждого кадра отличается, кадры также имеют меньше деталей. Например, Chrome определяет, что было использовано neweval И это только IE 11 против Chrome — другие браузеры также имеют различные форматы и детализацию.

К счастью, существуют инструменты, которые нормализуют свойства стека, чтобы он был одинаковым для всех браузеров. Например, raven-js использует TraceKit для нормализации строк ошибок. Там также stacktrace.js и несколько других проектов.

Совместимость браузера

window.onerror

Проблема в том, что каждый браузер реализует window.onerror

Вот таблица, аргументы которой передаются в onerror в большинстве браузеров:

браузер Сообщение URL LINENO colNo errorObj
Fire Fox
Хром
край
IE 11
IE 10
IE 9, 8
Safari 10 и выше
Safari 9
Браузер Android 4.4

Вероятно, неудивительно, что Internet Explorer 8, 9 и 10 имеют ограниченную поддержку onerror. Но вы можете быть удивлены тем, что Safari только добавил поддержку объекта ошибки в Safari 10 (выпущен в 2016 году). Кроме того, старые мобильные телефоны, которые все еще используют стандартный браузер Android (теперь замененный на Chrome Mobile), все еще существуют и не пропускают объект ошибки.

Без объекта ошибки нет свойства трассировки стека. Это означает, что эти браузеры не могут извлечь ценную информацию из стека из ошибок, обнаруженных onerror.

Polyfilling window.onerror с try / catch

Но есть обходной путь — вы можете обернуть код в вашем приложении внутри try / catch и поймать ошибку самостоятельно. Этот объект ошибки будет содержать наше желанное свойство stack

Рассмотрим следующий вспомогательный метод invoke

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

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

Вот снова invoke

 function invoke(obj, method, args) {
  try {
    return obj[method].apply(this, args);
  } catch (e) {
    captureError(e); // report the error
    throw e; // re-throw the error
  }
}

invoke(Math, 'highest', [1, 2]); // throws error, no method Math.highest

Конечно, делать это вручную везде довольно громоздко. Вы можете сделать это проще, создав универсальную служебную функцию-обертку:

 function wrapErrors(fn) {
  // don't wrap function more than once
  if (!fn.__wrapped__) {
    fn.__wrapped__ = function () {
      try {
        return fn.apply(this, arguments);
      } catch (e) {
        captureError(e); // report the error
        throw e; // re-throw the error
      }
    };
  }

  return fn.__wrapped__;
}

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

invoke(Math, 'highest', [1, 2]); // no method Math.highest

Поскольку JavaScript является однопоточным, вам не нужно использовать перенос везде — только в начале каждого нового стека.

Это означает, что вам нужно обернуть объявления функций:

  • В начале вашего приложения (например, в $(document).ready
  • В обработчиках событий (например, addEventListener$.fn.click
  • Основанные на таймере обратные вызовы (например, setTimeoutrequestAnimationFrame

Например:

 $(wrapErrors(function () { // application start
  doSynchronousStuff1(); // doesn't need to be wrapped

  setTimeout(wrapErrors(function () {
    doSynchronousStuff2(); // doesn't need to be wrapped
  });

  $('.foo').click(wrapErrors(function () {
    doSynchronousStuff3(); // doesn't need to be wrapped
  });
}));

Если это кажется чертовски много работы, не волнуйтесь! Большинство библиотек отчетов об ошибках имеют механизмы для расширения встроенных функций, таких как addEventListenersetTimeout И да, Raven-JS делает это тоже.

Передача ошибки на ваши серверы

Итак, вы выполнили свою работу — вы подключились к window.onerror

Есть только один последний шаг: передача информации об ошибках на ваши серверы. Чтобы это работало, вам нужно настроить веб-службу отчетов, которая будет принимать ваши данные об ошибках по HTTP, записывать их в файл и / или сохранять в базе данных.

Если этот веб-сервис находится в том же домене, что и ваше веб-приложение, просто используйте XMLHttpRequest. В приведенном ниже примере мы используем функцию AJAX jQuery для передачи данных на наши серверы:

 function captureError(ex) {
  var errorData = {
    name: ex.name, // e.g. ReferenceError
    message: ex.line, // e.g. x is undefined
    url: document.location.href,
    stack: ex.stack // stacktrace string; remember, different per-browser!
  };

  $.post('/logger/js/', {
    data: errorData
  });
}

Обратите внимание, что если вам нужно передать свою ошибку из разных источников, ваша конечная точка создания отчетов должна будет поддерживать Cross Source Resource Sharing (CORS).

Резюме

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

  • Как работает window.onerror
  • Как использовать try / catch для захвата трассировки стека, где отсутствует window.onerror
  • Передача данных об ошибках на ваши серверы

Конечно, если вы не хотите беспокоиться обо всем этом, существует множество коммерческих инструментов и инструментов с открытым исходным кодом, которые выполняют всю тяжелую работу по составлению отчетов на стороне клиента. (Psst: вы можете попробовать Sentry для отладки JavaScript .)

Это оно! Счастливый мониторинг ошибок .