2. Python日志模块基础:logging模块核心组件与基本配置
日志这东西,说白了就是系统的「黑匣子」。我在做量化交易系统时,有一次凌晨三点策略突然停止运行,要不是日志记录得完整,根本不可能在十分钟内定位到问题——原来是某个交易所的API接口返回格式变了。嗯,从那以后,我对日志的重视程度直接拉满。
Python自带的logging模块,别看它是标准库,功能一点都不弱。很多新手喜欢用print打日志,我建议你趁早改掉这个习惯。为什么?因为print输出到控制台就没了,而logging可以同时写文件、发邮件、输出到终端,还能按级别过滤。你想想看,生产环境里谁会盯着控制台看?
2.1 核心组件:Logger、Handler、Formatter、Filter
logging模块有四个核心组件,我习惯把它们比作一个「日志流水线」:
- Logger:日志的入口,你代码里调用的
logger.info()就是它 - Handler:决定日志去哪,文件、控制台、网络,都靠它
- Formatter:决定日志长什么样,时间、级别、消息,格式你说了算
- Filter:决定哪些日志能过,哪些要拦,精细控制
我曾经见过一个团队,所有日志都往根Logger上怼,结果日志文件一个月涨到200GB。这就是典型的「只用Logger,不管Handler」的后果。
核心关系图:日志从产生到输出的完整流程
2.2 Logger:日志的起点
Logger是你要直接打交道的对象。我个人习惯给每个模块创建一个独立的Logger,而不是用全局的根Logger。
import logging
# 创建Logger
logger = logging.getLogger('quant_system.order_manager')
logger.setLevel(logging.DEBUG)
# 记录日志
logger.debug('订单参数校验开始')
logger.info('订单已提交,订单号: %s', order_id)
logger.warning('订单执行延迟,当前延迟: %.2f秒', delay)
logger.error('订单执行失败,错误码: %d', error_code)
logger.critical('系统内存不足,无法继续执行')
这里有个坑:setLevel设置的是Logger自己的级别,但最终生效的是Logger级别和Handler级别的「交集」——取两者中更严格的那个。我曾经调试了半天,发现Logger设了DEBUG,Handler设了WARNING,结果DEBUG日志死活不输出。嗯,这个细节很容易忽略。
命名建议:用__name__作为Logger名称,这样能自动继承模块的包路径。比如logger = logging.getLogger(__name__),日志里会显示quant_system.order_manager,定位问题非常方便。
2.3 Handler:日志去哪,它说了算
Handler决定了日志的「归宿」。常用的Handler有这么几种:
| Handler类型 | 输出目标 | 适用场景 |
|---|---|---|
| StreamHandler | 控制台(stdout/stderr) | 开发调试、本地运行 |
| FileHandler | 单个文件 | 简单日志记录 |
| RotatingFileHandler | 按大小轮转的文件 | 生产环境,防止日志撑爆磁盘 |
| TimedRotatingFileHandler | 按时间轮转的文件 | 需要按天/小时归档的场景 |
| SMTPHandler | 邮件 | 错误告警通知 |
我在量化系统里最常用的是TimedRotatingFileHandler。为什么?因为交易日志每天几百MB,如果不按天切割,一个月后一个文件几个GB,编辑器都打不开。而且按天切割还有个好处——方便回溯某一天的交易记录。
from logging.handlers import TimedRotatingFileHandler
# 按天轮转,保留30天
handler = TimedRotatingFileHandler(
filename='logs/trading.log',
when='midnight', # 每天午夜切割
interval=1, # 间隔1天
backupCount=30, # 保留30个备份
encoding='utf-8'
)
handler.setLevel(logging.INFO)
注意:backupCount不是越大越好。我曾经有个同事设了365,结果一年后磁盘满了才发现。建议根据磁盘空间和日志量合理设置,一般量化系统保留30-90天就够了。
2.4 Formatter:日志长什么样
Formatter就是日志的「化妆师」。同样的信息,格式不同,可读性天差地别。
# 常用格式
formatter = logging.Formatter(
fmt='%(asctime)s | %(levelname)-8s | %(name)s | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 输出示例:
# 2024-01-15 14:30:22 | INFO | quant_system.order_manager | 订单已提交
我个人习惯的格式包含四个要素:时间、级别、模块名、消息。为什么?因为排查问题时,你首先要知道「什么时候发生的」「严重程度如何」「哪个模块出的」「具体是什么」。缺一个都可能让你多花半小时。
常用的格式化字段:
%(asctime)s:时间,配合datefmt自定义格式%(levelname)s:日志级别,建议用%-8s左对齐固定宽度%(name)s:Logger名称,定位模块%(message)s:日志内容%(filename)s:文件名,调试时有用%(lineno)d:行号,精准定位代码位置
2.5 Filter:精细控制日志输出
Filter用得相对少,但某些场景下特别好用。比如我只想记录某个特定订单的日志,或者过滤掉某些敏感信息。
class SensitiveDataFilter(logging.Filter):
"""过滤掉包含敏感信息的日志"""
def filter(self, record):
# 如果日志消息包含密码字段,直接过滤
if 'password' in record.getMessage().lower():
return False
return True
# 使用
logger.addFilter(SensitiveDataFilter())
我曾经在日志里不小心打印了用户的API密钥,还好是测试环境。从那以后,我所有生产环境的Logger都加了一个敏感信息过滤器。你想想看,如果日志泄露了交易所的API Key,后果不堪设想。
2.6 基本配置:快速上手
对于小型项目,用basicConfig一把梭就够了。但注意,它只能调用一次,后续调用无效。
import logging
# 快速配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s | %(levelname)-8s | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
filename='app.log',
filemode='a' # 追加模式
)
# 直接使用根Logger
logging.info('系统启动成功')
但说实话,basicConfig只适合脚本和简单应用。真正的量化系统,我建议用更灵活的配置方式——手动创建Logger、Handler、Formatter,然后组装起来。
import logging
from logging.handlers import RotatingFileHandler
# 创建Logger
logger = logging.getLogger('quant_system')
logger.setLevel(logging.DEBUG)
# 创建控制台Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 创建文件Handler(按大小轮转)
file_handler = RotatingFileHandler(
'logs/system.log',
maxBytes=10*1024*1024, # 10MB
backupCount=5,
encoding='utf-8'
)
file_handler.setLevel(logging.DEBUG)
# 创建Formatter
formatter = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(name)s | %(message)s'
)
# 组装
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# 使用
logger.info('系统初始化完成')
logger.debug('加载配置文件: config.yaml')
最佳实践:开发环境用控制台输出INFO及以上级别,生产环境用文件记录DEBUG及以上级别。这样开发时不会被大量DEBUG日志刷屏,生产环境又能保留完整的调试信息。
2.7 避坑指南
最后分享几个我踩过的坑:
- 重复日志:每次调用
basicConfig或addHandler都会添加新的Handler,如果不检查,日志会重复输出。我习惯在添加Handler前先判断logger.handlers是否为空。 - 编码问题:中文日志在Windows上经常乱码。记得在FileHandler里指定
encoding='utf-8'。 - 性能问题:DEBUG级别的日志如果包含字符串格式化,即使不输出也会执行。用
logger.debug('耗时操作: %s', expensive_func())比logger.debug(f'耗时操作: {expensive_func()}')更高效,因为前者只在需要输出时才计算。 - 日志泄露:永远不要在日志里打印密码、密钥、个人隐私信息。加Filter是个好习惯。
嗯,logging模块的基础就这些。掌握了这四个核心组件,你就能搭建出适合量化系统的日志体系了。下一节我们会聊怎么把这些组件组织成一个完整的日志配置,以及如何应对生产环境的各种挑战。
公众号:蓝海资料掘金营,微信deep3321