Сделать node.js не выходить с ошибкой
Я работаю над ориентированным на websocket сервером node.js с помощью Socket.IO. Я заметил ошибку, когда некоторые браузеры не следуют правильной процедуре подключения к серверу, и код не написан, чтобы грамотно обрабатывать его, и, короче говоря, он вызывает метод для объекта, который никогда не был настроен, таким образом убивая сервер из-за ошибки.
Моя проблема связана не с ошибкой, а с тем фактом, что при возникновении таких ошибок весь сервер отключается. Есть ли что-нибудь, что я могу сделать на глобальном уровне в node, чтобы сделать это, если произойдет ошибка, он просто зарегистрирует сообщение, возможно, убьет событие, но процесс сервера будет продолжать работать?
Я не хочу, чтобы соединения других пользователей снижались из-за того, что один умный пользователь использовал нечеткую ошибку в большой включенной кодовой базе.
Ответы
Ответ 1
Вы можете присоединить слушателя к событию uncaughtException
объекта процесса.
Код, взятый из фактического Node.js Ссылка на API (это второй элемент под "process" ):
process.on('uncaughtException', function (err) {
console.log('Caught exception: ', err);
});
setTimeout(function () {
console.log('This will still run.');
}, 500);
// Intentionally cause an exception, but don't catch it.
nonexistentFunc();
console.log('This will not run.');
Все, что вам нужно сделать, это зарегистрировать его или сделать с ним что-либо, если вы знаете, при каких обстоятельствах возникает ошибка, вы должны подать сообщение об ошибке на странице Socket.IO GitHub:
https://github.com/LearnBoost/Socket.IO-node/issues
Ответ 2
Использование uncaughtException - очень плохая идея.
Лучшей альтернативой является использование доменов в Node.js 0.8. Если вы используете более раннюю версию Node.js, используйте forever, чтобы перезапустить свои процессы или даже лучше использовать node cluster, чтобы вызвать несколько рабочих процессов и перезапустить рабочего в событии uncaughtException.
От: http://nodejs.org/api/process.html#process_event_uncaughtexception
Предупреждение: правильное использование "uncaughtException"
Обратите внимание, что 'uncaughtException' является грубым механизмом обработки исключений, предназначенным для использования только в качестве последнего средства. Событие не должно использоваться в качестве эквивалента On Error Resume Next. Необработанные исключения по сути означают, что приложение находится в состоянии undefined. Попытка возобновить код приложения без надлежащего восстановления после исключения может вызвать дополнительные непредвиденные и непредсказуемые проблемы.
Исключения, выброшенные из обработчика событий, не будут обнаружены. Вместо этого процесс завершится с ненулевым кодом выхода, и будет напечатана трассировка стека. Это делается для того, чтобы избежать бесконечной рекурсии.
Попытка возобновить нормально после непоставленного исключения может быть похожа на вытаскивание шнура питания при обновлении компьютера - девять из десяти раз ничего не происходит - но в 10-й раз система становится поврежденной.
Правильное использование "uncaughtException" - это выполнить синхронную очистку выделенных ресурсов (например, дескрипторы файлов, дескрипторы и т.д.) перед закрытием процесса. Небезопасно возобновлять нормальную работу после "uncaughtException" .
Чтобы перезапустить аварийное приложение более надежным способом, независимо от того, испускается ли исключение uncaughtException, внешний монитор следует использовать в отдельном процессе для обнаружения сбоев приложений и восстановления или перезапуска при необходимости.
Ответ 3
Я просто сделал кучу исследований по этому вопросу (см. здесь, здесь, здесь и здесь), и ответ на ваш вопрос заключается в том, что Node не позволит вам написать один обработчик ошибок, который поймает каждый сценарий ошибок, который может произойти в вашей системе.
Некоторые фреймворки, такие как express, позволят вам поймать определенные типы ошибок (когда метод async возвращает объект ошибки), но есть и другие условия, которые вы не можете поймать с помощью глобального обработчика ошибок. Это ограничение (на мой взгляд) Node и, возможно, присуще асинхронному программированию в целом.
Например, скажем, у вас есть следующий экспресс-обработчик:
app.get("/test", function(req, res, next) {
require("fs").readFile("/some/file", function(err, data) {
if(err)
next(err);
else
res.send("yay");
});
});
Скажем, что файл "some/file" на самом деле не существует. В этом случае fs.readFile вернет ошибку в качестве первого аргумента методу обратного вызова. Если вы проверите это и сделаете следующее (ошибочно), когда это произойдет, обработчик ошибок по умолчанию будет захвачен и сделает все, что вы сделаете (например, верните 500 пользователю). Это грациозный способ обработки ошибки. Конечно, если вы забыли позвонить next(err)
, это не сработает.
Таким образом, что условие ошибки, с которым может справиться глобальный обработчик, рассмотрит другой случай:
app.get("/test", function(req, res, next) {
require("fs").readFile("/some/file", function(err, data) {
if(err)
next(err);
else {
nullObject.someMethod(); //throws a null reference exception
res.send("yay");
}
});
});
В этом случае возникает ошибка, если ваш код приводит к вызову метода для нулевого объекта. Здесь будет выведено исключение, оно не будет захвачено глобальным обработчиком ошибок, и ваше приложение Node завершится. Все клиенты, которые в настоящее время выполняют запросы на эту услугу, внезапно отключились без объяснения причин. Неловкий.
В настоящее время нет глобальных функций обработчика ошибок в Node для обработки этого случая. Вы не можете помещать гигантский try/catch
вокруг всех ваших обработчиков, потому что к моменту выполнения вашего обратного вызова asyn эти блоки try/catch
больше не находятся в области видимости. Это просто характер асинхронного кода, он разбивает парадигму обработки ошибок try/catch.
AFAIK, ваш единственный ресурс здесь состоит в том, чтобы поместить блоки try/catch
вокруг синхронных частей вашего кода внутри каждого из ваших асинхронных обратных вызовов, примерно так:
app.get("/test", function(req, res, next) {
require("fs").readFile("/some/file", function(err, data) {
if(err) {
next(err);
}
else {
try {
nullObject.someMethod(); //throws a null reference exception
res.send("yay");
}
catch(e) {
res.send(500);
}
}
});
});
Это сделает какой-нибудь неприятный код, особенно после того, как вы начнете получать вложенные асинхронные вызовы.
Некоторые люди думают, что то, что Node делает в этих случаях (то есть умереть), - это правильная вещь, потому что ваша система находится в противоречивом состоянии, и у вас нет другого выбора. Я не согласен с этими рассуждениями, но я не буду вдаваться в философские дебаты об этом. Дело в том, что с Node ваши варианты - это много маленьких блоков try/catch
или надеюсь, что ваше тестовое покрытие будет достаточно хорошим, чтобы этого не произошло. Вы можете поместить что-то вроде upstart или supervisor, чтобы перезапустить приложение, когда оно идет вниз, но это просто смягчение проблемы, а не решение.
Node.js имеет в настоящее время неустойчивую функцию, называемую домены, которая, как представляется, затрагивает эту проблему, хотя я мало что знаю о он.
Ответ 4
Я только что собрал класс, который слушает необработанные исключения, и когда он видит его:
- выводит трассировку стека на консоль.
- записывает в него собственный файл журнала
- отправляет вам трассировку стека
- перезагружает сервер (или убивает его, зависит от вас)
Это потребует небольшой настройки для вашего приложения, поскольку я пока не сделал его общим, но это всего лишь несколько строк, и это может быть то, что вы ищете!
Проверьте это!
Примечание: на данный момент это более 4 лет, незаконченное, и теперь может быть лучший способ - я не знаю!)
process.on
(
'uncaughtException',
function (err)
{
var stack = err.stack;
var timeout = 1;
// print note to logger
logger.log("SERVER CRASHED!");
// logger.printLastLogs();
logger.log(err, stack);
// save log to timestamped logfile
// var filename = "crash_" + _2.formatDate(new Date()) + ".log";
// logger.log("LOGGING ERROR TO "+filename);
// var fs = require('fs');
// fs.writeFile('logs/'+filename, log);
// email log to developer
if(helper.Config.get('email_on_error') == 'true')
{
logger.log("EMAILING ERROR");
require('./Mailer'); // this is a simple wrapper around nodemailer http://documentup.com/andris9/nodemailer/
helper.Mailer.sendMail("GAMEHUB NODE SERVER CRASHED", stack);
timeout = 10;
}
// Send signal to clients
// logger.log("EMITTING SERVER DOWN CODE");
// helper.IO.emit(SIGNALS.SERVER.DOWN, "The server has crashed unexpectedly. Restarting in 10s..");
// If we exit straight away, the write log and send email operations wont have time to run
setTimeout
(
function()
{
logger.log("KILLING PROCESS");
process.exit();
},
// timeout * 1000
timeout * 100000 // extra time. pm2 auto-restarts on crash...
);
}
);
Ответ 5
Была аналогичная проблема. Иво ответ хороший. Но как вы можете поймать ошибку в цикле и продолжить?
var folder='/anyFolder';
fs.readdir(folder, function(err,files){
for(var i=0; i<files.length; i++){
var stats = fs.statSync(folder+'/'+files[i]);
}
});
Здесь fs.statSynch выдает ошибку (против скрытого файла в Windows, который barfs я не знаю почему). Ошибка может быть поймана методом process.on(...), но цикл останавливается.
Я попытался добавить обработчик напрямую:
var stats = fs.statSync(folder+'/'+files[i]).on('error',function(err){console.log(err);});
Это тоже не сработало.
Добавление try/catch вокруг сомнительного fs.statSynch() было лучшим решением для меня:
var stats;
try{
stats = fs.statSync(path);
}catch(err){console.log(err);}
Затем это привело к исправлению кода (создание чистого пути var из папки и файла).
Ответ 6
Я нашел PM2 как лучшее решение для обработки серверов node, одиночных и нескольких экземпляров
Ответ 7
Одним из способов сделать это будет откручивание дочернего процесса и общение с родительским процессом через событие "сообщение".
В дочернем процессе, где возникает ошибка, поймите это с помощью 'uncaughtException', чтобы избежать сбоя приложения. Учтите, что Исключения, выброшенные из обработчика событий не будут обнаружены. Как только ошибка будет обнаружена безопасно, отправьте сообщение, например: {закончить: ложь}.
Родительский процесс будет прослушивать событие сообщения и снова отправить сообщение дочернему процессу для повторного запуска функции.
Ребенок:
// In child.js
// function causing an exception
const errorComputation = function() {
for (let i = 0; i < 50; i ++) {
console.log('i is.......', i);
if (i === 25) {
throw new Error('i = 25');
}
}
process.send({finish: true});
}
// Instead the process will exit with a non-zero exit code and the stack trace will be printed. This is to avoid infinite recursion.
process.on('uncaughtException', err => {
console.log('uncaught exception..',err.message);
process.send({finish: false});
});
// listen to the parent process and run the errorComputation again
process.on('message', () => {
console.log('starting process ...');
errorComputation();
})
Родительский процесс:
// In parent.js
const { fork } = require('child_process');
const compute = fork('child.js');
// listen onto the child process
compute.on('message', (data) => {
if (!data.finish) {
compute.send('start');
} else {
console.log('Child process finish successfully!')
}
});
// send initial message to start the child process.
compute.send('start');