我如何覆盖 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");
我在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");