All files / packages/logger/src/format/plugins splat.ts

0% Statements 0/47
100% Branches 1/1
100% Functions 1/1
0% Lines 0/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120                                                                                                                                                                                                                                               
import type { TransformableInfo, TransformFunction } from '../type'
import util from 'node:util'
import { SPLAT } from '../../triple-beam'
 
/**
 * 捕获给定字符串中的格式(即 %s 字符串)的数量。
 * 基于 `util.format`,参见 Node.js 源代码:
 * https://github.com/nodejs/node/blob/b1c8f15c5f169e021f7c46eb7b219de95fe97603/lib/util.js#L201-L230
 */
const formatRegExp = /%[scdjifoO%]/g
 
/**
 * 捕获格式字符串中转义的 % 符号的数量(即 %s 字符串)。
 */
const escapedPercent = /%%/g
 
class Splatter {
  constructor() {}
 
  /**
   * 检查 tokens <= splat.length,将 { splat, meta } 分配到 `info` 中,并写入此实例。
   *
   * @param info Logform 信息消息。
   * @param tokens 一组字符串插值标记。
   * @returns 修改后的信息消息
   * @private
   */
  private _splat(info: TransformableInfo, tokens: string[]) {
    const msg = info.message || ''
    const splat = info[SPLAT] || info.splat || []
    const percents = msg.match(escapedPercent)
    const escapes = percents ? percents.length : 0
 
    // 预期的 splat 是标记的数量减去转义的数量
    // 例如:
    // - { expectedSplat: 3 } '%d %s %j'
    // - { expectedSplat: 5 } '[%s] %d%% %d%% %s %j'
    //
    // 任何 "meta" 都将是预期 splat 大小之外的参数,无论类型如何。例如:
    //
    // logger.log('info', '%d%% %s %j', 100, 'wow', { such: 'js' }, { thisIsMeta: true });
    // 将导致 splat 为四(4),但预期只有三(3)。因此:
    //
    // extraSplat = 3 - 4 = -1
    // metas = [100, 'wow', { such: 'js' }, { thisIsMeta: true }].splice(-1, -1 * -1);
    // splat = [100, 'wow', { such: 'js' }]
    const expectedSplat = tokens.length - escapes
    const extraSplat = expectedSplat - splat.length
    const metas = extraSplat < 0 ? splat.splice(extraSplat, -1 * extraSplat) : []
 
    // 现在 { splat } 已经与任何潜在的 { meta } 分开。我们
    // 可以将其分配给 `info` 对象并将其写入我们的格式流。
    // 如果额外的 metas 不是对象或缺少可枚举属性
    // 你将会遇到麻烦。
    const metalen = metas.length
    if (metalen) {
      for (let i = 0; i < metalen; i++) {
        Object.assign(info, metas[i])
      }
    }
 
    info.message = util.format(msg, ...splat)
    return info
  }
 
  /**
   * 使用 `util.format` 完成 `info.message` 提供的 `info` 消息。
   * 如果没有标记,则 `info` 是不可变的。
   *
   * @param info Logform 信息消息。
   * @returns 修改后的信息消息
   */
  transform: TransformFunction = (info) => {
    const msg = info.message || ''
    const splat = info[SPLAT] || info.splat
 
    // 如果 splat 未定义,则无需处理任何内容
    if (!splat || !splat.length) {
      return info
    }
 
    // 提取标记,如果没有可用的标记,则默认为空数组以
    // 确保预期结果的一致性
    const tokens = msg.match(formatRegExp)
 
    // 此条件将处理具有 info[SPLAT]
    // 但没有标记存在的输入
    if (!tokens && (splat || splat.length)) {
      const metas = splat.length > 1 ? splat.splice(0) : splat
 
      // 现在 { splat } 已经与任何潜在的 { meta } 分开。我们
      // 可以将其分配给 `info` 对象并将其写入我们的格式流。
      // 如果额外的 metas 不是对象或缺少可枚举属性
      // 你将会遇到麻烦。
      const metalen = metas.length
      if (metalen) {
        for (let i = 0; i < metalen; i++) {
          Object.assign(info, metas[i])
        }
      }
 
      return info
    }
 
    if (tokens) {
      return this._splat(info, tokens)
    }
 
    return info
  }
}
 
/*
 * function splat (info)
 * 返回一个新的 splat 格式 TransformStream 实例
 * 它从 `info` 对象执行字符串插值。这之前
 * 在 `winston < 3.0.0` 中隐式暴露。
 */
export default () => new Splatter()