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.lidar是sensor_fusion的子Logger。子Logger默认会继承父Logger的设置,但也可以覆盖。
我的经验:在ADAS项目中,我习惯按功能模块创建Logger。比如adas.perception、adas.planning、adas.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调试场景中,看看怎么用日志来定位传感器数据异常、算法性能瓶颈这些问题。