<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Node.js 診斷指南 第一彈

          共 7296字,需瀏覽 15分鐘

           ·

          2022-01-10 05:46


          作者 | 冰森
          來源 | Node地下鐵
          https://mp.weixin.qq.com/s/gxEGrU9wnORfCmINW29dzA

          大廠技術(shù)  高級前端  Node進階

          點擊上方 程序員成長指北,關(guān)注公眾號

          回復(fù)1,加入高級Node交流群

          TL;DR

          本文介紹的診斷小技巧有:調(diào)試環(huán)境變量、進程退出碼、廢棄 API 警告、識別同步 I/O 和處理 Unhandled Promise Rejection。


          調(diào)試環(huán)境變量


          以筆者最初使用的 Node.js 版本 v0.10.x 來說,當(dāng)時內(nèi)置的 util 還沒有提供 util.debuglog 方法,只有一個的 util.debug 效果等同于 console.error,而在當(dāng)時已經(jīng)開始要流行的是使用 TJ 的 debug 包,例如:


          var debug = require('debug')('http')
          , http = require('http')
          , name = 'My App';

          // fake app

          debug('booting %o', name);

          http.createServer(function(req, res){
          debug(req.method + ' ' + req.url);
          res.end('hello\n');
          }).listen(3000, function(){
          debug('listening');
          });


          通過 DEBUG 環(huán)境變量指定了 http 這個 label,就可以把代碼中 label 為 http 的調(diào)試日志輸出:



          在 TJ 的設(shè)想里,只要開發(fā)者所寫的所有模塊及其依賴的模塊都使用這種方式,那么調(diào)試的時候大家都可以使用 DEBUG 這個 env 來過濾和開啟所有人的 debug 日志。


          由于 TJ 的這個模塊出來實在是太早了(10年前),所以基本上已經(jīng)成為了社區(qū)的一個默認(rèn)約定,大部分流行的 npm 包內(nèi)部都會使用這個模塊來輸出調(diào)試日志(而不是 util.debuglog)。


          以國內(nèi)的 eggjs 框架為例,我們直接啟動的日志如下:



          通過 DEBUG 環(huán)境變量可以開啟指定模塊的內(nèi)部調(diào)試日志:


          PS: 通過 DEBUG=* 就可以開啟所有的日志。


          實際上 Node.js 也內(nèi)置了類似的機制,不過目前普遍認(rèn)為 Node.js 內(nèi)置的環(huán)境變量主要是用來排查 Node.js 內(nèi)置的代碼和模塊用的。這里主要有兩個環(huán)境變量分別對應(yīng)了 Node.js 內(nèi)置的 JavaScript 層以及 C++ 層的調(diào)試日志,分別是:


          • NODE_DEBUG 用于開啟 JavaScript 調(diào)試日志開關(guān)(也包括用戶使用 util.debuglog 的部分)

          • NODE_DEBUG_NATIVE 用戶開啟 C++ 調(diào)試日志開關(guān)


          常見的 NODE_DEBUG 開關(guān):

          • timer

          • http

          • net

          • fs

          • cluster

          • tls

          • stream

          • child_process

          • module


          以上文中的 http 代碼為例,同時開啟 DEBUG 和 NODE_DEBUG 效果:


          上圖中,我們開啟了 Node.js 內(nèi)置的 net 模塊的日志初始,之后每次 http 請求進來,net 模塊的 debug 日志都會詳細(xì)輸出(由于日志內(nèi)容比較多所以沒有放出來請求的例子圖,大家可以自行測試)。


          進程退出碼


          有的時候我們的 Node.js 進程直接退出了,如果沒有收集到足夠錯誤日志,可以根據(jù)進程的退出碼輔助判斷錯誤情況。下面引用 Node.js 文檔中的內(nèi)容:


          在沒有更多異步操作的時候,Node.js 會直接 0 狀態(tài)代碼退出。在其他情況下使用以下狀態(tài)代碼:


          • 1 未捕獲的致命異常:存在未捕獲的異常,并且其沒有被 domain 模塊或 'uncaughtException' 事件處理器處理。

          • // 省略...

          • 6 空函數(shù)的內(nèi)部異常處理器:存在未捕獲的異常,然后內(nèi)部致命異常處理器由于不明原因設(shè)置為了 nullptr(空函數(shù)),即沒有辦法執(zhí)行默認(rèn)的致命異常處理。

          • 7 內(nèi)部異常處理器運行時失?。捍嬖谖床东@的異常,并且內(nèi)部致命異常處理函數(shù)本身在嘗試處理時拋出錯誤。例如,如果 'uncaughtException' 或 domain.on('error') 處理器拋出錯誤,就會發(fā)生這種情況。

          • // 省略..


          • >128 信號退出:如果 Node.js 收到致命的信號,例如 SIGKILL 或 SIGHUP,則其退出碼將是 128 加上信號代碼的值。這是標(biāo)準(zhǔn)的 POSIX 實踐,因為退出碼被定義為 7 位整數(shù),并且信號退出設(shè)置高位,然后包含信號代碼的值。例如,信號 SIGABRT 的值是 6,因此預(yù)期的退出碼將是 128 + 6 或 134。


          完整參見 https://nodejs.org/api/process.html#process_exit_codes。


          我們來舉個例子:


          // arr-crash.jsprocess.on('uncaughtException', () => {  console.error('Ya!');});
          const arr = [];while(true) arr.push(1);


          然后測試:




          在 Node.js 進程 crash 之后我們通過 echo $? 可以輸出之前進程崩潰之后的退出碼,上例中為 134。根據(jù)上文中的文檔,我們可以知道 134 = 128 + 6 即導(dǎo)致進程退出的異常類型為 6,參考文檔:


          • 6 空函數(shù)的內(nèi)部異常處理器:存在未捕獲的異常,然后內(nèi)部致命異常處理器由于不明原因設(shè)置為了 nullptr(空函數(shù)),即沒有辦法執(zhí)行默認(rèn)的致命異常處理。


          雖然無法根據(jù)錯誤碼知道本例中的進程 crash 的異常是什么,但是可以知道 crash 的時候進程是被強行 SIGKILL 或 SIGHUP 退出的,并且此時  V8 連默認(rèn)的異常處理器都用不了了(內(nèi)存爆了啥都做不了了)。


          再來一個測試列子:


          // uncaught-exception.jsprocess.on('uncaughtException', () => {  throw new Error('there..')});
          throw new Error('here..')


          測試情況:



          根據(jù)錯誤碼,我們可以找到文檔:


          • 7 內(nèi)部異常處理器運行時失?。捍嬖谖床东@的異常,并且內(nèi)部致命異常處理函數(shù)本身在嘗試處理時拋出錯誤。例如,如果 'uncaughtException' 或 domain.on('error') 處理器拋出錯誤,就會發(fā)生這種情況。


          可以發(fā)現(xiàn)跟我們測試的情況吻合 —— 在執(zhí)行致命異常處理器(uncaughtException handler)的時候拋錯導(dǎo)致異常無法處理然后進程退出。


          廢棄 API 警告


          Node.js 提供了官方的廢棄(deprecate)標(biāo)記,開發(fā)者也可以通過 util.deprecate 方法,將某些接口標(biāo)記為廢棄狀態(tài),例如:


          const util = require('util');
          exports.mergeData = util.deprecate(() => { // 之前的代碼}, 'mergeData() 已廢棄. 請使用 merge() ');


          我們也可以通過 --no-deprecations 來關(guān)閉平常使用過程中碰到的 deprecate 警告(不過不是很推薦)。


          有時候我們想找到拋這個警告的代碼位置在哪,那么可以使用 --trace-deprecation 來開啟 trace 錯誤棧,這樣在警告出來的時候就會附上具體的代碼 stack。如果更嚴(yán)格一些不希望代碼中出現(xiàn)使用廢棄版本的情況,那么可以考慮使用 --throw-deprecations 標(biāo)志,這樣使用廢棄 API 的地方會直接 throw error,例如:


          、


          識別同步 I/O 操作


          由于 Node.js 是單線程的,在使用提供服務(wù)的時候,如果出現(xiàn)了耗時過長的同步 I/O 操作,那么期間就會 block 住整個線程。為了避免這種情況我們可以使用 --trace-sync-io 標(biāo)志開啟 Node.js 內(nèi)置的同步 I/O 追蹤檢測功能。


          const { readFileSync } = require('fs');
          setImmediate(() => readFileSync(__filename));


          測試效果:



          Unhandled Promise Rejection


          測試代碼:


          const p = new Promise((resolve, reject) => {  // throw new Error(111); // 與下一行等效  reject(new Error(111));});


          測試效果


          (node:10142) UnhandledPromiseRejectionWarning: Error: 111
          at /Users/lellansinhuang/workspace/midwayjs-tutorial/tmp/reject.js:4:10
          at new Promise (<anonymous>)
          at Object.<anonymous> (/Users/lellansinhuang/workspace/midwayjs-tutorial/tmp/reject.js:2:11)
          at Module._compile (internal/modules/cjs/loader.js:1147:30)
          at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10)
          at Module.load (internal/modules/cjs/loader.js:996:32)
          at Function.Module._load (internal/modules/cjs/loader.js:896:14)
          at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
          at internal/main/run_main_module.js:17:47
          (node:10142) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)


          JavaScript 的 Promise 有三種狀態(tài),分別是 pending、fulfilled 和 rejected。當(dāng)一個 promise 的 rejected 狀態(tài)沒有后續(xù)處理的時候就會觸發(fā)上述報錯。也就是說如果上面的代碼加上,如 p.catch(console.log) 這樣的捕獲異常邏輯就不會出現(xiàn) UnhandledPromiseRejectionWarning 警告。


          Unhandled Promise Rejection 是在 Node.js v12 時引入的新特性。不處理 Promies 的這種狀態(tài)在瀏覽器中一種可以接受的行文,但是在服務(wù)端則不然,因為這種報錯可能導(dǎo)致內(nèi)存泄漏。為了避免這種問題,可以了解一下 --unhandled-rejections 的幾種設(shè)置:


          • throw: 觸發(fā)一個 unhandledRejection 事件,你可以通過 promise.on('unhandledRejection') 來監(jiān)聽,如果你沒有監(jiān)聽,那么會當(dāng)成一個未捕獲的 error 拋出。(默認(rèn)行為)

          • strict: 直接拋 error。

          • warn: 不論是否設(shè)置了監(jiān)聽行為,總是產(chǎn)生一個警告。

          • warn-with-error-code: 觸發(fā) unhandledRejection 事件,如果沒有監(jiān)聽,觸發(fā)一個警告,并把進程退出碼設(shè)置為 1。

          • none: 靜默所有警告。


          我們將上文中的測試改為使用 strict 模式,則可以得到如下報錯:


          $ node --unhandled-rejections=strict reject.js
          /Users/lellansinhuang/workspace/midwayjs-tutorial/tmp/reject.js:4
          reject(new Error(111));
          ^

          Error: 111
          at /Users/lellansinhuang/workspace/midwayjs-tutorial/tmp/reject.js:4:10
          at new Promise (<anonymous>)
          at Object.<anonymous> (/Users/lellansinhuang/workspace/midwayjs-tutorial/tmp/reject.js:2:11)
          at Module._compile (internal/modules/cjs/loader.js:1147:30)
          at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10)
          at Module.load (internal/modules/cjs/loader.js:996:32)
          at Function.Module._load (internal/modules/cjs/loader.js:896:14)
          at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
          at internal/main/run_main_module.js:17:47


          通過 strict 讓進程直接 crash 或者流程報錯 block 住,這是一種 let it crash 的思想。

          Node 社群


          我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。


             “分享、點贊、在看” 支持一波??

          瀏覽 89
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  99热这里只有精品首页 | 熟女视频大香蕉视频大香蕉视频大香蕉 | A片黄色视频免费 | 影音先锋AV麻豆啪啪资源网 | 国产双飞视频 |