使node.js在出错时不退出

时间:2010-11-18 09:34:55

标签: node.js

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

我关心的不是特别是bug,而是当发生此类错误时,整个服务器都会崩溃。我可以在节点的全局级别上做任何事情来做到这一点,如果发生错误,它只会记录一条消息,可能会终止事件,但服务器进程会继续运行吗?

我不希望其他用户的连接因为一个聪明的用户利用大量包含的代码库中的未被捕获的错误而停止运行。

7 个答案:

答案 0 :(得分:71)

您可以将侦听器附加到进程对象的uncaughtException事件。

取自实际Node.js API reference的代码(这是“流程”下的第二项):

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.');

现在你要做的就是记录它或用它做一些事情,如果你知道在什么情况下发生了bug,你应该在Socket.IO的GitHub页面上提交一个bug:
https://github.com/LearnBoost/Socket.IO-node/issues

答案 1 :(得分:30)

使用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的等效项。未处理的异常本身意味着应用程序处于未定义状态。尝试在没有从异常中正确恢复的情况下恢复应用程序代码可能会导致其他无法预料和不可预测的问题。

     

不会捕获从事件处理程序中抛出的异常。相反,该过程将以非零退出代码退出,并且将打印堆栈跟踪。这是为了避免无限递归。

     

在未被捕获的异常后尝试恢复正常可能类似于在升级计算机时拔出电源线 - 十分之九没有任何事情发生 - 但是第10次,系统已损坏。

     

正确使用'uncaughtException'是在关闭进程之前执行已分配资源(例如文件描述符,句柄等)的同步清理。在'uncaughtException'之后恢复正常操作是不安全的。

     

要以更可靠的方式重新启动崩溃的应用程序,无论是否发出uncaughtException,都应在单独的进程中使用外部监视器来检测应用程序故障并根据需要进行恢复或重新启动。

答案 2 :(得分:6)

我刚刚对此进行了大量研究(请参阅herehereherehere),您问题的答案是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将返回错误作为回调方法的第一个参数。如果你检查它并在它发生时做下一个(错误),默认的快速错误处理程序将接管并做你做的任何事情(例如,向用户返回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对象上调用方法,则会出现错误。这里将抛出异常,它不会被全局错误处理程序捕获,并且您的节点应用程序将终止。当前正在执行该服务请求的所有客户端将突然断开连接,无法解释原因。非正常。

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块,或者希望您的测试覆盖率足够好,以免发生这种情况。您可以使用upstartsupervisor之类的内容来重新启动应用,但这只是缓解问题,而不是解决方案。

Node.js目前有一个名为domains的不稳定功能似乎可以解决这个问题,但我对此并不了解。

答案 3 :(得分:2)

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

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

这需要对你的应用程序进行一些调整,因为我还没有把它变成通用的,但它只是几行,它可能就是你正在寻找的东西!

Check it out!

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

答案 4 :(得分:1)

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

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);});

这也不起作用。

在可疑的fs.statSynch()周围添加一个try / catch对我来说是最好的解决方案:

var stats;
try{
    stats = fs.statSync(path);
}catch(err){console.log(err);}

然后导致代码修复(从文件夹和文件中创建一个干净的路径var)。

答案 5 :(得分:0)

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

答案 6 :(得分:0)

这样做的一种方法是旋转子进程并通过'message'事件与父进程通信。

在发生错误的子进程中,使用'uncaughtException'捕获它,以避免崩溃应用程序。 请注意事件处理程序will not be caught 中引发的异常。一旦安全捕获到错误,请发送消息,例如: {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');