Руководство по внешнему мониторингу ошибок

JavaScript

предисловие

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

В этой статье в основном обсуждаются следующие моменты:

  1. Распространенные типы ошибок JS
  2. Общие методы обработки ошибок JS
  3. Как сообщить и некоторые мысли о том, что сообщить

вопрос:

  1. Как я могу узнать в режиме реального времени, что ресурсы, такие как JS, CSS, img и другие ресурсы, не загружаются (CDN или кровать изображения зависла, удалена непреднамеренно или имя файла изменилось)? вместо того, чтобы пользователи говорили вам?
  2. Как сообщить полезную информацию об ошибке, чтобы программисты могли быстро найти ошибку и исправить ее? Вместо того, чтобы сообщать какую-то запутанную информацию?
  3. Как эффективно использовать файлы SourceMap и работать с сообщениями об ошибках в современных разработках, не требующих сжатия и искажения кода?
  4. Как что-то может пойти не так, не попросив пользователя помочь вам воспроизвести это? Хотите модель? Хотите сделать шаги?
  5. Как лучше посчитать распределение проблем (модели устройств, браузеры, география, пропускная способность и т. д.), и самостоятельно выбрать тенденцию совместимости по данным?
  6. ...

Распространенные ошибки

  1. ошибка сценария
    • Грамматические ошибки
    • ошибка выполнения
      • ошибка синхронизации
      • Асинхронная ошибка
      • Ошибка обещания
  2. Ошибка сети
    • ошибка загрузки ресурса
    • ошибка пользовательского запроса

Вы можете прочитать исходный код библиотеки классов мониторингаerrorWatchДля углубления понимания его также можно использовать непосредственно в проекте.

Грамматические ошибки

Например, английские иероглифы записываются как китайские иероглифы. Обычно легко найти во время разработки.

syntaxError

Синтаксические ошибки не могут бытьtry catchиметь дело с

try {
  const error = 'error';   // 圆角分号
} catch(e) {
  console.log('我感知不到错误');
}

ошибка синхронизации

Когда JS-движок выполняет скрипт, он помещает задачи в стек событий блоками и опрашивает их на выполнение.Каждая задача события имеет свой собственный контекст. Ошибки в коде, выполняющемся синхронно в текущем контексте, могут бытьtry catchЗахват, чтобы гарантировать выполнение последующего синхронного кода.

try {
  error
} catch(e) {
  console.log(e);
}

Асинхронная ошибка

ОбщийsetTimeoutи другие методы создадут новую событийную задачу и вставят ее в стек событий для последующего выполнения. такtry catchОшибки кода из других контекстов не могут быть обнаружены.

try {
  setTimeout(() => {
    error        // 异步错误
  })
} catch(e) {
  console.log('我感知不到错误');
}

Чтобы облегчить анализ возникающих ошибок, обычно используютwindow.onerrorсобытие для прослушивания возникновения ошибки. это лучше, чемtry catchВозможность захвата информации об ошибках сильнее.

/**
 * @param {String}  msg    错误描述
 * @param {String}  url    报错文件
 * @param {Number}  row    行号
 * @param {Number}  col    列号
 * @param {Object}  error  错误Error对象
 */
 window.onerror = function (msg, url, row, col, error) {
  console.log('我知道错误了');
  // return true; // 返回 true 的时候,异常不会向上抛出,控制台不会输出错误
};

windowOnerror

  • window.onerror Примечания
  1. window.onerrorМожет ловить общий синтаксис, синхронные, асинхронные ошибки и другие ошибки;
  2. window.onerrorне могу пойматьPromiseошибка, сетевая ошибка;
  3. window.onerrorДолжен выполняться перед всеми JS-скриптами, чтобы не пропустить;
  4. window.onerrorЕго легко переопределить, его следует учитывать при обработке обратных вызовов, и люди также используют этот прослушиватель событий.

Ошибка сети

Поскольку исключение сетевого запроса не будет всплывать, его следует получить на этапе захвата событий. мы можем использоватьwindow.addEventListener. Например, код, изображения и т. д.CDNКогда ресурсов не хватает, крайне важно своевременно получать обратную связь.

