Day 33:【99天精通Python】日志记录 (Logging) - 告别 Print 调试
前言
欢迎来到第33天!
在之前的编程练习中,当我们需要调试代码或者查看程序运行状态时,最常用的办法就是print()。
但是在真正的项目开发(尤其是服务器后台、长时间运行的任务)中,print有很多严重的缺点:
- 无法持久化:关掉终端,信息就没了。
- 无法分级:满屏的输出,分不清哪些是重要的报错,哪些只是普通的调试信息。
- 缺少上下文:不知道这条信息是几点几分、在哪个文件的哪一行打印的。
Python 内置的logging模块提供了一套标准的日志记录系统。学会使用它,是你的代码走向专业化的重要标志。
本节内容:
- 为什么不用
print? - 日志的 5 个级别
basicConfig快速配置- 日志格式化 (Formatter)
- 进阶:同时输出到控制台和文件
- 实战练习:打造一个生产级日志系统
一、日志的 5 个级别 (Levels)
logging模块定义了 5 个标准级别,严重程度递增。我们可以根据需要选择记录哪个级别的信息。
| 级别 | 数值 | 描述 | 适用场景 |
|---|---|---|---|
| DEBUG | 10 | 调试细节 | 只有开发人员关心,如变量的值、循环进度。 |
| INFO | 20 | 正常信息 | 确认程序按预期运行,如"服务启动"、“任务开始”。 |
| WARNING | 30 | 警告 (默认) | 出现意外但暂不影响运行,如"磁盘空间不足"。 |
| ERROR | 40 | 错误 | 某个功能执行失败,如"文件未找到"、“连接超时”。 |
| CRITICAL | 50 | 严重错误 | 程序可能崩溃,无法继续运行。 |
注意:默认级别是WARNING。这意味着 DEBUG 和 INFO 级别的信息默认不会被打印出来。
二、快速上手:basicConfig
2.1 最简单的用法
importlogging# 默认只会打印 WARNING 及以上级别logging.debug("这是调试信息")# 不显示logging.info("这是普通信息")# 不显示logging.warning("这是警告信息")# 显示: WARNING:root:这是警告信息logging.error("这是错误信息")# 显示logging.critical("这是严重错误")# 显示2.2 配置日志 (保存到文件 + 修改级别)
使用logging.basicConfig()可以对系统进行初始化设置。
importlogging# 配置日志logging.basicConfig(filename='app.log',# 指定输出文件 (如果不写,默认输出到控制台)filemode='a',# 模式: 'w'覆盖, 'a'追加 (默认是a)level=logging.INFO,# 设置最低显示级别为 INFOformat='%(asctime)s - %(levelname)s - %(message)s',# 自定义格式encoding='utf-8'# 防止中文乱码 (Python 3.9+))logging.info("程序启动了...")logging.warning("磁盘空间低于 10%")logging.error("数据库连接失败!")运行后,app.log文件内容:
2026-02-01 10:00:01,123 - INFO - 程序启动了... 2026-02-01 10:00:01,125 - WARNING - 磁盘空间低于 10% 2026-02-01 10:00:01,126 - ERROR - 数据库连接失败!2.3 常用格式化字段
在format参数中可以使用以下占位符:
%(asctime)s: 时间 (默认精确到毫秒)%(levelname)s: 日志级别名称 (INFO, ERROR…)%(message)s: 日志内容%(filename)s: 调用日志的文件名%(lineno)d: 代码行号%(threadName)s: 线程名
三、进阶:Logger, Handler, Formatter
basicConfig虽然简单,但功能有限(例如:它很难实现"同时输出到控制台和文件")。
Python 的 logging 系统采用了模块化设计,由四大组件组成:
- Logger (记录器):程序直接调用的接口。
- Handler (处理器):决定把日志发到哪里(屏幕、文件、邮件…)。
- Formatter (格式化器):决定日志长什么样。
- Filter (过滤器):决定哪些日志要输出(较少用)。
实战:同时输出到屏幕和文件
这是开发中最常用的配置。
importlogging# 1. 创建 Loggerlogger=logging.getLogger("MyProject")logger.setLevel(logging.DEBUG)# 设置全局最低级别# 2. 创建 Handlers# StreamHandler: 输出到控制台 (屏幕)console_handler=logging.StreamHandler()console_handler.setLevel(logging.WARNING)# 屏幕只显示警告及以上# FileHandler: 输出到文件file_handler=logging.FileHandler("app_detail.log",encoding='utf-8')file_handler.setLevel(logging.DEBUG)# 文件记录所有细节# 3. 创建 Formattersfmt_simple=logging.Formatter('%(levelname)s: %(message)s')fmt_detail=logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')# 4. 给 Handlers 绑定 Formatterconsole_handler.setFormatter(fmt_simple)file_handler.setFormatter(fmt_detail)# 5. 给 Logger 添加 Handlerslogger.addHandler(console_handler)logger.addHandler(file_handler)# --- 测试使用 ---logger.debug("这是一条调试信息 (只会进文件)")logger.info("程序运行正常 (只会进文件)")logger.warning("注意!这里有个隐患 (屏幕+文件)")logger.error("出错了!(屏幕+文件)")效果:
- 屏幕上:只看到清爽的警告和错误信息。
- 文件中:记录了详尽的调试信息,方便事后排查。
四、捕获异常堆栈 (Traceback)
当程序崩溃捕获异常时,我们希望日志能记录下完整的报错堆栈信息,而不仅仅是一句"出错了"。
使用exc_info=True参数。
try:1/0exceptZeroDivisionError:# 错误写法:只记录了一句话,不知道哪一行错# logging.error("除数不能为0")# 正确写法:记录完整堆栈logging.error("发生了一个错误",exc_info=True)# 或者用更简单的写法 (仅在 except 块中有效)# logging.exception("发生了一个错误")日志输出:
ERROR:root:发生了一个错误 Traceback (most recent call last): File "test.py", line 2, in <module> 1 / 0 ZeroDivisionError: division by zero五、日志轮转 (Rotating Logs)
如果系统一直运行,日志文件会越来越大,最后把磁盘塞满。
我们需要日志轮转:比如每 10MB 切割一个新文件,或者每天生成一个新文件。
fromlogging.handlersimportRotatingFileHandler# 定义一个 10MB 大小的 handler,最多保留 5 个备份# 当 log.txt 满了,会重命名为 log.txt.1, log.txt.2 ...handler=RotatingFileHandler('app.log',maxBytes=10*1024*1024,# 10MBbackupCount=5,encoding='utf-8')# 剩下的配置同上...六、小结
关键要点:
- 别再用 Print 了:除非是临时调试,否则请使用 Logging。
- 级别管理:开发用 DEBUG/INFO,生产环境用 WARNING/ERROR。
- 异常记录:在
except块中用logging.exception()自动保存堆栈信息。 - 模块化:在不同的模块中,使用
logger = logging.getLogger(__name__)获取独立的记录器。
七、课后作业
- 日志封装:编写一个
MyLogger类,封装上述的"屏幕+文件"双重输出逻辑。调用时只需要log = MyLogger("test.log"),然后log.info(...)即可。 - 异常捕获器:结合装饰器知识,编写一个
@catch_error装饰器。如果被装饰的函数报错,自动将错误堆栈写入日志文件,而不是让程序崩溃。 - 计算器升级:将之前的计算器程序升级,记录用户的每一次操作(如 “User calculated 1+1=2”)到
calc.log文件中。
下节预告
Day 34:单元测试 (Unittest)- 写好的代码怎么保证没 Bug?靠人工点点点太慢了。明天我们学习如何编写自动化测试脚本,让代码自己测自己!
系列导航:
- 上一篇:Day 32 - 打包发布PyInstaller
- 下一篇:Day 34 - 单元测试Unittest(待更新)