2010-11-18 78 views
44

我正在使用Socket.IO來處理面向websocket的node.js服務器。我注意到某些瀏覽器沒有遵循正確的連接過程到服務器的錯誤,並且代碼沒有被寫入以優雅地處理它,並且簡而言之,它調用了從未設置的對象的方法,從而導致服務器由於錯誤。使node.js不能在出錯時退出

我特別關心的不是bug,而是當發生這樣的錯誤時,整個服務器都關閉了。有什麼我可以在節點上的全局級別上做的事情,如果發生錯誤,它只會記錄一條消息,或許會終止事件,但服務器進程將繼續運行?

我不希望其他用戶的連接斷開,因爲一個聰明的用戶利用大型包含代碼庫中的未捕獲錯誤。

+0

這是_really_困擾我節點的一部分。我不能真的胃,但PHP至少有一個錯誤並不意味着_whole_網站關閉... – Merc 2017-11-23 15:08:39

回答

61

您可以將偵聽程序附加到過程對象的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.'); 

你要做的,現在要做的就是登錄,或用它做什麼,如果你知道在什麼情況下發生這個錯誤,你應該在Socket.IO的GitHub頁面上提交一個bug:
https://github.com/LearnBoost/Socket.IO-node/issues

+1

真棒,謝謝伊沃,這是完美的! – RobKohr 2010-11-19 14:54:01

+0

好吧,幾乎完美。太糟糕了,它不會給出錯誤發生的行號。 – RobKohr 2010-11-20 00:14:52

+4

你可以打印出'err.stack',這會給你一個堆棧跟蹤,其中也包括行號。 – 2010-11-20 11:36:50

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

這也不能工作。

添加一個try/catch周圍的可疑fs.statSynch()是最好的解決方案我:

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

這就導致了代碼修復(使來自文件夾和文件乾淨的路徑變量)。

+0

您應該使用'stat'的異步版本,然後像處理錯誤一樣處理錯誤 - 您必須使用一些遞歸循環而不是a。它和'readdir'具有相同的接口,這會讓你返回一個arg錯誤(順便說一句,你忽略了代碼段中的錯誤)。 – whitfin 2015-10-21 17:48:26

2

我只是拼湊一個偵聽未處理的異常,而一個類時,它看到的一個是:

  • 打印堆棧跟蹤到控制檯
  • 其記錄在自己的日誌文件
  • 發電子郵件給你堆棧跟蹤
  • 重新啓動服務器(或殺死它,給你)

這將需要一點點TWE請求你的應用程序,因爲我還沒有把它作爲通用的,但它只是幾行,它可能是你正在尋找的!

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... 
     ); 
    } 
); 
+0

你的班級在Github上發生了什麼?這是給頁面找不到錯誤。 – Raf 2016-01-24 14:12:05

+1

@Raf對不起那個冠軍!在過去的4年中,我已經完成了某些清除任務的清除過程中必須刪除的內容...已更新,我希望是該文件的正確版本:) – 2016-02-11 07:20:56

+0

感謝您的答案中的更新。有趣的是知道它也可以這樣做。我所做的是,讓winston負責捕獲uncaughtExceptions並使用其郵件傳輸來發送電子郵件和使用模塊(如pm2或永遠)來重新啓動節點實例。 – Raf 2016-02-11 12:06:49

29

使用! uncaughtException是一個非常糟糕的主意。

最好的選擇是在Node.js 0.8中使用域。如果您使用的是早期版本的Node.js,而是使用forever重新啓動您的進程,或者甚至更好地使用node cluster來產生多個工作進程,並在發生uncaughtException事件時重新啓動worker。

來源:http://nodejs.org/api/process.html#process_event_uncaughtexception

警告:使用「uncaughtException」正確

注意「uncaughtException」是打算異常處理粗機制,只能作爲最後的手段。該事件不應等同於「錯誤繼續下一步」。未處理的異常本質上意味着應用程序處於未定義狀態。嘗試恢復應用程序代碼而不正確地從異常恢復可能會導致額外的無法預料的和不可預知的問題。

從事件處理程序中拋出的異常不會被捕獲。相反,該進程將以非零退出代碼退出,並且將打印堆棧跟蹤。這是爲了避免無限遞歸。

嘗試正常恢復後,未捕獲的異常可能類似於升級電腦時拔出電源線 - 十次中有九次沒有任何反應 - 但第10次,系統損壞。

'uncaughtException'的正確使用是在關閉進程之前對分配的資源(例如文件描述符,句柄等)執行同步清理。 'uncaughtException'後恢復正常操作是不安全的。

爲了以更可靠的方式重新啓動崩潰的應用程序,uncaughtException是否被髮出與否,外部監視器應在一個單獨的處理,以檢測應用程序故障和恢復或者根據需要重新啓動被採用。

+0

這是一個好得多的答案,比頂部的更好([殺死進程出錯](http://fr.slideshare.net/the_undefined/nodejs-best-practices-10428790)(幻燈片28)) – maxdec 2013-06-28 15:01:14

+15

我認爲這個回答需要一些關於如何使用域的例子,以及他們如何解決這個問題。 – 2014-04-27 11:40:31

+0

它有它自己的風險... ;-) – inf3rno 2015-03-09 00:02:42

6

我只是做了這一堆的研究(見herehereherehere)和回答你的問題是,節點不會讓你寫一個錯誤處理程序,將捕獲每個錯誤場景這可能會在您的系統中發生。

一些框架如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"); 
    }); 
}); 

假設文件「一些/文件」實際上並不存在。在這種情況下,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中目前沒有全局錯誤處理函數來處理這種情況。你不能在你的快遞處理程序周圍放置一個巨大的try/catch,因爲當你的asyn回調執行的時候,那些try/catch塊不再在範圍內。這只是異步代碼的本質,它打破了try/catch錯誤處理範例。

據我所知,你唯一的辦法在這裏是把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這樣的地方重新啓動你的應用程序,但這只是緩解問題,而不是解決方案。

Node.js有一個當前不穩定的功能,名爲domains,似乎解決了這個問題,但我對此不太瞭解。

+0

是否有適當的方法來處理這種錯誤,例如,在Python中?還是Golang?或者因爲這些語言是同步的,他們沒有這些問題? – Green 2015-08-18 03:40:08

0

我發現PM2作爲處理節點服務器,單個和多個實例

這樣做將紡紗子進程,並通過「信息」事件父進程通信的
0

一種方法最好的解決方案。

在發生錯誤的子進程中,使用'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'); 
+0

請解釋你的代碼,以便更好地理解你的方法。 – sirandy 2017-10-05 23:14:44