window.addEventListener('error', (error) => {
  console.log('404 错误');
  console.log(error);
  // return true; // 中断事件传播
}, true);

addEventListener

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

window.addEventListener('error', (e) => {
  if (e.target !== window) { // 避免重复上报
    console.log({
    	url: window.location.href, // 引用资源地址
    	srcUrl: e.target.src, // 资源加载出错地址
    })
  }
}, true);
  • window.onerrorа такжеwindow.addEventListener

window.addEventListenerПреимущество в том, что вы не боитесь перезаписи обратных вызовов, вы можете прослушивать несколько функций обратного вызова, но не забывайте их уничтожать, чтобы избежать утечек памяти и ошибок. но не могу получитьwindow.onerrorТакое богатство информации. Обычно используется толькоwindow.addEventListenerдля мониторинга ошибок загрузки ресурсов.

  • Для пользовательских ошибок сетевых запросов лучше всего сообщать о них вручную.

Ошибка обещания

если вы используетеpromiseеще нетcatch, тогдаonerrorТоже бессильный.

Promise.reject('promise error');
new Promise((resolve, reject) => {
  reject('promise error');
});
new Promise((resolve) => {
  resolve();
}).then(() => {
  throw 'promise error';
});

Также вы можете использоватьwindow.onunhandledrejectionилиwindow.addEventListener("unhandledrejection")для контроля ошибок. Получите объект PromiseError, который может разрешить объект ошибки вreasonсвойства, что-то вродеstack.

Конкретную совместимую обработку можно увидеть в TraceKit.js.

Метод отчетности

  1. imgотчет
  2. ajaxотчет
function report(errInfo) {
  new Image().src = 'http://your-api-website?data=' + errInfo;
}

ajaxСледует использовать только библиотеку классов, которая аналогична.

  • Уведомление:imgЗапрос имеет ограничение по длине, данные слишком велики, лучше использоватьajax.post.

Пополнить

Script error

Скрипты, ссылающиеся на разные доменные имена, если нет специальной обработки, сообщает об ошибке. Как правило, браузер из соображений безопасности и не отображает конкретные ошибки, ноScript error. Например, у других есть скрытые мотивы цитировать ваш бизнес-код с закрытым исходным кодом в Интернете, и, конечно же, вы не хотите, чтобы они знали сообщение об ошибке вашего скрипта.

Как решить проблему междоменных отчетов об ошибках собственного скрипта?

  • Все ресурсы переключаются на единое доменное имя, но это теряетсяCDNПреимущества.
  • в файле скриптаHTTP response headerустановить вCORS.
  1. Access-Control-Allow-Origin: You-allow-origin;
  2. добавить тег скриптаcrossoriginсвойства, такие как<script src="http://www.xxx.com/index.js" crossorigin></script>

заголовки ответов иcrossoriginПроблема ценности

  1. crossorigin="anonymous"(По умолчанию),CORSне равноYou-allow-origin, не могу принестиcookie
  2. crossorigin="use-credentials"а такжеAccess-Control-Allow-Credentials: true ,CORSне может быть установлено на*,Группаcookie. еслиCORSне равноYou-allow-origin, браузер не загружает файл js.

Когда вы хорошо справляетесь с имеющимися в вашем распоряжении ресурсамиcorsчас,Script errorВ принципе, это можно отфильтровать и не сообщать.

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

Анатомия ошибок JavaScript

ОдинJavaScriptОшибки обычно состоят из следующих ошибок

  • сообщение об ошибке
  • трассировки стека

error

consoleError

Разработчики могут выдать ошибку JavaScript по-разному:

  • throw new Error('Problem description.')
  • throw Error('Problem description.') <-- equivalent to the first one
  • throw 'Problem description.' <-- bad
  • throw null <-- even worse

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

Если сообщение об ошибке в стеке трассировки может быть проанализировано для каждой строки, эта строка будет указана в соответствующем списке.SourceMapНе можете ли вы найти конкретный исходный код каждой строки? Проблема в том, что разные браузеры не имеют единого стандартного формата вышеуказанной информации. Сложность заключается в решении вопросов совместимости.

