koa-logger source code analysis

Taking the GET request "/" as an example, koa-logger will print the following log:

 <-- GET /
GET / - 790ms
  --> GET / 200 803ms 185b

Divided into request and response logs

 // 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)
    }
  }
}

There is also a logger helper function:

 /**
 * 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')
}

Summarize

The code of this package is very simple, less than 200 lines of code, and the function is also very simple. It needs to be expanded and customized manually.


看见了
876 声望16 粉丝

前端开发,略懂后台;


下一篇 »
策略模式

引用和评论

0 条评论