4、Python日志模块入门:logging模块基础、Handler、Formatter、Filter的使用

做运动控制调试,最怕什么?

我告诉你,最怕程序跑飞了,你连它死在哪一步都不知道。

早期我做伺服驱动调试,全靠print大法。代码里到处是print("step1")、print("step2")。后来发现,print输出混在一起,根本分不清是哪个模块打的。更坑的是,正式上线前还得一个个删掉,删漏一个就等着被领导骂。

直到我用了Python的logging模块,才算是真正解放了。今天我就把这套东西掰开揉碎了讲给你听。

4.1 为什么不用print?

print的问题很明显:

  • 没有级别区分——紧急信息和普通信息混在一起
  • 没有时间戳——你根本不知道这条日志是什么时候打的
  • 没有目标控制——要么全打印,要么全不打印
  • 正式环境还得删代码——这是最蠢的做法

logging模块就是来解决这些问题的。它把日志分成了五个级别:DEBUG、INFO、WARNING、ERROR、CRITICAL。你可以控制只输出某个级别以上的日志。

核心概念:logging模块由四个组件构成——Logger(记录器)、Handler(处理器)、Formatter(格式化器)、Filter(过滤器)。说白了,Logger负责产生日志,Handler负责把日志送到该去的地方,Formatter决定日志长什么样,Filter决定哪些日志能过。

4.2 最简单的用法

先来个最基础的例子。你什么都不用配置,直接就能用:

import logging

logging.debug("这是debug信息")
logging.info("这是info信息")
logging.warning("这是warning信息")
logging.error("这是error信息")
logging.critical("这是critical信息")

运行一下你会发现,只有WARNING及以上级别的日志输出了。为什么?因为默认的日志级别就是WARNING。这是Python设计者的考虑——正式环境一般不需要看DEBUG和INFO信息。

我的习惯:开发阶段我会把级别设成DEBUG,上线前改成INFO或WARNING。这样调试信息不会污染正式日志。

4.3 配置日志级别和格式

光用默认配置肯定不够。你需要自己控制日志的格式和输出级别。看这个例子:

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

logging.debug("这条会输出")
logging.info("这条也会输出")

basicConfig是快速配置的方法。但要注意,它只能调用一次,后面的调用会失效。我在项目中吃过这个亏——在多个模块里分别调了basicConfig,结果只有第一个生效。

4.4 Handler——日志该去哪?

Handler决定了日志的最终去向。常见的Handler有:

  • StreamHandler——输出到控制台
  • FileHandler——输出到文件
  • RotatingFileHandler——按文件大小滚动
  • TimedRotatingFileHandler——按时间滚动

我一般会同时用两个Handler:一个输出到控制台方便实时看,一个写入文件方便事后查。像这样:

import logging

logger = logging.getLogger("motion_control")
logger.setLevel(logging.DEBUG)

# 控制台Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# 文件Handler
file_handler = logging.FileHandler("motion.log")
file_handler.setLevel(logging.DEBUG)

# 给logger添加Handler
logger.addHandler(console_handler)
logger.addHandler(file_handler)

logger.debug("这条只进文件")
logger.info("控制台和文件都能看到")

注意:Handler也有自己的级别。Logger的级别是总开关,Handler的级别是分开关。只有两个级别都通过了,日志才会输出。我曾经在这里栽过跟头——Logger设了DEBUG,但Handler设了WARNING,结果DEBUG信息死活不出来。

4.5 Formatter——日志长什么样?

Formatter就是给日志化妆的。你可以控制它显示时间、模块名、行号、线程ID等等。常用的格式字段:

字段 含义 示例
%(asctime)s 时间戳 2024-01-15 14:30:25
%(name)s Logger名称 motion_control
%(levelname)s 日志级别 INFO
%(message)s 日志内容 电机启动成功
%(filename)s 文件名 main.py
%(lineno)d 行号 42

我个人习惯的格式是:

formatter = logging.Formatter(
    '%(asctime)s | %(name)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s'
)

这样一眼就能看出日志来自哪个文件的哪一行,调试起来特别方便。

4.6 Filter——日志的守门员

Filter的作用是过滤日志。比如你只想看某个模块的ERROR日志,或者只想看包含特定关键字的日志。Filter可以精确控制。

举个例子,假设你只想记录包含"电机"二字的日志:

import logging

class MotorFilter(logging.Filter):
    def filter(self, record):
        return "电机" in record.getMessage()

logger = logging.getLogger("motion_control")
logger.addFilter(MotorFilter())

logger.info("电机启动成功")  # 这条会通过
logger.info("传感器数据异常")  # 这条会被过滤掉

避坑指南:Filter是在Handler之前执行的。如果你在Logger上加了Filter,所有Handler都会受影响。如果你只想对某个Handler做过滤,就把Filter加在那个Handler上。

4.7 知识体系总览

下面这张图帮你理清logging模块的整体结构:

Python Logging 模块核心结构 Logger(记录器) Filter(过滤器) Handler(处理器) Formatter(格式化器) 输出目标 控制台 | 文件 | 滚动文件 | 网络 StreamHandler | FileHandler | RotatingFileHandler 数据流向:Logger → Filter → Handler → Formatter → 输出目标

4.8 实战建议

最后给你几个我在运动控制项目中的实战建议:

  1. 按模块创建Logger——每个运动轴、每个传感器模块都创建独立的Logger,方便定位问题
  2. 日志文件要滚动——运动控制程序可能跑几天几夜,日志文件会非常大。用RotatingFileHandler按大小滚动,或者用TimedRotatingFileHandler按天滚动
  3. 关键操作打INFO——电机启停、参数变更这些关键操作,一定要打INFO日志
  4. 异常信息打ERROR——捕获到异常时,用logger.exception()会自动带上堆栈信息
  5. 调试信息打DEBUG——PID计算值、传感器原始数据这些,打DEBUG级别,正式环境关掉

总结一下:logging模块其实不复杂。记住四个组件——Logger、Handler、Formatter、Filter。Logger负责产生,Handler负责发送,Formatter负责美化,Filter负责把关。把这四个东西搞明白了,你的日志系统就稳了。


专注资料整理