跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

Python 日志着色:如何仅对控制台输出添加颜色而不影响文件日志

本文详解为何 logging.filter 无法实现“仅终端着色”,并提供基于自定义 formatter 的正确解决方案,确保颜色代码只作用于 stream handler,完全隔离文件日志的纯净性。 在 Python 日志系统中,一个常见误区是试图用 logging.Filter 实现日志内容的样式化(如为 levelname 添加 ANSI 颜色)。但正如示例所揭示的: Filter 在日志记录被格式化前就已修改 record 对象本身 ——而 record 是跨所有 Handler 共享的。因此,即使你只将 ColourLevelNameFilter 添加到 StreamHandler,它仍会污染 FileHandler 所用的同一 LogRecord 实例,导致颜色转义序列(如 \x1b[031m)意外写入日志文件。 根本原因在于: Filter.filter(record) 接收的是原始 LogRecord 对象; record.levelname = ... 是原地修改(in-place mutation); 同一 record 会被依次传递给所有已注册的 Handler(包括 FileHandler),此时 levelname 已含颜色码。 ✅ 正确解法是 避免修改 record,改在格式化阶段动态注入样式 ——即继承 logging.Formatter,重写 format() 方法,在生成最终字符串时精准替换目标字段。 以下是一个健壮、生产就绪的着色 Formatter 示例: 立即学习 “ Python免费学习笔记(深入) ”; Trae win Trae Windows 官方版本现已全面支持 Win10 和 Win11 系统(64位)。国内开发者可直接访问 Trae 中文官网(trae.cn 或 trae.com.cn),一键下载官方正版 .exe 安装包,享受国内直连的高速稳定体验。安装过程极其简便,双击即可运行,并支持一键无缝导入 VS Code 或 Cursor 的原有主题、快捷键及插件配置。登录支持手机号、微信或稀土掘金账号。内置免费的豆包与 DeepSeek 等顶级大模型,让你开箱即用,轻松开启高效的 AI 智能编程之旅。 下载
import logging class ColourFormatter(logging.Formatter): FORMATS = { logging.DEBUG: '\x1b[36m{0}\x1b[0m', # cyan logging.INFO: '\x1b[32m{0}\x1b[0m', # green logging.WARNING: '\x1b[33m{0}\x1b[0m', # yellow logging.ERROR: '\x1b[31m{0}\x1b[0m', # red logging.CRITICAL: '\x1b[41m{0}\x1b[0m', # white-on-red } def format(self, record: logging.LogRecord) -> str: # 先调用父类获取标准格式化字符串 msg = super().format(record) # 获取对应级别的着色 levelname colour_levelname = self.FORMATS.get( record.levelno, '{0}' ).format(record.levelname) # 精准替换:仅替换独立出现的 levelname(避免误染 message 中同名文本) # 使用 f-string 模式匹配时间+级别组合,进一步提升安全性 timestamp_level = f'{record.asctime} {record.levelname}' colour_timestamp_level = f'{record.asctime} {colour_levelname}' return msg.replace(timestamp_level, colour_timestamp_level, 1)
使用时,为不同 Handler 分配不同 Formatter 即可实现完全隔离:
# 控制台使用着色 Formatter stream_handler = logging.StreamHandler() stream_handler.setFormatter(ColourFormatter( fmt='%(asctime)s %(levelname)s => %(message)s', datefmt='%Y-%m-%d %H:%M:%S' )) # 文件使用纯文本 Formatter(无颜色) file_handler = logging.FileHandler('app.log', mode='a') file_handler.setFormatter(logging.Formatter( fmt='%(asctime)s %(levelname)s => %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ))
⚠️ 注意事项: 永远不要在 Filter 中修改 record 的核心字段(如 levelname, msg, message) ——这违反了 Filter 的设计契约(应仅做“是否通过”判断); Formatter.format() 是安全的着色入口,因它作用于最终字符串,且每个 Handler 独立调用; 替换逻辑建议优先匹配 asctime + levelname 组合,而非单独 levelname,可彻底规避日志消息中偶然出现相同单词导致的误着色; 若需兼容 Windows 终端(旧版 cmd),可在程序启动时调用 colorama.init() 或检查 sys.stdout.isatty() 再启用颜色。 总结:日志着色的本质是 输出层定制 ,而非记录层干预。通过分离关注点(Filter 做 路由 ,Formatter 做渲染),你既能获得直观的终端体验,又能保证文件日志的机器可读性与跨平台兼容性。

相关文章