ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python醒目日志实战:从logging到彩色输出与rich库

Python醒目日志实战:从logging到彩色输出与rich库 1. 为什么要花心思做一份醒目的日志先问个实在问题你写Python跑程序的时候控制台刷出来的那一大片白底黑字你真的逐行看过吗我猜大概率是扫一眼有没有Traceback然后就开始在输出里搜关键字。这就是问题所在——常规日志在排错时根本不够用尤其是程序跑了几百行、几千行之后错误信息淹没在普通输出里你想定位一个警告眼睛都得瞪花了。我最早接触“醒目日志”这个概念是接手一个跑数据分析的脚本。那个脚本每天定时跑偶尔会在某个数据源上翻车但控制台只输出普通的print。有次线上数据出问题我硬是盯着一屏一屏滚动的输出看了十分钟才从几百行里找到那条红色的报错。从那时候起我就下决心把所有脚本的日志都改成“有颜色、带级别、一眼能分清内外”的样式。后来发现这个习惯带来的收益远超预期不仅自己排错快了同事拿我写的工具去跑也能第一时间看出哪一步出了问题不再需要对着白花花的控制台发呆。做醒目日志并不复杂核心就三件事结构化输出、按级别着色、关键信息高亮。这篇文章我把完整的方案拆开来讲从Python自带的logging模块起步讲到ANSI颜色、colorama、rich这些工具最后给你一套可以直接抄走的封装代码。不管你是刚学Python的入门用户还是已经在写脚本的工程党都能从中拿到一套立刻能用的方案。2. 先把logging用明白2.1 别再用print临时凑数了很多人写脚本习惯用print来打印过程信息打印完就完事。说实话临时调试用print没问题但只要是“要跑一段时间”“可能出问题需要排查”的脚本我强烈建议换成logging。为啥print天然有四个短板没有级别概念分不清“信息、警告、错误”的优先级。输出位置单一只能去控制台看没法同时写文件、发远程。回溯排查困难你不知道这条输出是哪一行代码打的哪个模块传过来的。没有标准格式打印内容五花八门脚本一多就混乱。logging模块是Python标准库的一部分从Python 2.3開始就有了稳定性毋庸置疑。它提供了Logger、Handler、Formatter三个核心角色Logger负责接收代码里的日志调用Handler负责决定日志送哪儿去控制台、文件、Socket等Formatter负责决定日志长什么样。三者配合你可以把日志同时输出到控制台和文件还能按级别分开处理。2.2 一套最基础的logging配置看一个最简单的标准写法import logging logging.basicConfig( levellogging.INFO, format%(asctime)s | %(levelname)-8s | %(name)s | %(message)s, datefmt%Y-%m-%d %H:%M:%S ) log logging.getLogger(demo) log.info(程序启动) log.warning(磁盘空间不足注意检查) log.error(数据库连接失败)运行之后控制台会输出类似这样规整的行2024-01-15 14:22:31 | INFO | demo | 程序启动 2024-01-15 14:22:31 | WARNING | demo | 磁盘空间不足注意检查 2024-01-15 14:22:31 | ERROR | demo | 数据库连接失败%(levelname)-8s这行格式化串很多人第一次看会懵其实就是把级别名左对齐并占满8个字符宽度这样INFO、WARNING、ERROR在视觉上能对齐输出整齐很多。格式化字段还有很多常用的有字段含义%(asctime)s时间戳%(levelname)s日志级别名%(name)sLogger名称%(message)s日志正文%(filename)s打印日志的文件名%(lineno)d打印日志的行号%(funcName)s打印日志的函数名%(process)d进程ID%(threadName)s线程名真正排错的时候filename和lineno特别有用。比如程序跑到第108行报错有行号你直接定位没有你就得自己在代码里搜那句日志在哪儿。所以我的建议是凡是脚本要保持长期运行的format里至少带上文件名和行号。3. 颜色不是花架子是信息分层3.1 ANSI转义序列的底层原理要让控制台输出彩色文字靠的就是ANSI转义序列。简单理解这一类特殊字符不会被直接打印出来而是告诉终端“接下来的文字换成什么颜色”。常见的写法是\033[显示方式;前景色;背景色m比如\033[91m代表亮红色前景\033[0m代表重置所有样式。Python里字符串可以直接拼这些控制符print(\033[91m 这是亮红色文字 \033[0m)在支持ANSI的终端macOS终端、Linux终端、Windows 10以上的PowerShell/Windows Terminal里渲染出来的就是红色文字后面的\033[0m会把颜色复位避免“后续所有输出都变红”的串色问题。常用的前景色编号我整理了一下颜色普通亮度高亮度黑色3090红色3191绿色3292黄色3393蓝色3494品红3595青色3696白色3797还有一个很多人忽略的点除了颜色ANSI还支持加粗1、下划线4、闪烁5、反白7。我自己用下来加粗在关键报错上的效果比单纯换色更醒目。比如一个错误级别日志用“亮红加粗”警告用“亮黄”即可不要全都加粗否则视觉权重就没了。3.2 colorama让跨平台不再头疼直接拼ANSI有一个坑老版本的Windows命令行终端cmd.exe默认不解析ANSI序列你输出一坨\033[91m屏幕上直接显示乱码。虽然Windows 10之后的系统大多默认开启支持但兼容性总归是个隐患尤其是你要把脚本分发给其他同事的时候。colorama这个库就是来解决这个问题的。它会检测当前平台在Windows下自动把ANSI序列转换为相应的Windows API调用从而让颜色在所有终端里都能正常显示。用法也极其简单pip install coloramaimport colorama from colorama import Fore, Back, Style colorama.init(autoresetTrue) print(Fore.RED 红色文字) print(Fore.GREEN 绿色文字) print(Fore.YELLOW 黄色文字) print(Style.BRIGHT Fore.RED 亮红色加粗)autoresetTrue这个参数等于每段文本之后自动加一个\033[0m这样你就不用每个字符串结尾都手动拼重置符了。如果不设置autoreset一旦打印了红色后面所有日志都是红色排查起来反而容易懵。3.3 用rich库把日志输出颜值拉满如果你觉得colorama还只是“上色”那rich算是把日志输出带到了一个全新的层次。它是一个终端富文本渲染库支持彩色、粗体、表格、进度条、语法高亮甚至还能把整个traceback格式化成非常漂亮的彩色堆栈。安装同样很简单pip install richrich自带一个logging的Handler可以直接接入标准loggingfrom rich.logging import RichHandler import logging logging.basicConfig( levellogging.INFO, format%(asctime)s | %(name)s | %(message)s, datefmt%Y-%m-%d %H:%M:%S, handlers[RichHandler(rich_tracebacksTrue, markupTrue)] ) log logging.getLogger(rich_demo) log.info(这是一条普通信息) log.warning(这是一条警告) log.error(这是一条错误)运行之后你会看到带颜色和缩进的日志块逐行显示警告、错误各有一套配色体系视觉效果非常清晰。而且rich_tracebacksTrue之后如果代码抛异常控制台会给出一个排版清晰、关键字带高亮的堆栈信息比默认的Python Traceback好读非常多。rich还支持在日志文本里插入标记语法比如你想把一段内容特别标红log.info([bold red]关键[/bold red] 任务执行完成耗时 [cyan]12.3s[/cyan])这个markupTrue开启之后中括号内就是样式控制符。我自己在实际项目里经常用这种内嵌标记来强调耗时、路径、行数等关键数据比全行统一颜色更有针对性。4. 实战封装一套可以直接用的醒目日志模块4.1 设计思路与完整代码讲了这么多工具最终要落到一个能直接复制的方案上。我的做法是封装一个独立的logger.py提供三个功能默认输出到控制台同时可选输出到文件。控制台按级别着色DEBUG灰、INFO青、WARNING黄、ERROR亮红加粗。抛异常时能记录完整的traceback。直接看代码import logging import sys from logging.handlers import RotatingFileHandler from colorama import Fore, Style, init # 兼容Windows终端颜色 init(autoresetTrue) # 自定义Formatter重写format方法实现级别着色 class ColoredFormatter(logging.Formatter): COLORS { DEBUG: Fore.CYAN, INFO: Fore.GREEN, WARNING: Fore.YELLOW, ERROR: Fore.RED, CRITICAL: Fore.RED Style.BRIGHT, } def format(self, record): # 先按标准格式生成文本再根据级别套颜色 msg super().format(record) color self.COLORS.get(record.levelname, ) if color: return f{color}{msg}{Style.RESET_ALL} return msg def setup_logger( name: str app, log_file: str | None None, level: int logging.DEBUG, ): logger logging.getLogger(name) logger.setLevel(level) logger.handlers.clear() # 避免重复添加handler fmt %(asctime)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s datefmt %Y-%m-%d %H:%M:%S # 控制台handler带颜色 console_handler logging.StreamHandler(sys.stdout) console_handler.setLevel(level) console_handler.setFormatter(ColoredFormatter(fmt, datefmt)) logger.addHandler(console_handler) # 文件handler不带颜色文件里存颜色码没有意义 if log_file: file_handler RotatingFileHandler( log_file, maxBytes10 * 1024 * 1024, backupCount5, encodingutf-8 ) file_handler.setLevel(level) file_handler.setFormatter(logging.Formatter(fmt, datefmt)) logger.addHandler(file_handler) return logger # 默认logger log setup_logger()这里有一个细节文件日志我刻意不加颜色。原因是日志文件如果存入了ANSI颜色码用编辑器打开会看到一堆\033[91m之类的乱码反而干扰查看。所以文件handler用普通的Formatter保留干净的结构化文本控制台handler用带颜色的Formatter方便肉眼识别。4.2 核心机制解读为什么代码这样写有读者可能会问ColoredFormatter里为什么要调用super().format(record)而不是直接在格式化串里拼颜色这涉及到logging的调用链Formatter.format()内部会先执行formatMessage()把%(xxx)s占位符替换成真实字段生成纯文本日志然后format()方法还负责处理异常信息当exc_infoTrue时会把traceback追加到消息尾部。如果我只改formatMessage()而忽略format()那么在记录异常的时候traceback就不会被正确附加。我的写法是在format()里先拿到完整的、已经包含traceback的文本再统一套颜色。这样无论是普通日志还是异常日志颜色都能正确加上。再看logger.handlers.clear()这行。很多同学在模块里多次调用setup_logger()时会发现日志被重复打印原因就是同名的Logger对象会累积Handler。每次调用前清空已有Handler可以避免这个问题但要注意如果程序里其他地方也往这个Logger上挂了自定义Handlerclear会一并清掉。所以更稳妥的做法是每次调用都返回一个新的Logger实例或者加一个全局字典缓存已创建的Logger。4.3 实际使用效果与扩展封装之后的使用方式就很顺了from logger import log log.debug(这是一条调试信息通常不显示) log.info(任务开始执行) log.warning(磁盘空间低于20%建议清理) log.error(连接第三方API失败) try: 1 / 0 except ZeroDivisionError: log.exception(计算过程出现异常)log.exception()是logging里的一个隐藏利器。它等价于log.error(..., exc_infoTrue)会在日志正文后面自动附上完整的traceback。有了这个方法你捕获到异常后直接写它保存到文件里的日志就能完整记录崩溃现场对远程排查特别有用。再举一个可视化场景的例子。很多Python GUI工具比如tkinter、PyQt里会放一个“开始”按钮点击后触发后台任务界面上需要一个日志展示区域。这种情况可以直接把stdio重定向到文本框import sys import tkinter as tk class TextRedirector: def __init__(self, widget, tagstdout): self.widget widget self.tag tag def write(self, msg): self.widget.insert(end, msg) self.widget.see(end) self.widget.tag_add(self.tag, end-1c, end-1l) def flush(self): pass root tk.Tk() text tk.Text(root, height20, width80) text.pack() sys.stdout TextRedirector(text, stdout) sys.stderr TextRedirector(text, stderr) log.info(点击按钮后日志会实时显示在文本框里)这样就把控制台的日志同步显示到了GUI界面点击按钮执行任务时用户能实时看到运行状态。如果你接的是PyQt5还可以用QTextEdit配合QPlainTextEdit效果类似。5. 实际排错里最常见的日志坑5.1 Windows下颜色不生效怎么办我在真实工作中遇到最多的情况是脚本在开发机上macOS/Linux跑得好好的一放到客户Windows服务器上颜色就变成了一堆乱码。解决办法分两种情况一你的环境是Windows 10以上且使用PowerShell或Windows Terminal。这种已经默认支持ANSI直接用colorama初始化一次就能正常。情况二还是老的cmd.exe或者某些被组策略锁定的环境颜色始终无法正常显示。这时候就别追求彩色了改用纯文本格式并依靠级别前缀和分隔符来区分日志轻重。如果你用colorama之后发现老cmd还是乱码可以考虑启用Windows的虚拟终端处理。在Python里可以这样做import ctypes kernel32 ctypes.windll.kernel32 kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)-11代表STD_OUTPUT_HANDLE7表示ENABLE_VIRTUAL_TERMINAL_PROCESSING把这个权限打开老终端也能识别ANSI序列。不过这个操作需要Windows 10 1703以上才生效更老的系统就只能放弃彩色了。5.2 日志重复输出越排查越晕大量新手在配置logging时都会踩这个坑明明只打了一条日志控制台却出现两遍、三遍甚至越来越多。原因是root logger和自定义logger都在工作。basicConfig()默认配置的是root logger而当你通过logging.getLogger(my_module)获取logger时如果它没有设置propagate为False日志事件会同时被当前logger的handler和root的handler各处理一次造成重复。最简单的修复办法logger logging.getLogger(my_module) logger.propagate False我在实际项目里更推荐另一种思路整个程序只保留一个根logger所有模块都通过logging.getLogger(__name__)获取子logger根logger上挂handler子logger只管发事件传播到根logger统一输出。这样日志的流向清晰不会重复。5.3 控制台显示正常日志文件里中文乱码这个问题在Windows平台特别容易出。Python 3.9之后open()的默认编码随平台变化Windows默认是GBK如果你的日志里有中文字符写到文件时没指定encodingutf-8要么写不进去要么打开文件时全是乱码。解决办法是在FileHandler和RotatingFileHandler创建时显式传入encodingutf-8file_handler RotatingFileHandler( log_file, maxBytes10 * 1024 * 1024, backupCount5, encodingutf-8 )这事让我在客户那边丢过一次数据脚本日志文件在中文Windows下用GBK编码写了一半程序崩溃后日志文件里全是问号最后只能靠“再说一遍业务过程”来复盘。从那以后所有跟文件读写相关的场景我都坚持显式指定UTF-8编码。5.4 日志文件在程序崩溃时丢数据这个坑比较隐蔽。logging模块默认使用缓冲写入程序如果遇到os._exit()直接硬退出、或者断电、进程被杀缓冲区的日志还没落盘就没了。解决方式有两种在关键流程结束后显式调用logging.shutdown()它会刷新所有handler并清理资源。给FileHandler指定delayFalse默认就会立即打开文件但写入是否实时还取决于操作系统的缓冲策略。更为稳妥的做法是改用QueueHandlerQueueListener的异步日志方案把日志写入放到独立线程主线程只管put不阻塞业务。不过日常脚本通常用不到这么重我一般只在长任务的末尾加一句logging.shutdown()就够用了。6. 我把这套方案用在了哪些场景里说了这么多分享几个实际落地场景方便你对号入座。跑数据脚本时一个健壮的日志输出能让你一眼看出哪个环节耗时最长。我的习惯是任务开始打INFO级日志标注“开始执行”带上任务ID。每个阶段结束打INFO带上耗时秒数。数据量异常、依赖缺缺失打WARNING。捕获异常打ERROR并手动加一条“建议检查XX配置文件”的提示。配合彩色输出调度平台上展示的日志就是一块一块的色块绿色代表正常流转黄色代表风险提示红色代表故障点。运维同事看到红色直接跳过去定位不再需要逐行读日志。GUI工具场景里我给内部一个批量文件处理工具加过日志面板。用户点击“开始”按钮后台线程用logging记录进度界面用tkinter的Text文本框实时刷新。以前用户反馈“点完按钮没反应”现在自己能通过日志看到走到哪一步、在哪一步卡住工单质量明显提升。还有一个特别好用的扩展把日志同时发到远程。logging的Handler机制天然支持扩展你只需要写一个继承logging.Handler的类在emit()方法里用HTTP、Redis、Kafka等方式把日志转发出去。这个玩法可以让多台机器的日志统一汇总到一张看板上对排查分布式任务特别有用。我之前用一个简单的requests.post就把日志直接转发到了企业微信机器人脚本出差错了微信群立刻收到红色告警连盯控制台都省了。7. 给你的建议做日志这件事方向比工具重要。很多新人一上来就想用最炫的rich库配了一堆颜色结果真正出问题时该记录的上下文信息没记录全花哨的界面反而掩盖了关键数据。我在实际使用中的体会是醒目日志的精髓不是颜色多好看而是信息分层清晰、关键上下文不遗漏、不同级别一眼可辨。控制台输出用颜色分层文件日志保持干净文本。线上排错依赖的是filename、lineno、exc_info这些细节别省。Windows环境务必考虑colorama或手动开启虚拟终端支持。长任务记得必须把日志写到文件里别只靠控制台。学会log.exception()这可能是你排错时最常用的一个方法。最后再分享一个小技巧你可以给关键的WARNING和ERROR日志配一个固定的前缀比如[CHECK]、[DATA_ERR]后续用grep或者编辑器搜索时一条命令就能把所有异常点拉出来。我自己的脚本里凡是需要人工介入的日志统一用[ACTION_REQUIRED]前缀这样每天扫一眼日志哪些问题必须处理一目了然。
返回列表