Object.defineProperty覆写console.debug时如何为整段日志着色
TypeScript日志类实现TODO日志全着色+正确行号方案
问题背景
我编写了一个日志类,支持在保留代码中日志调用语句的前提下关闭日志输出,同时可正确显示日志触发对应的行号与文件路径。
基于该类,我希望为TODO类型的日志设置醒目的显示颜色,但遇到两个核心问题:
- 原生类方法
todo()可正常给日志上色,但控制台输出的行号是类内部调用console.debug的位置,不是实际调用logger.todo()的业务代码位置 - 通过
Object.defineProperty覆写todo属性、用bind绑定控制台方法后,可以拿到正确的行号,但只有[TODO]标记被上色,其余日志文本是控制台默认颜色,且剩余参数会被打包成数组打印
原有问题代码
class Logger { // 注意:控制台需要勾选「All levels」才会显示debug级别日志 public OFF = 0; public LOG = 1; public ALL = 9; private level = 1; public constructor() { /*this.init();*/ // 测试阶段暂时注释 } // 原方法:颜色显示正常,行号定位错误 public todo(message?: string, ...optionalParams: any[]) { const css = 'background: #FF0000; color: #0000FF'; optionalParams.length != 0 ? console.debug('%c[TODO] '+message, css, optionalParams) : console.debug('%c[TODO] '+message, css); } // 覆写方法:行号定位正确,颜色失效、参数格式错误 /*private*/ public init() { Object.defineProperty(this, "todo", { get: function () { return this.level >= this.LOG ? console.debug.bind(console, '%c[TODO]', 'background: #FF0000; color: #0000FF') : function(){}; }, configurable: true }); } public setLevel(level: Level) { this.level = level; this.init(); } }
测试代码:
const logger = new Logger(); logger.todo('normal todo().'); logger.todo('normal todo() with data.', 666, 777); logger.init(); logger.todo('modified todo().'); logger.todo('modified todo() with data.', 666, 777);
运行效果参考:
解决方案
完全可以在Object.defineProperty的实现方案下实现整条日志着色,只需要修正bind时传入的参数格式即可,不需要额外增加包装函数(包装函数会导致行号定位到包装函数内部,失去正确行号的能力)。
问题根因
- 颜色失效原因:控制台
%c格式化标记的生效规则是:消耗紧随其后的第一个参数作为样式值,将样式应用到格式串中%c位置之后的所有字面量内容;如果后续还有格式串未匹配的额外追加参数,这些参数会默认使用控制台基础样式输出。原有代码中绑定的格式串是%c[TODO],因此只有格式串里的[TODO]字面量会应用样式,后续调用时传入的消息、额外参数都属于追加参数,走默认样式。 - 参数数组问题:参数被打包成数组是原有未覆写的
todo方法写法导致的——方法内用剩余参数收集到optionalParams数组后,直接把数组作为单个参数传入console.debug,没有做展开处理。bind返回的函数本身不会做参数打包,调用时传入的所有参数会自然追加到绑定参数列表后。
修正后代码
只需要修改init方法中bind的入参,把样式标记单独作为格式串的第一个占位符,将[TODO]文本作为绑定的普通参数放在样式参数之后即可:
/*private*/ public init() { Object.defineProperty(this, "todo", { get: function () { const todoStyle = 'background: #FF0000; color: #0000FF'; return this.level >= this.LOG // 关键修改:%c单独占格式串第一位,后接样式、[TODO]标记,后续调用传入的所有参数都会自动应用该样式 ? console.debug.bind(console, '%c', todoStyle, '[TODO] ') : function(){}; }, configurable: true }); }
可以删掉原来写的todo类方法避免初始化前逻辑不一致,记得在构造函数中调用this.init()完成初始化即可。
实现效果
- 整条TODO日志(包括
[TODO]标记、消息文本、所有额外参数)都会应用红底蓝字的醒目样式 - 控制台输出的行号、文件路径完全对应调用
logger.todo()的业务代码位置 - 额外参数会自动展开输出,不会被打包成数组
- 日志等级设为低于
LOG时,todo方法为空函数,不会输出任何内容,符合日志开关需求
内容的提问来源于stack exchange,提问作者tekki mariani
相关产品推荐
相关产品推荐