Напримерwindow.onerrorОбъект ошибки пятого параметра добавлен в 2013 г.WHATWGв спецификации. Ранний Safari и IE10 еще не сделали этого, Firefox добавил объект Error из версии 14, а chrome был добавлен только в 2013 году.

Рекомендуемая практика

  1. window.onerrorЭто лучший способ отлавливать ошибки JS и сообщать только при наличии допустимого объекта Error и стека трассировки. Также можно избежать ошибок, на которые нельзя повлиять, таких как ошибки плагинов и междоменные ошибки с неполной информацией.

  2. try catchУлучшено, информация об ошибках стала более полной, что можно компенсировать.window.onerrorнедостаточности. Но, как было сказано ранее,try catchНевозможно поймать асинхронные ошибки иpromiseошибка, ни использоватьV8Оптимизация работы двигателя.

Например, компания Тенсент.BadJS, дал следующие рекомендацииtry catchпакет

  • setTimeout и setInterval
  • привязка события
  • ajax callback
  • определить и потребовать
  • Главный деловой вход

Нужно ли делать такой мелкозернистый пакет, зависит от ситуации.

SourceMap

Например, следующий стек трассировки ошибок (трассировка стека)

ReferenceError: thisIsAbug is not defined
    at Object.makeError (http://localhost:7001/public/js/traceKit.min.js:1:9435)
    at http://localhost:7001/public/demo.html:28:12

можно разобрать в следующий формат

[
	{
	  "args" : [],
	  "url" : "http://localhost:7001/public/js/traceKit.min.js",
	  "func" : "Object.makeError",
	  "line" : 1,
	  "column" : 9435,
	  "context" : null
	}, 
	{
	  "args" : [],
	  "url" : "http://localhost:7001/public/demo.html",
	  "func" : "?",
	  "line" : 28,
	  "column" : 12,
	  "context" : null
	}
]

Имея звания и соответствующиеSourceMapФайл может быть проанализирован для получения информации об исходном коде.

sourceMapDel

Разобрать результаты

sourceMapDel

Код обработки следующий:

import { SourceMapConsumer } from 'source-map';

// 必须初始化
SourceMapConsumer.initialize({
  'lib/mappings.wasm': 'https://unpkg.com/source-map@0.7.3/lib/mappings.wasm',
});

/**
 * 根据sourceMap文件解析源代码
 * @param {String} rawSourceMap sourceMap文件
 * @param {Number} line 压缩代码报错行
 * @param {Number} column 压缩代码报错列
 * @param {Number} offset 设置返回临近行数
 * @returns {Promise<{context: string, originLine: number | null, source: string | null}>}
 * context:源码错误行和上下附近的 offset 行,originLine:源码报错行,source:源码文件名
 */
export const sourceMapDeal = async (rawSourceMap, line, column, offset) => {
  // 通过sourceMap库转换为sourceMapConsumer对象
  const consumer = await new SourceMapConsumer(rawSourceMap);
  // 传入要查找的行列数,查找到压缩前的源文件及行列数
  const sm = consumer.originalPositionFor({
    line, // 压缩后的行数
    column, // 压缩后的列数
  });
  // 压缩前的所有源文件列表
  const { sources } = consumer;
  // 根据查到的source,到源文件列表中查找索引位置
  const smIndex = sources.indexOf(sm.source);
  // 到源码列表中查到源代码
  const smContent = consumer.sourcesContent[smIndex];
  // 将源代码串按"行结束标记"拆分为数组形式
  const rawLines = smContent.split(/\r?\n/g);
  let begin = sm.line - offset;
  const end = sm.line + offset + 1;
  begin = begin < 0 ? 0 : begin;
  const context = rawLines.slice(begin, end).join('\n');
  // 记得销毁
  consumer.destroy();
  return {
    context,
    originLine: sm.line + 1, // line 是从 0 开始数,所以 +1
    source: sm.source,
  }
};

каждый согласноSourceMapФормат файла, вы можете понять этот код хорошо.

Справочный сайт

  1. mozilla/source-map
  2. Практика мониторинга исключений во внешнем коде
  3. Внешний мониторинг исключений — BadJS
  4. Ошибки скриптов оптимизированы до предела — сделайте ошибки скрипта понятными с первого взгляда