EggJS實現一個簡易的鏈路日誌,整合到kibana中

楊金凱發表於2019-02-27

專案背景

公司部署的kibana目前是從容器中的標準輸入輸出中採集日誌,目前還不支援指定檔案或者目錄,egg本身的日誌是基於檔案的,所以是沒發在kibana中檢視egg本身列印的日誌

標準輸入輸出

linux shell下常用輸入輸出操作符是:

1.  標準輸入   (stdin) :程式碼為 0 ,使用 < 或 << ; /dev/stdin -> /proc/self/fd/0   0代表:/dev/stdin 
2.  標準輸出   (stdout):程式碼為 1 ,使用 > 或 >> ; /dev/stdout -> /proc/self/fd/1  1代表:/dev/stdout
3.  標準錯誤輸出(stderr):程式碼為 2 ,使用 2> 或 2>> ; /dev/stderr -> /proc/self/fd/2 2代表:/dev/stderr
複製程式碼

node中console.log會輸出到stdout、console.error輸出到stderr,所以要想收集到日誌,那就要用console方式輸出,

但是node中console是一個同步操作,所以頻繁的執行console輸出到終端,會阻塞程式,影響效能,所以日誌輸出需要有一個合併操作,所以我需要自己寫一個個性化日誌輸出外掛,我最先想到的就是利用egg提供的擴充去做這件事

Egg 擴充

Egg 框架提供了多種擴充套件點擴充套件自身的功能:

Application
Context
Request
Response
Helper
在開發中,我們既可以使用已有的擴充套件 API 來方便開發,也可以對以上物件進行自定義擴充套件,進一步加強框架的功能。
複製程式碼

我選擇基於Context進行擴充,給他增加一個自定義Log方法,程式碼如下,可以根據自己的業務需求自行修改,檔案路徑為app/extend/context.js

module.exports = {
  logs: [],
  LogStart (text) {
    const time = new Date()
    this.logs = [{
      time,
      text: 'start print log'
    }]
  },
  Log (text) {
    const time = new Date()
    this.logs.push({
      time,
      text
    })
  },
  LogEnd (type) {
    const tranceId = new Date().getTime()
    const url = this.request.url
    const userId = this.request.header['user-id']
    let logObj = {
      tranceId,
      userId,
      url,
      steps: {}
    }
    this.logs.map((item, index) => {
      let timeStr = item.time.toLocaleString() + ' ' + item.time.getMilliseconds() + 'ms'
      logObj.steps[index] = `< ${timeStr} > - ${item.text}`
    })
    const useTime = new Date().getTime() - new Date(this.logs[0].time).getTime()
    logObj.useTime = `${useTime} ms`
    if (type === 'error') {
      console.error('System Error:' + JSON.stringify(logObj))
    } else {
      console.log(JSON.stringify(logObj))
    }
  }
} 
複製程式碼

egg日誌列印策略

通常 Web 訪問是高頻訪問,每次列印日誌都寫磁碟會造成頻繁磁碟 IO,為了提高效能,我們採用的檔案日誌寫入策略是: 日誌同步寫入記憶體,非同步每隔一段時間(預設 1 秒)刷盤

所以我在日誌呼叫結束會將日誌的完整鏈路一次全部打出,防止頻繁呼叫,只輸出一條記錄,在kibana中方便檢視

這樣我們在中介軟體中就可以這樣呼叫

module.exports = function (options) {
    return async function proxy(ctx, next) {
        await next()
        ctx.LogStart()
        ctx.Log('你要列印的日誌!')
        ctx.Log('執行A操作!')
        ctx.Log('執行B操作!')
        ctx.LogEnd()
    };
};
複製程式碼

最後在kibana可以看到下面這樣

EggJS實現一個簡易的鏈路日誌,整合到kibana中
kibana
整個鏈路的日誌都能統一輸出
EggJS實現一個簡易的鏈路日誌,整合到kibana中
kibana

以上是個人在專案中的一次小的嘗試,如有問題,還請指出,謝謝

相關文章