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

比未使用async_hook多了約10%。
對(duì)于我們qps是百級(jí)別的系統(tǒng)還好,但是如果是高并發(fā)的服務(wù),可能要慎重考慮下了。
總結(jié)
到此這篇關(guān)于如何用node優(yōu)雅地打印全鏈路日志的文章就介紹到這了,更多相關(guān)node打印全鏈路日志內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Node的Axure文件在線預(yù)覽的實(shí)現(xiàn)代碼
這篇文章主要介紹了基于Node的Axure文件在線預(yù)覽的實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
Node.js+ELK日志規(guī)范的實(shí)現(xiàn)
這篇文章主要介紹了Node.js+ELK日志規(guī)范的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-05-05
node.js中的fs.createReadStream方法使用說明
這篇文章主要介紹了node.js中的fs.createReadStream方法使用說明,本文介紹了fs.createReadStream方法說明、語法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12
利用Node.js了解與測(cè)量HTTP所花費(fèi)的時(shí)間詳解
這篇文章主要給大家介紹了關(guān)于利用Node.js了解與測(cè)量HTTP所花費(fèi)的時(shí)間的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09
Node使用koa2實(shí)現(xiàn)一個(gè)簡(jiǎn)單JWT鑒權(quán)的方法
這篇文章主要介紹了Node使用koa2實(shí)現(xiàn)一個(gè)簡(jiǎn)單JWT鑒權(quán)的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01

