Stories

Detail Return Return

【node】koa-logger - Stories Detail

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行代碼,功能也很簡單,需要拓展需要手動定製。

user avatar huaiyue_63f0b9e085bf0 Avatar
Favorites 1 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.