<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 中正確的使用日志對象

          共 4383字,需瀏覽 9分鐘

           ·

          2021-08-20 10:27

          點擊上方 程序員成長指北,關注公眾號

          回復1,加入高級Node交流群

          作者:張挺(作者授權轉載)

          地址:https://mp.weixin.qq.com/s/Pb51aYdrxAALM_wR4asDgg


          日志,是開發(fā)者排查問題的非常重要的手段,有時候甚至是唯一的,所以如何合理并正確的打印日志,成了開發(fā)時的重中之重。


          Node.js 中打日志的方式,一般有幾種:


          • 1、主動展示

          • 2、被動記錄


          這兩種方式都可以由不同的模塊來實現(xiàn),我們接下去就來看看怎么選擇。


          常見的主動展示


          一般來說,主動一般發(fā)生在開發(fā)期,不確定狀態(tài)的時候,我們會打印一些消息,比如常見的。

          console.log('hello world');

          這就是最簡單的主動打印的例子。


          但是大多數(shù)場景下,我們都不會使用 console 來進行打印,畢竟除了內置之外,在性能和功能方面沒有特別的優(yōu)勢。


          除了大眾都知道的 console 模塊,在 Node.js 領域還有一個較為知名的 debug 模塊。

          可以根據(jù)命名空間打印出不同顏色的輸出,但是最最有用的,則是他的環(huán)境變量控制能力。


          默認情況下(不包含任何環(huán)境變量),控制臺不會有任何輸出,而當 DEBUG 環(huán)境變量被賦值的時候,對應的命名空間的輸出才會被打印到 stdout。


          $ DEBUG=* node app.js


          由于 debug 模塊由 TJ 出品,并且在非常早的時候就投入,使用過于廣泛,至今仍有非常多的模塊使用了它。


          Node.js 官方一直希望能夠內置一個 debug 模塊。從 v0.11.3 開始,終于加上了一個 util.debuglog 方法。


          它的功能和 debug 模塊類似,同時是內置的模塊,所以逐步也有一些模塊開始過渡到它。

          const util = require('util');const debuglog = util.debuglog('foo');
          debuglog('hello from foo [%d]', 123);

          它的開關也類似,使用的是 NODE_DEBUG 環(huán)境變量,應該是特意和 debug 模塊做了區(qū)分。

          $ NODE_DEBUG=foo node app.js

          被動記錄的方式


          除了上面提到的類 console 等方式,我們常見的就是各種日志庫默認記錄的日志,由于這些日志平時只是默默的記錄,并不會過多關注,只會在特殊需要的時候(比如差錯,定位,計算時)才會查看,所以我們歸類為 “被動的方式”。


          大多的三方庫都有類似的功能,比如 log4j,winston,pino 等等。


          這些庫的核心功能一般是:


          • 1、將日志輸出到不同的渠道(比如控制臺、文本文件)

          • 2、日志格式的自定義(文本或者 JSON)

          • 3、日志的輸出等級(warn,debug,error)

          • 4、其他的一些能力,比如切割和文件輪轉,壓縮等等


          這些庫用起來一般就比較簡單,獲取實例,調用方法輸出即可。

          logger.info('hello world');

          注意,這里我們會觀察到輸出有一些不一樣的地方。

          2021-07-22 14:50:59,388 INFO 7739 [xxx] hello world

          整個日志是安裝上面類似標準的結構來進行輸出的,計算是 Error,也是相同的類似格式,那么這個結構包含了哪幾部分東西呢?

          日志格式


          其實整個日志格式追溯,可以到很久以前,不管是 JAVA 默認的 Simple Logger 結構還是類似 nginx 等反向代理服務器的日志,都會包含一些固定的字段,這些固定的字段長久以來形成了一種輸出約定,將這些字段組合起來,形成了當今的日志格式。


          當前的日志格式一般會包括幾個部分。


          • 時間戳

          • 日志等級

          • 進程id(node)

          • 日志的標簽(label,from xxx class)

          • 消息體(字符串或者 error stack)


          除此之外,可能還有一些自定義的內容,比如執(zhí)行消耗的時間,用戶 id,文本長度等等內容。


          在文本結構的輸出中,這些字段將被空格(space)分隔,以換行符作為結尾(\n),這樣可以方便外部的日志采集系統(tǒng)采集,比如阿里云的 SLS 等等。


          每個公司會有自己的日志采集和輸出規(guī)范,所以一般常見的庫都會支持自定義的日志格式,但是不管如何變化,基礎的字段(上述)都還會存在。


          隨著系統(tǒng)的迭代,先進使用 JSON 格式來記錄日志的方式也逐步出現(xiàn),以 Logstash 為首的一些數(shù)據(jù)(日志)采集分析一體的工具,也逐步的成熟,對結構化的數(shù)據(jù)支持的也很好,所以現(xiàn)在常見的庫也會同步支持 JSON 格式輸出。


          正確的打日志


          在了解了基本的日志庫和體系之后,我們來具體看一看真正打日志的問題。


          比如一個簡單調用遠端服務:

          async invokeRemoteAPI() {  const result = await remoteService.got();  return {    result  };}

          一般,我們會有意識的加上錯誤處理。

          async invokeRemoteAPI() {    try {    const result = await remoteService.got();  } catch(err) {    logger.error('got a error, err=', err);    throw err;  }    return {    result  };}

          按照上面的標準格式,這個 logger 還需要其他的一些額外信息,比如:

          async invokeRemoteAPI() {    const pid = process.pid;  const startTime = Date.now();    try {    const result = await remoteService.got();  } catch(err) {    const endTime = Date.now();    logger.error('pid=%s, rt=%s, got a error, err=', pid, Date.now() - startTime, err);    throw err;  }    return {    result  };}


          如果每個代碼都這么寫,就會變得無比冗余,所以,我們會提前將日志的輸出格式定義完畢,這樣,在實際輸出的時候就可以簡化,比如:

          const logger = new CustomLogger({  format: '${timestamp} ${level} ' + process.pid + ${rt}'});
          async invokeRemoteAPI() { const startTime = Date.now(); try { const result = await remoteService.got(); } catch(err) { const endTime = Date.now(); logger.error('got a error, err=', err, { rt: Date.now() - startTime }); throw err; } return { result };}


          所以在特定場景下,如果有固定的日志字段,在日志庫允許自定義的情況下,可以先定義好固定的日志格式。


          上下文日志


          除了最簡單的通用日志輸出之外,還有一種相對復雜的日志,我們稱之為和上下文(請求)綁定的日志,這類日志會輸出上下文相關聯(lián)的數(shù)據(jù),比如之前示例中的響應時間,用戶請求的 ip,請求的路由,甚至是鏈路的唯一 ID 等等。


          比如:

          2021-07-22 14:50:59,388 INFO 7739 [-/127.0.0.1/-/0ms GET /] hello world

          這種情況下,再用普通日志的方式加入?yún)?shù)就不合適了。


          當然,有些同學會說,我們直接定義一個新的,比如:

          class CustomCtxLogger extends CustomLogger {  constructor(ctx, format) {    this.ctx = ctx;    this.format = format;  }    error(...args) {    //xxx  }    info(...args) {    //xxx  }}

          這樣的做法,每次都會讓基類做初始化,會影響部分性能。我們使用另一種方式來減少性能影響,代理傳統(tǒng)日志。


          我們來看看最簡單的實現(xiàn)方式,以 koa 為例。

          // 普通日志const logger = new CustomLogger();
          class CtxLogger { constructor(ctx, logger) { this.ctx = ctx; this.logger = logger; } format() { return '${timestamp} ${level} ' + process.pid + '[${ctx.refer} ${ctx.rt}]' }}
          app.use(async (ctx, next) => { // 代理原始日志 const ctxLogger = new CtxLogger(ctx, logger); ctx.logger = ctxLogger; await next();});


          類似這種通過代理原始日志的方式,即減少了每次初始化新日志時的性能問題,又解決了 ctx 上字段透傳的問題。


          這也是常見的上下文日志的實踐。

          簡單總結一下


          我們了解了常用的日志庫以及和日志打印的關系,也簡單的實現(xiàn)了日志庫以及上下文日志的實現(xiàn),是不是現(xiàn)在對日志打印了有了一個基本的了解?


          這樣一套下來,相信你對 Node.js 打印日志的方式更加的了解,也在排錯時游刃有余了。

          Node 社群


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


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

          瀏覽 42
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  天堂网在线视频看看 | 日韩一页 | 亚洲23p | 久久一区二区三区四区 | 国产精品成人无码a无码 |