我如何覆盖 console.log 以便每个调用都有一个对象的 UUID?

How can I override console.log such that each call has a UUID of an object?

我在Node.js中写了一个class,它完成了一系列的网络请求。这个 class 被多次实例化以执行不同的网络请求。该程序在终端中执行,因此我需要特定的日志记录功能,以便我可以调试错误并观察成功。

我已经覆盖了每个函数的定义,这样我就可以像这样在每个日志语句前添加一个 UUID:

[16:07:22.911] [LOG]    [54fccbc5-f6c8-4e0a-b42a-196e831df0e6]  hello from worker 1

在这种情况下,每个 UUID 对于 class 的实例都是唯一的。

我使用另一个 npm 模块 console-stamp 来添加时间戳和日志级别元数据。

require('console-stamp')(console, {
  pattern: 'HH:MM:ss.l',
  colors: {
    stamp: chalk.cyan,
  },
});

为了实现此覆盖,我创建了一个方法,将工作人员的 UUID class 附加到日志语句:

function addUuidToConsole(uuid) {
  if (console.log) {
    const old = console.log;
    console.log = function log(...args) {
      Array.prototype.unshift.call(args, `[${uuid}] `);
      old.apply(this, args);
    };
  }
  if (console.warn) {
    const old = console.warn;
    console.warn = function warn(...args) {
      Array.prototype.unshift.call(args, `[${chalk.red(uuid)}] `);
      old.apply(this, args);
    };
  }
  if (console.error) {
    const old = console.error;
    console.error = function error(...args) {
      Array.prototype.unshift.call(args, `[${chalk.red(uuid)}] `);
      old.apply(this, args);
    };
  }
}

然后在我的 class 的构造函数中,我用实例的 uuid 调用这个函数。

class Worker {
  constructor(profile) {
    ...
    this.uuid = uuidv4();
    ...
    addUuidToConsole(this.uuid);
    ...
  }
  ...
}

问题

当我只使用 Worker 的一个实例时,这个解决方案是令人满意的。但是,当我使用超过 1 个实例时,后续的 UUID 会按顺序添加到前面。

[16:45:47.606] [LOG]    [9ce5e2b8-d49d-40c9-bb9d-3ed9e83fb441]  hello from worker 1
[16:45:47.607] [LOG]    [9ce5e2b8-d49d-40c9-bb9d-3ed9e83fb441]  [ef5bab6c-31c2-4ad9-aea0-c435f1861989]  hello from worker 2

此外,我的 次要问题 是这种覆盖使我无法使用 console.time()console.timeEnd() 来衡量我的请求的有效性.

我在调用这些计时方法时使用了 UUID,在覆盖它之后变得非常丑陋。当我调用 console.timeEnd() 时,我收到如下输出:

[16:45:47.606] [LOG]    [9ce5e2b8-d49d-40c9-bb9d-3ed9e83fb441]  %s: %sms 9ce5e2b8-d49d-40c9-bb9d-3ed9e83fb441 3.860

我当然想要的是让日志明显分开而不是“累积”。我认为这个错误是因为 Worker 实例共享同一个 console 对象,尽管我不确定如何解决这个问题以便它们的输出类似于:

[16:45:47.606] [LOG]    [9ce5e2b8-d49d-40c9-bb9d-3ed9e83fb441]  hello from worker 1
[16:45:47.607] [LOG]    [ef5bab6c-31c2-4ad9-aea0-c435f1861989]  hello from worker 2

我考虑的一个解决方案是放弃我的重写方法并在我对 console.log()console.debug() 等的每个调用中使用一个 formatMessage() 函数。

我的问题是如何设计一个优雅的解决方案,以便我可以快速区分来自不同 Worker 实例的日志输出。感谢您的建议和意见。

你的问题是 console 是一个单例对象。对 addUuidToConsole 的每次调用都将 log/warn/error 函数的先前版本包装在一个 [yet another] 函数中,该函数预先设置一个 UUID。

您想要的是为每个工作人员创建一个不同的日志对象,其中 none 实际上修改了 console 全局对象。相反,每个对象都应该提供一个 console-like API 来根据需要修改传递给它的任何参数,然后再将它们转发给相应的 console 方法。事实证明,这是 Proxy class.

的一个很好的用例

例如:

/**
 * Function to create a console-like object that prefixes
 * any (well, okay, most) output with "[${tag}]".
 *
 * This will behave identical to the built-in `console`, except
 * where provide custom wrapper functions, below.
 *
 * @param {String} prefix string
 */
function taggedConsole(tag) {
  // Cache of wrapper functions
  const wraps = {};

  // Return a Proxy for the console object
  return new Proxy(console, {
    // Trap for when `console[prop]` is referenced
    get(target, prop) {
      // If we've already created a wrapper function, return it
      if (wraps[prop]) return wraps[prop];

      // Functions we wrap (to inject `[tag]` argument)
      switch (prop) {
        // Create a wrapper to insert `tag` as the first arg
        case "debug":
        case "log":
        case "info":
        case "warn":
        case "error":
          wraps[prop] = function(...args) {
            return console[prop](`[${tag}]`, ...args);
          };
          break;

        // Create a wrapper to prefix the first arg with `tag`
        case "count":
        case "countReset":
        case "group":    // Note: Takes a label arg, but 
        case "groupEnd": // groupEnd ignores label arg so... :-/
        case "time":
        case "timeEnd":
        case "timeLog":
        case "trace":
          wraps[prop] = function(...args) {
            args[0] = `[${tag}] ${args[0]}`;
            return console[prop](...args);
          };
          break;
      }

      // Return wrapper if defined, otherwise return the original function
      return wraps[prop] || target[prop];
    }
  });
}

// FOR EXAMPLE ...

// Create a couple example consoles
consoleFoo = taggedConsole("FOO");
consoleBar = taggedConsole("BAR");

// Log stuff (output prefixed with the tag)
consoleFoo.log("hello");
consoleBar.log("world");

// Other functions that provide tag as prefix
consoleFoo.time("fdsafd");
consoleFoo.timeEnd("fdsafd");
consoleFoo.trace("fdsafd");