提问人:RobKohr 提问时间:11/18/2010 更新时间:12/26/2017 访问量:65406
使节点 .js 在出错时不退出
Make node.js not exit on error
问:
我正在使用 Socket.IO 在面向 websocket 的节点.js服务器上工作。我注意到一个错误,即某些浏览器没有遵循正确的服务器连接过程,并且编写代码不是为了优雅地处理它,简而言之,它调用了一个从未设置过的对象的方法,从而由于错误而杀死了服务器。
我特别关心的不是错误,而是当发生此类错误时,整个服务器都会关闭。我是否可以在节点的全局级别上做些什么来使它发生,因此如果发生错误,它只会记录一条消息,也许会终止事件,但服务器进程将继续运行?
我不希望其他用户的连接因为一个聪明的用户利用了大型代码库中未捕获的错误而中断。
答:
您可以将侦听器附加到进程对象的事件。uncaughtException
代码取自实际的 Node.js API 参考(它是“进程”下的第二项):
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
评论
err.stack
有类似的问题。伊沃的回答很好。但是,如何捕获循环中的错误并继续呢?
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 中的一个隐藏文件,我不知道为什么)。process.on(...) 技巧可以捕获错误,但循环会停止。
我尝试直接添加一个处理程序:
var stats = fs.statSync(folder+'/'+files[i]).on('error',function(err){console.log(err);});
这也没有用。
在有问题的 fs.statSynch() 周围添加一个 try/catch 对我来说是最好的解决方案:
var stats;
try{
stats = fs.statSync(path);
}catch(err){console.log(err);}
这导致了代码修复(从文件夹和文件创建一个干净的路径 var)。
评论
stat
readdir
我刚刚整理了一个类,它监听未经处理的异常,当它看到一个时,它:
- 将堆栈跟踪打印到控制台
- 将其记录在它自己的日志文件中
- 通过电子邮件将堆栈跟踪发送给您
- 重新启动服务器(或终止它,由您决定)
它需要对您的应用程序进行一些调整,因为我还没有让它变得通用,但它只有几行,它可能就是你要找的!
注意:此时这已经超过 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...
);
}
);
评论
使用 uncaughtException 是一个非常糟糕的主意。
最好的替代方法是使用 Node.js 0.8 中的域。如果您使用的是早期版本的 Node.js,请使用 forever 来重新启动进程,或者最好使用节点集群生成多个工作进程,并在发生 uncaughtException 事件时重新启动工作进程。
寄件人: http://nodejs.org/api/process.html#process_event_uncaughtexception
警告:正确使用“uncaughtException”
请注意,“uncaughtException”是一种粗略的异常处理机制,仅用作最后的手段。该事件不应用作 On Error Resume Next 的等效事件。未经处理的异常本质上意味着应用程序处于未定义的状态。尝试在未正确从异常中恢复的情况下恢复应用程序代码可能会导致其他不可预见和不可预测的问题。
不会捕获从事件处理程序中引发的异常。相反,该进程将退出,并带有非零退出代码,并且将打印堆栈跟踪。这是为了避免无限递归。
在未捕获的异常后尝试恢复正常可能类似于升级计算机时拔出电源线 - 十次中有九次没有任何反应 - 但第 10 次,系统会损坏。
“uncaughtException”的正确用法是在关闭进程之前对分配的资源(例如文件描述符、句柄等)执行同步清理。在“uncaughtException”之后恢复正常操作是不安全的。
若要以更可靠的方式重新启动崩溃的应用程序,无论是否发出 uncaughtException,都应在单独的进程中使用外部监视器来检测应用程序故障,并根据需要进行恢复或重新启动。
评论
我刚刚对此进行了大量研究(请参阅此处,此处,此处和此处),对您的问题的回答是,Node不允许您编写一个错误处理程序来捕获系统中可能发生的每个错误场景。
某些框架(如 express)允许您捕获某些类型的错误(当异步方法返回错误对象时),但还有其他条件无法使用全局错误处理程序捕获。这是 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 将返回一个错误作为回调方法的第一个参数。如果您检查并在发生时执行 next(err),则默认的 express 错误处理程序将接管并执行您让它执行的任何操作(例如,将 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");
}
});
});
在这种情况下,如果代码导致对 null 对象调用方法,则存在 bug。这里将抛出一个异常,它不会被全局错误处理程序捕获,并且您的节点应用程序将终止。当前在该服务上执行请求的所有客户端都将突然断开连接,并且无法解释原因。不优雅。
Node 中目前没有全局错误处理程序功能来处理这种情况。你不能在所有 express 处理程序周围放置一个巨型,因为当你的 asyn 回调执行时,这些块不再在范围内。这就是异步代码的本质,它打破了 try/catch 错误处理范式。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,您的选择是很多小块,或者希望您的测试覆盖率足够好,以免发生这种情况。你可以设置一些类似 upstart 或 supervisor 的东西,以便在应用程序出现故障时重新启动它,但这只是缓解问题,而不是解决方案。try/catch
Node.js 有一个当前不稳定的功能,称为域,似乎可以解决这个问题,尽管我对此知之甚少。
评论
我发现 PM2 是处理节点服务器、单个实例和多个实例的最佳解决方案
一种方法是旋转子进程,并通过“message”事件与父进程通信。
在发生错误的子进程中,使用“uncaughtException”捕获该错误,以避免应用程序崩溃。请注意,不会捕获从事件处理程序中抛出的异常。安全捕获错误后,发送如下消息:{finish: false}。
父进程将侦听消息事件,并再次将消息发送到子进程以重新运行该函数。
子进程:
// 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');
评论