使节点 .js 在出错时不退出

Make node.js not exit on error

提问人:RobKohr 提问时间:11/18/2010 更新时间:12/26/2017 访问量:65406

问:

我正在使用 Socket.IO 在面向 websocket 的节点.js服务器上工作。我注意到一个错误,即某些浏览器没有遵循正确的服务器连接过程,并且编写代码不是为了优雅地处理它,简而言之,它调用了一个从未设置过的对象的方法,从而由于错误而杀死了服务器。

我特别关心的不是错误,而是当发生此类错误时,整个服务器都会关闭。我是否可以在节点的全局级别上做些什么来使它发生,因此如果发生错误,它只会记录一条消息,也许会终止事件,但服务器进程将继续运行?

我不希望其他用户的连接因为一个聪明的用户利用了大型代码库中未捕获的错误而中断。

节点 .js

评论

11赞 Merc 11/23/2017
这是 Node 真正困扰我的部分。我真的无法忍受 PHP,但至少 PHP 出现错误并不意味着整个网站都关闭了......

答:

92赞 Ivo Wetzel 11/18/2010 #1

您可以将侦听器附加到进程对象的事件。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

评论

0赞 RobKohr 11/20/2010
好吧,几乎完美。太糟糕了,它没有给出发生错误的行号。
7赞 Ivo Wetzel 11/20/2010
您可以打印出来,这将为您提供一个堆栈跟踪,其中也恰好包括行号。err.stack
0赞 StapleGun 4/20/2012
404 状态代码用于 HTTP,这个问题是关于套接字服务器的,所以虽然你可以随心所欲地处理错误,但 404 代码在这里没有意义。但是,如果您通过 node.js 运行 HTTP 服务器,那么如果您将该逻辑构建到错误处理程序中,则肯定会将错误 404 返回给客户端。
5赞 Rudolf Meijering 11/8/2012
请注意,这是一种不好的做法,可能会导致意外行为。请参阅下面的回答。
0赞 arcsin 9/10/2022
var 进程 = require('node:process');
1赞 KTys 1/2/2012 #2

有类似的问题。伊沃的回答很好。但是,如何捕获循环中的错误并继续呢?

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)。

评论

0赞 whitfin 10/22/2015
您应该使用 的异步版本,然后像您一样处理错误 - 您必须使用一些递归循环而不是 for。它具有与 相同的接口,它以 arg 的形式返回一个错误(顺便说一句,您在代码片段中忽略了它)。statreaddir
2赞 Dean Rather 5/16/2012 #3

我刚刚整理了一个类,它监听未经处理的异常,当它看到一个时,它:

  • 将堆栈跟踪打印到控制台
  • 将其记录在它自己的日志文件中
  • 通过电子邮件将堆栈跟踪发送给您
  • 重新启动服务器(或终止它,由您决定)

它需要对您的应用程序进行一些调整,因为我还没有让它变得通用,但它只有几行,它可能就是你要找的!

一探究竟!

注意:此时这已经超过 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...
        );
    }
);

评论

0赞 Raf 1/24/2016
你在 Github 上的班级怎么了?它给出页面未找到错误。
1赞 Dean Rather 2/11/2016
@Raf对不起那个冠军!一定是在我过去 4 年所做的一些要点清除中删除的......更新了我希望是文件:)的正确版本
0赞 Raf 2/11/2016
感谢您的回答更新。有趣的是,它也可以以这种方式完成。我所做的是,让 winston 负责捕获 uncaughtExceptions 并使用其邮件传输发送电子邮件,并使用 pm2 或 forever 等模块来重新启动节点实例。
37赞 Rudolf Meijering 10/24/2012 #4

使用 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,都应在单独的进程中使用外部监视器来检测应用程序故障,并根据需要进行恢复或重新启动。

评论

0赞 maxdec 6/28/2013
这是一个比上面的答案要好得多的答案(在错误时杀死进程(幻灯片 28))
22赞 guy mograbi 4/27/2014
我认为这个答案需要一些关于如何使用域以及它们如何解决这个问题的例子。
0赞 inf3rno 3/9/2015
它有自己的风险...... ;-)
2赞 guy mograbi 3/14/2015
对不起,仍然不同意你对原因的回答。你没有解释为什么域名使用更好。原因是 - 它不是......域使用仅允许您包装较小的代码部分(而不是整个过程)以捕获未捕获的异常。但是,如果整个代码都位于域中,则它与未捕获的异常相同。所以根据这个问题,你的回答“这不好,这很好”在我看来无关紧要。nodejs.org/api/domain.html#domain_warning_don_t_ignore_errors
2赞 Rudolf Meijering 3/16/2015
未定义的异常会使您的应用程序处于未定义的状态,“解决”此问题的唯一好方法是重新启动它。域的作用是更好地包含这种未定义的状态。在未捕获的异常之后,必须通过返回错误并停止进一步的代码执行来“重新启动”特定域。因此,未捕获的异常仍然很糟糕,但域限制了它们的影响,并可能允许您丢弃更少的数据。例如,您可以丢弃生成未捕获异常的传入请求并返回错误,但其他 100 个待处理请求仍然可以继续。
8赞 d512 8/27/2013 #5

我刚刚对此进行了大量研究(请参阅此处,此处,此处和此处),对您的问题的回答是,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/catchtry/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,您的选择是很多小块,或者希望您的测试覆盖率足够好,以免发生这种情况。你可以设置一些类似 upstartsupervisor 的东西,以便在应用程序出现故障时重新启动它,但这只是缓解问题,而不是解决方案。try/catch

Node.js 有一个当前不稳定的功能,称为,似乎可以解决这个问题,尽管我对此知之甚少。

评论

0赞 Green 8/18/2015
是否有适当的方法来处理此类错误,例如,在 Python 中?还是咕噜咕噜?或者因为这些语言是同步的,所以它们没有这些问题?
0赞 Mladen Janjetovic 1/30/2017 #6

我发现 PM2 是处理节点服务器、单个实例和多个实例的最佳解决方案

0赞 Amandeep Singh 10/6/2017 #7

一种方法是旋转子进程,并通过“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'); 

评论

0赞 sirandy 10/6/2017
请解释您的代码,以便更好地理解您的方法。