第二章:Python日志模块(logging)实战
日志这东西,说白了就是系统的「黑匣子」。我做了这么多年量化交易系统运维,见过太多因为日志没配好,出问题时两眼一抹黑的惨案。今天咱们就好好聊聊Python自带的logging模块——别看它基础,用好了真能救命。
2.1 Logger:日志系统的入口
Logger是整个日志体系的起点。你可以把它想象成一个「日志管理员」,负责决定哪些日志该记录、记录到什么级别。
核心要点:每个Logger都有一个名字,通常用模块名命名。名字用点号分隔,形成层级关系。
import logging
# 创建Logger,建议用__name__
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # 设置最低日志级别
我个人习惯在项目入口处只配置root logger,子模块各自创建自己的logger。这样既能统一管理,又能灵活控制。
小技巧:Logger的名字不要用root,那是系统保留的。用模块名最稳妥。
2.2 Handler:日志该去哪?
Handler决定日志的「目的地」。是写到文件?打印到控制台?还是通过网络发出去?全看Handler怎么配。
常见的Handler有这么几种:
| Handler类型 | 用途 | 适用场景 |
|---|---|---|
| StreamHandler | 输出到控制台 | 开发调试 |
| FileHandler | 写入单个文件 | 简单日志存储 |
| RotatingFileHandler | 按大小轮转 | 生产环境 |
| TimedRotatingFileHandler | 按时间轮转 | 按天/小时切割 |
我在项目中遇到过一个问题:某个交易策略的日志文件涨到了10GB,服务器磁盘直接爆了。从那以后,我强制要求所有生产环境必须用RotatingFileHandler。
2.3 Formatter:日志长什么样?
Formatter就是日志的「化妆师」。同样的信息,格式不同,可读性天差地别。
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
常用的格式化字段:
%(asctime)s— 时间戳,默认格式是YYYY-MM-DD HH:MM:SS%(name)s— Logger的名字%(levelname)s— 日志级别(DEBUG、INFO、WARNING等)%(message)s— 实际的日志内容%(filename)s— 文件名%(lineno)d— 行号
注意:Formatter的格式字符串里不要漏了括号和百分号,写错了不会报错,但日志内容会乱掉。我曾经因为这个排查了整整一个下午。
2.4 Filter:日志的「安检员」
Filter用来做日志过滤。比如我只想看某个模块的ERROR日志,或者想过滤掉某些敏感信息。
class SensitiveFilter(logging.Filter):
def filter(self, record):
# 过滤掉包含密码的日志
return 'password' not in record.getMessage()
logger.addFilter(SensitiveFilter())
嗯,这里要注意:Filter返回True表示放行,False表示拦截。别搞反了。
2.5 日志轮转:RotatingFileHandler实战
这是生产环境最常用的配置。说白了就是:日志文件写到一定大小,自动切一个新文件,旧的保留备份。
import logging
from logging.handlers import RotatingFileHandler
# 创建logger
logger = logging.getLogger('trading_system')
logger.setLevel(logging.DEBUG)
# 创建RotatingFileHandler
handler = RotatingFileHandler(
'trading.log', # 日志文件名
maxBytes=10*1024*1024, # 10MB
backupCount=5 # 保留5个备份
)
# 设置格式
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
# 添加到logger
logger.addHandler(handler)
# 使用示例
logger.info('交易系统启动成功')
logger.error('订单执行失败,错误码:1001')
为什么用10MB?我个人经验是:对于量化交易系统,10MB大概能记录1-2小时的详细日志。太小了轮转太频繁,太大了查日志费劲。
最佳实践:生产环境建议同时配两个Handler:一个RotatingFileHandler写文件,一个StreamHandler打控制台。文件日志记录DEBUG级别,控制台只显示INFO及以上。这样既保留了完整记录,又不会让控制台刷屏。
2.6 完整配置示例
下面是我在量化交易项目中常用的配置模板,你可以直接拿来用:
import logging
import logging.handlers
import os
def setup_logging(log_dir='logs', log_name='trading'):
"""配置日志系统"""
os.makedirs(log_dir, exist_ok=True)
# 创建logger
logger = logging.getLogger('trading')
logger.setLevel(logging.DEBUG)
# 文件Handler - 记录所有日志
file_handler = logging.handlers.RotatingFileHandler(
os.path.join(log_dir, f'{log_name}.log'),
maxBytes=10*1024*1024,
backupCount=5,
encoding='utf-8'
)
file_handler.setLevel(logging.DEBUG)
# 控制台Handler - 只显示INFO及以上
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 格式化
formatter = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(name)s | %(message)s'
)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# 添加Handler
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
避坑指南:我曾经犯过一个错误——在多个模块里重复配置logger,导致日志重复输出。解决办法是:在程序入口只配置一次,其他模块直接logging.getLogger(__name__)获取即可。
2.7 知识体系总览
下面这张图展示了logging模块的核心组件和它们之间的关系:
你看,整个流程其实很清晰:Logger接收日志消息,交给Handler处理,Handler用Formatter格式化,最后输出到目标。Filter可以在Logger或Handler层面做拦截。
配置好日志系统,就像给交易系统装上了「行车记录仪」。平时可能觉得没啥用,一旦出问题,它就是你的救命稻草。记住:日志不是写给别人看的,是写给未来的自己看的。