3、Python日志模块基础:logging模块介绍、Logger、Handler、Formatter、Filter组件详解

做ADAS系统调试,说白了就是跟数据流和异常打交道。我刚开始做这行的时候,习惯用print大法,结果有一次在实车上跑,日志刷了十几万行,根本找不到关键信息。从那以后,我老老实实把Python的logging模块捡起来,认真研究了一遍。

今天咱们就聊聊这个logging模块。它不是什么花哨的东西,但绝对是调试工具链的基石。你想想看,没有好的日志,出了问题就像在黑屋子里找一只黑猫,太难了。

3.1 logging模块是什么?

Python自带的logging模块,说白了就是一个日志框架。它帮你解决三个问题:

  • 日志往哪写——控制台、文件、网络,随你选
  • 日志怎么写——带时间戳、带行号、带模块名,格式自己定
  • 哪些日志要写——DEBUG级别的调试信息要不要?ERROR级别的错误必须写

我个人习惯,在ADAS项目里,日志系统是第一个搭建的模块。为什么?因为后面所有的调试、性能分析、故障排查,都依赖它。

核心思想:logging模块采用分层架构,各组件各司其职。你不需要一次性掌握所有细节,先理解四个核心组件就够了。

3.2 四个核心组件详解

logging模块有四个核心组件:Logger、Handler、Formatter、Filter。我按它们在日志流中的顺序来讲。

3.2.1 Logger——日志的入口

Logger是日志记录器,你的代码里调用的logger.info()logger.error(),就是它在干活。

每个Logger都有一个名字,通常用模块名来命名。比如:

import logging

# 创建一个名为'sensor_fusion'的logger
logger = logging.getLogger('sensor_fusion')
logger.setLevel(logging.DEBUG)

# 使用它记录日志
logger.debug('开始融合雷达数据')
logger.info('融合完成,输出目标列表')
logger.warning('雷达数据延迟超过100ms')
logger.error('雷达数据丢失,融合失败')

嗯,这里要注意:Logger是有层级关系的。比如sensor_fusion.lidarsensor_fusion的子Logger。子Logger默认会继承父Logger的设置,但也可以覆盖。

我的经验:在ADAS项目中,我习惯按功能模块创建Logger。比如adas.perceptionadas.planningadas.control。这样在日志里一眼就能看出问题出在哪个模块。

3.2.2 Handler——日志的目的地

Handler决定日志往哪写。常见的Handler有:

Handler类型 用途 适用场景
StreamHandler 输出到控制台 开发调试时实时查看
FileHandler 输出到文件 记录完整日志,便于事后分析
RotatingFileHandler 按文件大小轮转 防止日志文件过大
TimedRotatingFileHandler 按时间轮转 按天/小时生成日志文件

一个Logger可以绑定多个Handler。比如开发时,我既想看到控制台输出,又想保存到文件:

import logging

logger = logging.getLogger('adas.perception')
logger.setLevel(logging.DEBUG)

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

# 文件Handler
file_handler = logging.FileHandler('perception.log')
file_handler.setLevel(logging.DEBUG)

# 绑定到Logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)

我曾经在实车测试时,因为日志文件太大把存储空间撑爆了。后来改用RotatingFileHandler,设置每个文件10MB,保留最近5个文件,再也没出过问题。

3.2.3 Formatter——日志的格式

Formatter决定日志长什么样。默认的格式太简单,我一般会自定义:

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

# 应用到Handler
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

常用的格式化字段:

字段 含义 示例
%(asctime)s 时间戳 2024-01-15 14:30:22,123
%(name)s Logger名称 adas.perception
%(levelname)s 日志级别 INFO, ERROR
%(filename)s 文件名 lidar_fusion.py
%(lineno)d 行号 42
%(message)s 日志内容 融合完成

避坑指南:我曾经在日志里忘记加行号,结果定位问题时要一行一行数代码。从那以后,我的Formatter里永远有%(filename)s:%(lineno)d

3.2.4 Filter——日志的过滤器

Filter用来做更精细的日志控制。比如我只想看某个模块的WARNING以上日志:

import logging

class ModuleFilter(logging.Filter):
    def filter(self, record):
        # 只保留adas.perception模块的日志
        return record.name.startswith('adas.perception')

# 应用Filter
handler = logging.StreamHandler()
handler.addFilter(ModuleFilter())

Filter比setLevel更灵活。你可以根据日志内容、模块名、甚至自定义条件来决定是否记录。

3.3 完整的配置示例

好了,咱们把上面这些组件串起来,看看一个完整的ADAS日志配置长什么样:

import logging
from logging.handlers import RotatingFileHandler

def setup_adas_logger():
    # 创建Logger
    logger = logging.getLogger('adas')
    logger.setLevel(logging.DEBUG)
    
    # 格式化器
    formatter = logging.Formatter(
        '%(asctime)s | %(name)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    
    # 控制台Handler - 只显示INFO及以上
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    console.setFormatter(formatter)
    
    # 文件Handler - 记录所有级别,按大小轮转
    file_handler = RotatingFileHandler(
        'adas_debug.log',
        maxBytes=10*1024*1024,  # 10MB
        backupCount=5
    )
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(formatter)
    
    # 错误文件Handler - 只记录ERROR及以上
    error_handler = RotatingFileHandler(
        'adas_error.log',
        maxBytes=5*1024*1024,  # 5MB
        backupCount=3
    )
    error_handler.setLevel(logging.ERROR)
    error_handler.setFormatter(formatter)
    
    # 绑定Handler
    logger.addHandler(console)
    logger.addHandler(file_handler)
    logger.addHandler(error_handler)
    
    return logger

# 使用
logger = setup_adas_logger()
logger.info('ADAS日志系统初始化完成')
logger.debug('传感器校准参数: x=0.02, y=0.01')
logger.error('CAN总线通信超时')

我的建议:在ADAS项目中,至少配置三个Handler:控制台(开发用)、完整日志文件(调试用)、错误日志文件(问题排查用)。这样既能实时看到关键信息,又不会丢失细节。

3.4 日志级别选择策略

日志级别怎么选?我一般遵循这个原则:

  • DEBUG:开发阶段的详细信息,比如传感器原始数据、中间计算结果
  • INFO:正常流程的关键节点,比如模块启动、配置加载、状态切换
  • WARNING:不影响运行但需要注意的情况,比如数据延迟、参数接近阈值
  • ERROR:功能异常但系统还能继续运行,比如某个传感器数据丢失
  • CRITICAL:系统无法继续运行,必须停机处理

你想想看,如果所有日志都用INFO级别,那真正重要的错误信息就会被淹没。我见过有人把每个循环都打INFO日志,结果日志文件一天就几十个G。

3.5 小结

logging模块其实不复杂,核心就是四个组件:Logger负责记录,Handler负责输出,Formatter负责格式化,Filter负责过滤。把它们组合好,就能搭建一个灵活、高效的日志系统。

下一章,咱们会把这些组件用到实际的ADAS调试场景中,看看怎么用日志来定位传感器数据异常、算法性能瓶颈这些问题。