專案背景
公司部署的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可以看到下面這樣
整個鏈路的日誌都能統一輸出