第二章: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 日志输出目的地 File / Stream / Socket Rotating / TimedRotating Formatter 日志格式 Filter 日志过滤 输出目标 控制台 / 文件 网络 / 数据库 日志处理流程:Logger → Handler → Formatter → 输出目标 Filter 可在 Logger 或 Handler 级别进行过滤 Python logging 模块核心组件关系图

你看,整个流程其实很清晰:Logger接收日志消息,交给Handler处理,Handler用Formatter格式化,最后输出到目标。Filter可以在Logger或Handler层面做拦截。

配置好日志系统,就像给交易系统装上了「行车记录仪」。平时可能觉得没啥用,一旦出问题,它就是你的救命稻草。记住:日志不是写给别人看的,是写给未来的自己看的。