предисловие
Ранее я исследовал ошибки скриптов клиентского кода компании, пытаясь уменьшить количество ошибок JS Error. Я практиковал и резюмировал этот материал на основе своего предыдущего опыта. Позвольте мне рассказать о некоторых моих выводах об исключениях кода внешнего интерфейса. мониторинг.
В этой статье в общих чертах обсуждаются следующие моменты:
- Как JS обрабатывает исключения
- Метод отчетности
- Ненормальный мониторинг и отчеты о распространенных проблемах
Обработка исключений JS
Для Javascript мы сталкиваемся только с исключениями. Возникновение исключений не приведет непосредственно к сбою JS-движка, но в лучшем случае завершит выполнение текущей задачи.
- Текущий блок кода будет помещен в очередь задач как задача, а поток JS будет непрерывно извлекать задачи из очереди задач для выполнения.
- Когда во время выполнения задачи возникает исключение, а исключение не поймано и не обработано, оно будет выбрасываться слой за слоем по стеку вызовов, и, наконец, выполнение текущей задачи будет прекращено.
- Поток JS продолжит извлекать следующую задачу из очереди задач, чтобы продолжить выполнение.
<script>
error
console.log('永远不会执行');
</script>
<script>
console.log('我继续执行')
</script>
Прежде чем сообщать об ошибках сценария, нам нужно обработать исключения.Программа должна сначала воспринять возникновение ошибок сценария, а затем говорить об отчетах об исключениях.
Ошибки сценария обычно делятся на два типа: синтаксические ошибки и ошибки времени выполнения.
Вот несколько способов обработки мониторинга исключений:
обработка исключений try-catch
Try-catch часто встречается в нашем коде.После обертывания блока кода с помощью try-catch, когда в блоке кода возникает ошибка, catch сможет захватить информацию об ошибке, и страница продолжит выполнение.
Однако возможности try-catch по обработке исключений ограничены, и он может перехватывать только неасинхронные ошибки во время выполнения, он бессилен и не может перехватывать синтаксические и асинхронные ошибки.
Пример: ошибка времени выполнения
try {
error // 未定义变量
} catch(e) {
console.log('我知道错误了');
console.log(e);
}
Однако синтаксические ошибки и асинхронные ошибки не могут быть обнаружены.
Пример: синтаксическая ошибка
try {
var error = 'error'; // 大写分号
} catch(e) {
console.log('我感知不到错误');
console.log(e);
}
Общие синтаксические ошибки будут отражены в редакторе, и часто отображаются следующие сообщения об ошибках:Uncaught SyntaxError: Invalid or unexpected token xxxтак. Тем не менее, такого рода ошибки вызывают прямое исключение, часто приводящее к сбою программы, и обычно это легко заметить во время кодирования.
Пример: асинхронная ошибка
try {
setTimeout(() => {
error // 异步错误
})
} catch(e) {
console.log('我感知不到错误');
console.log(e);
}
Если вы не поместите try-catch в функцию setTimeout, вы не сможете воспринять ошибку, но это делает код более подробным.
обработка исключений window.onerror
Способность window.onerror перехватывать исключения немного сильнее, чем try-catch.Независимо от того, является ли это асинхронной или неасинхронной ошибкой, onerror может перехватывать ошибки времени выполнения.
Пример: ошибка синхронизации во время выполнения
/**
* @param {String} msg 错误信息
* @param {String} url 出错文件
* @param {Number} row 行号
* @param {Number} col 列号
* @param {Object} error 错误详细信息
*/
window.onerror = function (msg, url, row, col, error) {
console.log('我知道错误了');
console.log({
msg, url, row, col, error
})
return true;
};
error;
Пример: асинхронная ошибка
window.onerror = function (msg, url, row, col, error) {
console.log('我知道异步错误了');
console.log({
msg, url, row, col, error
})
return true;
};
setTimeout(() => {
error;
});
Тем не менее, window.onerror по-прежнему бессилен для синтаксических ошибок, поэтому мы должны максимально избегать синтаксических ошибок при написании кода, но обычно такие ошибки приводят к сбою всей страницы, что относительно легко обнаружить.
На практике onerror в основном используется для перехвата непредвиденных ошибок, а try-catch используется для отслеживания конкретных ошибок в предсказуемых обстоятельствах.Комбинация этих двух методов более эффективна.
Следует отметить, что исключение не будет выброшено вверх только тогда, когда функция window.onerror вернет true, в противном случае консоль все равно будет отображаться, даже если известно исключениеUncaught Error: xxxxx.
Есть еще две вещи, которые стоит отметить в отношении window.onerror.
- Для глобального захвата onerror лучше всего писать его перед всеми JS скриптами, потому что вы не можете гарантировать, что написанный вами код неверен, если вы напишете его позже, как только произойдет ошибка, она не будет перехвачена onerror .
- Кроме того, onerror — это ошибка, которая не может перехватывать сетевые исключения.
когда мы встретимся<img src="./404.png">Когда сообщается об исключении сетевого запроса 404, onerror не может помочь нам перехватить исключение.
<script>
window.onerror = function (msg, url, row, col, error) {
console.log('我知道异步错误了');
console.log({
msg, url, row, col, error
})
return true;
};
</script>
<img src="./404.png">
Поскольку исключения сетевых запросов не всплывают, они должны быть перехвачены на этапе захвата.Однако, хотя этот метод может перехватывать исключения сетевых запросов, невозможно определить, является ли статус HTTP 404 или другим, например 500 и т. д. Таким образом, необходимо сотрудничать с журналом сервера для исследования и анализа.
<script>
window.addEventListener('error', (msg, url, row, col, error) => {
console.log('我知道 404 错误了');
console.log(
msg, url, row, col, error
);
return true;
}, true);
</script>
<img src="./404.png" alt="">
Это знание все равно нужно знать, иначе пользователи будут заходить на сайт, CDN изображений не сможет обслуживать, изображение не будет загружено и разработчик этого не заметит, будет неловко.
Ошибка обещания
Промисы могут помочь нам решить проблему ада асинхронных обратных вызовов, но как только экземпляр промиса выдает исключение, а вы не перехватываете его с помощью catch, onerror или try-catch не могут ничего сделать, чтобы перехватить ошибку.
window.addEventListener('error', (msg, url, row, col, error) => {
console.log('我感知不到 promise 错误');
console.log(
msg, url, row, col, error
);
}, true);
Promise.reject('promise error');
new Promise((resolve, reject) => {
reject('promise error');
});
new Promise((resolve) => {
resolve();
}).then(() => {
throw 'promise error'
});
Хотя это хорошая привычка писать функцию catch в конце при написании экземпляра Promise, легко запутаться и забыть написать функцию catch, если вы пишете слишком много кода.
Итак, если ваше приложение использует много экземпляров Promise, особенно вы должны быть осторожны с некоторыми асинхронными библиотеками на основе обещаний, такими как axios, потому что вы не знаете, когда эти асинхронные запросы вызовут исключение, и вы не обрабатываете его, Так что вам лучше добавить необработанное отклонение глобального исключения исключения Promise.
window.addEventListener("unhandledrejection", function(e){
e.preventDefault()
console.log('我知道 promise 的错误了');
console.log(e.reason);
return true;
});
Promise.reject('promise error');
new Promise((resolve, reject) => {
reject('promise error');
});
new Promise((resolve) => {
resolve();
}).then(() => {
throw 'promise error'
});
Конечно, если ваше приложение не выполняет глобальную обработку исключений Promise, оно, скорее всего, будет выглядеть примерно как домашняя страница:
Неправильный способ сообщения
После мониторинга и получения информации об ошибке следующим шагом будет отправка собранной информации об ошибке на платформу сбора информации.Существуют две часто используемые формы отправки:
- Отправка данных через Ajax
- Динамически создавать форму тега img
Пример. Динамическое создание тегов img для отчетов
function report(error) {
var reportUrl = 'http://xxxx/report';
new Image().src = reportUrl + 'error=' + error;
}
Мониторинг и отчеты о распространенных проблемах
Все следующие примеры я выложил на свой гитхаб, читатели могут сами на них ссылаться, и я не буду их повторять позже.
git clone https://github.com/happylindz/blog.git
cd blog/code/jserror/
npm install
Ошибка скрипта В чем ошибка скрипта
Поскольку наша онлайн-версия часто выполняет CDNизацию статических ресурсов, это приведет к тому, что страницы, которые мы часто посещаем, и файлы скриптов будут поступать с разных доменных имен.Если в это время не будет выполнена дополнительная настройка, легко возникнут ошибки скрипта.
доступныйnpm run nocorsПроверьте эффект.
Ошибка сценария генерируется браузером в соответствии с ограничением политики того же источника. Браузер принимает во внимание меры безопасности. Если при обращении страницы к внешнему файлу сценария с другим доменным именем возникает исключение, эта страница не имеет права знать эту информацию об ошибке, а вместо этого вывести сообщение типа Ошибка сценария.
Целью этого является предотвращение утечки данных в небезопасные домены, например,
<script src="xxxx.com/login.html"></script>
Выше мы представили не файл js, а html.Этот html является страницей входа в банк.Если вы уже вошли в систему, страница входа автоматически перейдет наWelcome xxx..., если вы не вошли в систему, перейдите к Please Login..., то ошибка тоже будетДобро пожаловать xxx... не определено, Пожалуйста, войдите... не определено, по этой информации можно судить о том, входит ли пользователь в свою учетную запись, что обеспечивает очень удобный канал оценки для злоумышленников, что довольно небезопасно.
После того, как предыстория введена, тогда мы должны идти решать эту проблему?
Первое решение, о котором можно подумать, — это стратегия гомологии. Встраивайте JS-файлы в html или помещайте их в тот же домен. Хотя проблема с ошибкой скрипта может быть решена просто и эффективно, она не может использовать преимущества кэширования файлов и CDN. не рекомендуется использовать. Правильный способ должен состоять в том, чтобы принципиально решить ошибку ошибки скрипта.
Механизм совместного использования ресурсов между источниками (CORS)
Сначала добавьте атрибут crossOrigin в тег скрипта на странице.
// http://localhost:8080/index.html
<script>
window.onerror = function (msg, url, row, col, error) {
console.log('我知道错误了,也知道错误信息');
console.log({
msg, url, row, col, error
})
return true;
};
</script>
<script src="http://localhost:8081/test.js" crossorigin></script>
// http://localhost:8081/test.js
setTimeout(() => {
console.log(error);
});
Когда вы изменяете интерфейсный код, вам также необходимо добавить дополнительный бэкенд в заголовок ответа.Access-Control-Allow-Origin: localhost:8080, здесь я беру Коа в качестве примера.
const Koa = require('koa');
const path = require('path');
const cors = require('koa-cors');
const app = new Koa();
app.use(cors());
app.use(require('koa-static')(path.resolve(__dirname, './public')));
app.listen(8081, () => {
console.log('koa app listening at 8081')
});
Читатели могутnpm run corsЯ не буду расширять подробные междоменные знания, если вам интересно, вы можете прочитать статьи, которые я написал ранее:Междоменный, все, что вам нужно знать, здесь
Как вы думаете, это конец? Нет, давайте поговорим о некоторых ошибках скрипта, с которыми вы не часто сталкиваетесь:
Все мы знаем, что JSONP используется для получения данных между доменами и имеет хорошую совместимость, он до сих пор используется в некоторых приложениях, поэтому в вашем проекте может использоваться такой код:
// http://localhost:8080/index.html
window.onerror = function (msg, url, row, col, error) {
console.log('我知道错误了,但不知道错误信息');
console.log({
msg, url, row, col, error
})
return true;
};
function jsonpCallback(data) {
console.log(data);
}
const url = 'http://localhost:8081/data?callback=jsonpCallback';
const script = document.createElement('script');
script.src = url;
document.body.appendChild(script);
Поскольку возвращенная информация будет выполняться как файл сценария, если возвращенное содержимое сценария неверно, неверная информация не может быть захвачена.
Решение не сложное, как и раньше добавить crossOrigin при добавлении динамического скрипта, а в бэкенде добавить соответствующее поле CORS.
const script = document.createElement('script');
script.crossOrigin = 'anonymous';
script.src = url;
document.body.appendChild(script);
Читатели могут пройтиnpm run jsonpПосмотреть эффект
Узнав принцип, вы можете почувствовать, что это ничего, не просто добавление поля crossOrigin в каждый динамически сгенерированный скрипт, но в реальных проектах вы можете программировать для многих библиотек, например, используя jQuery, Seajs или webpack для асинхронной загрузки скриптов. , Многие библиотеки инкапсулируют возможность асинхронной загрузки сценариев, в случае с jQeury вы можете запускать подобные асинхронные сценарии.
$.ajax({
url: 'http://localhost:8081/data',
dataType: 'jsonp',
success: (data) => {
console.log(data);
}
})
Если эти библиотеки не предоставляют возможности crossOrigin (она может быть в jQuery jsonp, притворитесь, что вы не знаете), то вы можете только модифицировать исходный код, написанный другими, поэтому я предлагаю здесь идею, заключающуюся в захвате документа. createElement, из Root поднимитесь и добавьте поле crossOrigin в каждый динамически сгенерированный скрипт.
document.createElement = (function() {
const fn = document.createElement.bind(document);
return function(type) {
const result = fn(type);
if(type === 'script') {
result.crossOrigin = 'anonymous';
}
return result;
}
})();
window.onerror = function (msg, url, row, col, error) {
console.log('我知道错误了,也知道错误信息');
console.log({
msg, url, row, col, error
})
return true;
};
$.ajax({
url: 'http://localhost:8081/data',
dataType: 'jsonp',
success: (data) => {
console.log(data);
}
})
Эффект тот же, читатель может пройтиnpm run jsonpjqувидеть эффект:
Теоретически переписать createElement таким образом не проблема, но если в исходный код вторгнуться, то нет гарантии, что не будет ошибок.В инженерии все равно нужно попробовать и посмотреть, прежде чем использовать.Могут быть проблемы с совместимостью.Если вы думаете, что будут какие-либо проблемы Если да, пожалуйста, оставьте сообщение для обсуждения.
Вопрос об ошибке сценария написан здесь.Если вы понимаете вышеизложенное, в основном большинство ошибок сценария можно легко решить.
Может ли window.onerror перехватывать ошибки iframe
Когда ваша страница использует iframe, вам необходимо проводить нештатный мониторинг введенных вами iframe, иначе, как только возникнет проблема со страницами iframe, которые вы вводите, ваш основной сайт не будет отображаться, и вы об этом не узнаете.
Прежде всего, необходимо подчеркнуть, что родительское окно не может быть напрямую захвачено напрямую с помощью window.onerror.Если вы хотите захватить исключения iframe, существует несколько ситуаций.
Если ваша страница iframe и ваш основной сайт находятся на одном и том же доменном имени, вы можете напрямую добавить событие onerror в iframe.
<iframe src="./iframe.html" frameborder="0"></iframe>
<script>
window.frames[0].onerror = function (msg, url, row, col, error) {
console.log('我知道 iframe 的错误了,也知道错误信息');
console.log({
msg, url, row, col, error
})
return true;
};
</script>
Читатели могут пройтиnpm run iframeПроверьте эффект:
Если встроенная вами страница iframe не принадлежит к тому же доменному имени, что и ваш основной сайт, но содержимое iframe не принадлежит третьей стороне и находится под вашим контролем, вы можете отправить информацию об исключении на основной сайт, сообщив с iframe. Есть много способов общения с iframes, например: postMessage, хеш или поле имени, междоменное и т. д., которые не будут здесь раскрываться, если вам интересно, вы можете прочитать:Междоменный, все, что вам нужно знать, здесь
Если это другой домен и веб-сайт не находится под вашим контролем, нет никакого способа захватить его, кроме просмотра подробной информации об ошибке через консоль. Это сделано из соображений безопасности. Вы ввели домашнюю страницу Baidu и ошибка сообщают чужие страницы.Зачем вам следить за этим,это поднимет много проблем с безопасностью.
Как найти местоположение исключения скрипта в сжатом коде
Код в строке почти весь сжат, десятки файлов упакованы в один и углифированы код, когда мы получаемa is not definedКогда мы не знаем, что на самом деле означает переменная а, журнал ошибок, сообщаемый в это время, очевидно, недействителен.
Первое, что приходит на ум, — использовать исходную карту для определения конкретного местоположения кода ошибки.Ошибка сценария поиска исходной карты
Кроме того, вы также можете добавить несколько строк пробелов между каждым объединенным файлом при упаковке и добавить соответствующие комментарии, чтобы при обнаружении проблемы было легко узнать, какой файл сообщил об ошибке, а затем выполнить поиск по ключевым словам. быстро определить местонахождение проблемы.
Собрано слишком много аномальной информации, что мне делать?
Если у вашего сайта большая посещаемость, если PV страницы 1кВт, то будет 1кВт информации, отправленной неизбежной ошибкой.Мы можем установить коэффициент сбора для сайта:
Reporter.send = function(data) {
// 只采集 30%
if(Math.random() < 0.3) {
send(data) // 上报错误信息
}
}
Эта скорость сбора может быть установлена в соответствии с реальной ситуацией, а методы разнообразны, может использоваться случайное число или оно может быть определено в соответствии с некоторыми характеристиками пользователя.
Вышеизложенное примерно соответствует моему пониманию мониторинга кода переднего плана. Это легко сказать, но как только он используется в разработке, неизбежно рассмотрение различных вопросов, таких как совместимость. Читатели могут настроить его в соответствии со своими конкретными условиями. стабильность сайта играет жизненно важную роль. Если в тексте что-то не так, поправьте меня.