如何用node優(yōu)雅地打印全鏈路日志
前言
當用戶報問題:線上某個功能使用報錯時,如何快速準確地定位?當某個請求接口返回數(shù)據(jù)緩慢時,如何有效地追蹤優(yōu)化?
一、原理和實踐
眾所周知,當一個請求到來時,大概會有以下日志產(chǎn)生:
1、AceesLog:用戶訪問日志
2、Exception:代碼異常日志
3、SQL:sql查詢日志
4、ThirdParty:第三方服務日志
該如何追蹤一條請求產(chǎn)生的所有日志?
一般做法是使用一個requestId來做唯一標識,
然后寫一個中間件,把requestId注入到context上下文中,當需要打日志時,再從context中取出打印,
在第三方服務和SQL日志中,還需要把requestId傳入到相應的函數(shù)里面打印,這樣層層傳遞,實在太麻煩,代碼侵入性也比較強。
我們的目標是降低代碼侵入性,一次注入,自動跟蹤。
經(jīng)過調研,async_hooks可以追蹤異步行為的生命周期,在每個異步資源(每個請求都是一個異步資源)中,它都有2個ID,
分別是asyncId(異步資源當前生命周期ID),trigerAsyncId(父級異步資源ID)。
async_hooks提供了以下生命周期鉤子來監(jiān)聽異步資源:
asyncHook = async_hook.createHook({
// 監(jiān)聽異步資源的創(chuàng)建
init(asyncId,type,triggerAsyncId,resource){},
// 異步資源回調函數(shù)開始執(zhí)行之前
before(asyncId){},
// 異步資源回調函數(shù)開始執(zhí)行后
after(asyncId){},
// 監(jiān)聽異步資源的銷毀
destroy(asyncId){}
})那如果我們做一個映射,每個asyncId映射一個storage,storage里面再存儲對應的requestId,那requestId就可以很容易獲取了。
正好cls-hooked這個庫已經(jīng)基于async_hooks做好了封裝,在同一份異步資源維護一份數(shù)據(jù),以鍵值對的形式存儲。(注意:async_hooked需要在高版本node>=8.2.1使用)當然社區(qū)中還有其他的實現(xiàn),比如cls-session,node-continuation-local-storage等。
下面講下我把cls-hooked運用在我項目中的實例:
/session.js 創(chuàng)建命名存儲空間
const createNamespace = require('cls-hooked').createNamespace
const session = createNamespace('requestId-store')
module.exports = session/logger.js 打印日志
const session = require('./session')
module.exports = {
info: (message) =>
{
const requestId = session.get('requestId')
console.log(`requestId:${requestId}`, message)
},
error: (message) =>
{
const requestId = session.get('requestId')
console.error(`requestId:${requestId}`, message)
}
}/sequelize.js sql調用logger打印日志
const logger = require("./logger")
new Sequelize(
logging: function (sql, costtime) {
logger.error( `sql exe : ${sql} | costtime ${costtime} ms` );
} )/app.js 設置requestId、設置requestId返回響應頭、打印訪問日志
const session = require('./session')
const logger = require('./logger')
async function accessHandler(ctx, next)
{
const requestId = ctx.header['x-request-id'] || uuid()
const params = ctx.request.body ? JSON.stringify(ctx.request.body) : JSON.stringify(ctx.request.query)
// 設置requestId session.run(() => { session.set('requestId', requestId)
logger.info(`url:${ctx.request.path};params:${params}`) next()
// 設置返回響應頭
ctx.res.setHeader('X-Request-Id',requestId)
}) }我們看下當一條請求路徑是/home?a=1到來時的日志:
訪問日志:
requestId:79f422a6-6151-4bfd-93ca-3c6f892fb9ac url:/home;params:{"a":"1"}
Sql日志:
requestId:79f422a6-6151-4bfd-93ca-3c6f892fb9ac sql exe :
Executed (default): SELECT `id` FROM t_user可以看到同一條請求整個鏈路的日志requestId是一樣的。如果后面有告警發(fā)到告警平臺,那么我們根據(jù)requestId就可以找到這條請求執(zhí)行的整個鏈路了。
細心的同學可能會觀察到我在接口返回的響應頭里面也設置了requestId,目的就是為了后續(xù)如果發(fā)現(xiàn)某條請求響應緩慢或者有問題,那直接從瀏覽器就可以知道requestId,就可以做分析了。
二、性能開銷
我本地做了一下壓測,
這是內存的占用對比:

比未使用async_hook多了約10%。
對于我們qps是百級別的系統(tǒng)還好,但是如果是高并發(fā)的服務,可能要慎重考慮下了。
總結
到此這篇關于如何用node優(yōu)雅地打印全鏈路日志的文章就介紹到這了,更多相關node打印全鏈路日志內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
node.js中的fs.createReadStream方法使用說明
這篇文章主要介紹了node.js中的fs.createReadStream方法使用說明,本文介紹了fs.createReadStream方法說明、語法、接收參數(shù)、使用實例和實現(xiàn)源碼,需要的朋友可以參考下2014-12-12
Node使用koa2實現(xiàn)一個簡單JWT鑒權的方法
這篇文章主要介紹了Node使用koa2實現(xiàn)一個簡單JWT鑒權的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-01-01

