4. 日志系统搭建:Python logging 模块详解
在嵌入式设备端与上位机的联合调试中,日志系统是定位异常的第一道防线。Python 标准库 logging 模块提供了灵活且强大的日志框架,能够满足从开发调试到生产监控的全链路需求。本节将深入剖析 logging 模块的核心组件、日志级别、格式化策略以及双输出(文件 + 控制台)的实战配置。
4.1 logging 模块核心组件架构
Python logging 模块采用模块化设计,由四个核心组件协作完成日志的生成、处理与输出。理解这些组件的关系是搭建高效日志系统的基础。
4.2 日志级别详解
logging 模块定义了五个标准日志级别,从低到高依次为 DEBUG、INFO、WARNING、ERROR、CRITICAL。每个级别对应不同的严重程度,开发者可以根据场景选择合适的级别输出日志。
| 级别 | 数值 | 适用场景 | 设备端调试示例 |
|---|---|---|---|
| DEBUG | 10 | 详细的调试信息,仅在开发阶段启用 | 传感器原始数据、寄存器读写值 |
| INFO | 20 | 确认程序按预期运行 | 设备初始化成功、连接建立 |
| WARNING | 30 | 表明潜在问题,但程序仍可运行 | 通信超时重试、配置参数异常 |
| ERROR | 40 | 由于严重问题,部分功能无法执行 | 设备断开连接、校验失败 |
| CRITICAL | 50 | 严重错误,程序可能无法继续运行 | 固件崩溃、硬件故障 |
最佳实践: 在设备端联合调试中,建议将日志级别设置为
DEBUG 以捕获完整链路信息;生产环境则提升至 WARNING 或 ERROR,避免日志文件过快膨胀。
4.3 日志格式定制
Formatter 组件负责将日志记录(LogRecord)转换为可读的字符串。通过定义格式字符串,可以灵活控制日志中包含的时间、级别、模块名、行号、消息等字段。
4.3.1 常用格式字段
| 字段 | 含义 | 输出示例 |
|---|---|---|
%(asctime)s |
日志产生时间 | 2025-03-21 14:30:15,123 |
%(levelname)s |
日志级别名称 | ERROR |
%(name)s |
Logger 名称 | device.sensor |
%(filename)s |
源文件名 | uart_handler.py |
%(lineno)d |
行号 | 42 |
%(message)s |
日志消息正文 | CRC 校验失败 |
4.3.2 典型格式模板
# 开发调试格式(包含详细位置信息)
DEV_FORMAT = '%(asctime)s [%(levelname)-8s] %(filename)s:%(lineno)d - %(message)s'
# 生产环境格式(简洁,包含模块名)
PROD_FORMAT = '%(asctime)s | %(levelname)-8s | %(name)s | %(message)s'
# 设备端专用格式(包含线程ID,便于多线程调试)
DEVICE_FORMAT = '%(asctime)s [%(levelname)-8s] [T:%(thread)d] %(filename)s:%(lineno)d - %(message)s'
4.4 文件与控制台双输出配置
在联合调试场景中,控制台输出用于实时观察程序运行状态,文件输出则用于持久化记录,便于事后分析。通过为 Logger 添加多个 Handler,可以轻松实现双输出。
4.4.1 基础双输出配置
import logging
def setup_logger(name='device_logger', log_file='device_debug.log'):
"""配置日志系统:控制台 + 文件双输出"""
# 1. 创建 Logger 实例
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG) # 全局级别设为 DEBUG
# 2. 定义格式器
formatter = logging.Formatter(
'%(asctime)s [%(levelname)-8s] %(filename)s:%(lineno)d - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 3. 创建控制台 Handler(输出到 stderr)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG) # 控制台输出所有级别
console_handler.setFormatter(formatter)
# 4. 创建文件 Handler(追加模式)
file_handler = logging.FileHandler(log_file, mode='a', encoding='utf-8')
file_handler.setLevel(logging.WARNING) # 文件只记录 WARNING 及以上
file_handler.setFormatter(formatter)
# 5. 将 Handler 添加到 Logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)
return logger
# 使用示例
log = setup_logger()
log.debug('传感器初始化完成') # 仅控制台输出
log.warning('通信超时,第 2 次重试') # 控制台 + 文件均输出
log.error('CRC 校验失败,丢弃数据包') # 控制台 + 文件均输出
4.4.2 双输出流程示意图
4.5 高级配置:按模块分级与日志轮转
在大型设备端项目中,不同模块可能需要不同的日志级别和输出策略。同时,为避免日志文件无限增长,应使用轮转文件处理器。
4.5.1 按模块设置不同级别
import logging
# 创建根 Logger
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
# 通信模块:只记录 WARNING 及以上
comm_logger = logging.getLogger('communication')
comm_logger.setLevel(logging.WARNING)
# 传感器模块:记录所有 DEBUG 信息
sensor_logger = logging.getLogger('sensor')
sensor_logger.setLevel(logging.DEBUG)
# 注意:子 Logger 的级别会继承根 Logger,但可以独立覆盖
4.5.2 使用 RotatingFileHandler 实现日志轮转
from logging.handlers import RotatingFileHandler
# 创建轮转文件处理器:每个文件 5MB,保留 3 个备份
rotating_handler = RotatingFileHandler(
'device_rotation.log',
mode='a',
maxBytes=5 * 1024 * 1024, # 5MB
backupCount=3,
encoding='utf-8'
)
rotating_handler.setLevel(logging.DEBUG)
rotating_handler.setFormatter(formatter)
logger.addHandler(rotating_handler)
注意事项: 在嵌入式设备端,日志文件写入可能影响实时性。建议将日志写入操作放在独立线程中执行,或使用异步日志库(如
loguru)降低 I/O 阻塞风险。同时,注意设备存储空间,合理设置日志轮转参数。
4.6 实战:设备端联合调试日志配置模板
以下是一个完整的日志配置模板,适用于 ErrorHandler 异常捕获与设备端配置的联合调试场景。
import logging
from logging.handlers import RotatingFileHandler
import sys
class DeviceLogger:
"""设备端日志管理器,支持控制台与文件双输出"""
def __init__(self, name='ErrorHandler', log_file='error_handler.log'):
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.DEBUG)
# 防止重复添加 Handler
if not self.logger.handlers:
self._setup_handlers(log_file)
def _setup_handlers(self, log_file):
"""配置 Handler"""
# 格式器:包含时间、级别、模块、行号
formatter = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 控制台 Handler:输出所有级别
console = logging.StreamHandler(sys.stdout)
console.setLevel(logging.DEBUG)
console.setFormatter(formatter)
# 文件 Handler:轮转模式,记录 WARNING 及以上
file_handler = RotatingFileHandler(
log_file,
maxBytes=10 * 1024 * 1024, # 10MB
backupCount=5,
encoding='utf-8'
)
file_handler.setLevel(logging.WARNING)
file_handler.setFormatter(formatter)
self.logger.addHandler(console)
self.logger.addHandler(file_handler)
def get_logger(self):
return self.logger
# 实例化
device_log = DeviceLogger().get_logger()
# 在异常捕获中使用
try:
# 模拟设备通信
raise ConnectionError('设备无响应')
except ConnectionError as e:
device_log.error('通信异常: %s', e)
# 执行重试逻辑...
4.7 日志级别过滤与性能优化
在设备端资源受限的环境中,日志系统的性能开销不可忽视。以下策略可有效降低日志对主业务的影响。
- 级别预检查: 使用
logger.isEnabledFor(level)在构造日志消息前判断是否输出,避免不必要的字符串格式化。 - 延迟求值: 使用
%s占位符而非 f-string,仅在日志实际输出时才格式化消息。 - 异步写入: 将日志写入操作放入队列,由独立线程批量处理。
- 按需开启 DEBUG: 通过配置文件或环境变量动态调整日志级别,生产环境默认关闭 DEBUG。
# 性能优化示例:级别预检查
if logger.isEnabledFor(logging.DEBUG):
# 仅在 DEBUG 级别启用时执行,避免无谓的字符串拼接
logger.debug('传感器数据: temp=%.2f, humidity=%.2f', temp, humidity)
核心要点: 日志系统是调试的“眼睛”,但不当的配置会成为性能的“瓶颈”。在设备端联合调试中,建议采用“控制台全量输出 + 文件按级别过滤”的策略,既保证实时性,又兼顾持久化分析需求。