logging模块

1 从结绳记事到日志

在远古时代,人类使用结绳记事记录重要事件——绳结的大小、位置和颜色代表不同含义。随着文明发展,我们有了纸笔、印刷术,直到今天的电子日志系统。

Python的logging模块就是数字时代的"结绳记事",帮助开发者记录程序运行的关键信息。

2 为什么需要日志

日志和保险一样,系统没有问题时没有存在感,出了问题后承担“行车记录仪”的功能。

日志记录应用程序和基础设施运行中的每一个关键事件、操作和消息,包括所发生事件的描述、相关时间戳、与事件相关的严重性级别类型以及其他相关元数据。日志对于调试问题、诊断错误和审核系统活动非常有用。它们提供了系统事件的文本叙述,使人们更容易理解所采取的操作顺序。

3 Python日志模块概述

Python 的 logging 模块通过层级化的日志器(Logger)处理器(Handler)过滤器(Filter)格式化器(Formatter) 实现灵活的日志管理。

  • Logger

    日志级别:DEBUG < INFO < WARNING < ERROR < CRITICAL,日志仅当级别≥日志器级别时才会处理。

  • Filter

    • 精细化控制:基于日志内容(如级别、消息文本、模块名)决定是否处理日志。

    • 绑定位置:可附加到日志器(全局过滤)或处理器(目标特定过滤)。

  • Handler

    负责将日志传递到不同目标:

    • 控制台处理器logging.StreamHandler(输出到 sys.stdout/stderr)。

    • 文件处理器logging.FileHandler(写入文件)、RotatingFileHandler(滚动文件)。

    • 其他处理器:如 SMTPHandler(邮件)、SocketHandler(网络)等。

    • 多处理器支持:一个日志器可绑定多个处理器(如同时输出到控制台和文件)。

  • Formatter

    • 将日志记录(LogRecord)转换为字符串(如添加时间、模块名)

4 日志使用常见错误

4.1 没有log或者少打log

解决方案:用装饰器简化代码。

例如我们有三个函数, addmuldiv

如果我们需要记录函数的入口参数、返回结果和异常信息,以往需要在每个函数中重复编写日志代码,显得冗余且费力。

以下是优化后的实现:

  • 通过装饰器封装日志记录代码,消除每个函数中的重复日志语句

  • 自动记录函数名、参数、返回值和异常信息

输出示例:

4.2 log信息不全

  1. 缺少上下文信息

    解决方案:使用LogAdapter简化操作

    有时打印log时容易信息不全,容易混淆两个类的信息,例如:

    log后的信息都是 Eating apple ,同时,如果类的属性有删减,也需要一个一个修改logging的代码,非常麻烦。

    这时可以使用log的extra来简化操作,代码如下:

    extra的内容也可以在logging模块中全局配置:

    输出示例:

  2. 报错缺少堆栈信息

    解决方案:设置 exc_info=True ,代码示例如下:

  3. 需要临时更换context

    解决方案:实现LoggingContext类,临时改变log的行为(monkey patch)

4.3 level不合适导致磁盘占满

解决方案:可以使用 RotatingHandler 或者 TimeRotatingHandler

4.4 logging浪费过多CPU资源

解决方案:注意log的风格

Python logging 的官方文档明确建议使用占位符格式:

"将变量作为参数传递给日志记录方法,而非自行格式化字符串。"

  • 当使用 %{} 占位符 + 参数传递时,仅在日志实际需要输出时(如当前日志级别≥设置的级别)才会进行字符串格式化。

  • .format() 或 f-string 会立即执行格式化,即使日志被过滤(例如当前级别为 ERROR,但调用的是 INFO 日志)。

  • 节省资源:在高并发或频繁记录低级别日志(如 DEBUG)时,避免不必要的字符串操作。

下面是代码示例:

5 总结

从远古的绳结到现代的日志系统,记录的核心目的从未改变——保存重要信息,传递关键知识。Python的logging模块提供了强大而灵活的日志解决方案,是每个Python开发者必须掌握的核心技能之一。

参考

Last updated