koa-logger源码解析
以GET请求“/”为例子,koa-logger
会打印以下日志:
<-- GET /
GET / - 790ms
--> GET / 200 803ms 185b
分为是请求与响应的日志
// koa-logger 主函数
function dev (opts) {
// print函数,默认参数为空时是调用console.log,如果有transporter选项,则调用transporter函数代替
const print = (function () {
let transporter
if (typeof opts === 'function') {
transporter = opts
} else if (opts && opts.transporter) {
transporter = opts.transporter
}
return function printFunc (...args) {
// 注意:这里做了格式说明符替换
const str = util.format(...args)
if (transporter) {
transporter(str, args)
} else {
console.log(...args)
}
}
}())
return async function logger (ctx, next) {
// request
const start = ctx[Symbol.for('request-received.startTime')] ? ctx[Symbol.for('request-received.startTime')].getTime() : Date.now()
// 打印请求日志
print(' ' + chalk.gray('<--') +
' ' + chalk.bold('%s') +
' ' + chalk.gray('%s'),
ctx.method,
ctx.originalUrl)
try {
await next()
} catch (err) {
// log uncaught downstream errors
log(print, ctx, start, null, err)
throw err
}
// calculate the length of a streaming response
// by intercepting the stream with a counter.
// only necessary if a content-length header is currently not set.
// 通过使用计数器拦截流来计算响应流的长度。只有在当前没有设置Content-Length的响应报头时才需要。
const length = ctx.response.length
const body = ctx.body
let counter
if (length == null && body && body.readable) {
ctx.body = body
.pipe(counter = Counter())
.on('error', ctx.onerror)
}
// log when the response is finished or closed,
// whichever happens first.
// 记录响应完成或关闭的时间,以先发生的时间为准。一般都是触发finish事件
const res = ctx.res
const onfinish = done.bind(null, 'finish')
const onclose = done.bind(null, 'close')
// 重点:判断一个流请求是否结束
res.once('finish', onfinish)
res.once('close', onclose)
function done (event) {
res.removeListener('finish', onfinish)
res.removeListener('close', onclose)
// 打印 请求方式 路径 状态码 响应时间 报文长度
log(print, ctx, start, counter ? counter.length : length, null, event)
}
}
}
还有个logger辅助函数:
/**
* Log helper. 封装的输出打点数据的辅助函数
*/
function log (print, ctx, start, len, err, event) {
// 从err获取响应的状态码
// 以下字段比如err.isBoom、err.output.statusCode、err.status等都是根据业务来的
// 一般业务都有统一的处理响应的中间件
// 注意500、404的处理区分
const status = err
? (err.isBoom ? err.output.statusCode : err.status || 500)
: (ctx.status || 404)
// set the color of the status code;
// 根据不同状态码区间,1xx、2xx、3xx、4xx、5xx,映射选择不用颜色输出加以区分
const s = status / 100 | 0
// eslint-disable-next-line
const color = colorCodes.hasOwnProperty(s) ? colorCodes[s] : colorCodes[0]
// get the human readable response length
let length
// 204、205、304特殊处理
if (~[204, 205, 304].indexOf(status)) {
length = ''
} else if (len == null) {
length = '-'
} else {
// 借助bytes库,计算响应内容大小、输出类似2b、2kb
length = bytes(len).toLowerCase()
}
const upstream = err ? chalk.red('xxx')
: event === 'close' ? chalk.yellow('-x-')
: chalk.gray('-->')
print(' ' + upstream +
' ' + chalk.bold('%s') +
' ' + chalk.gray('%s') +
' ' + chalk[color]('%s') +
' ' + chalk.gray('%s') +
' ' + chalk.gray('%s'),
ctx.method,
ctx.originalUrl,
status,
time(start),
length)
}
/**
* Show the response time in a human readable format.
* In milliseconds if less than 10 seconds,
* in seconds otherwise.
*/
function time (start) {
const delta = Date.now() - start
return humanize(delta < 10000
? delta + 'ms'
: Math.round(delta / 1000) + 's')
}
总结
这个包代码很简单,不到200行代码,功能也很简单,需要拓展需要手动定制。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。