项目背景
公司部署的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可以看到下面这样
整个链路的日志都能统一输出