人人都会AI编程

18.3 logging:日志模块、分级日志、日志配置、日志轮转

更新时间:2026-07-12

程序运行中总会遇到各种情况:正常请求、意料之外的错误、性能瓶颈的蛛丝马迹。仅靠 print() 输出,在项目规模变大后会立刻暴露问题——信息杂乱、无法分级、难以持久化和搜索。logging 模块是标准库内置的专业日志方案,用很小的学习成本换来可维护的日志体系。

为什么不用 print()

  • print() 无法区分信息重要性,调试时加的一句输出和严重错误的提示混在一起。
  • 不能方便地输出到文件、只显示部分级别的日志。
  • 不能自动添加时间戳、模块名等信息。
  • 多线程/多进程下输出容易交错。

logging 模块天然解决了这些问题,并且用法并不复杂。

日志级别:让重要信息自动凸显

logging 定义了五个标准级别,数值上 DEBUG < INFO < WARNING < ERROR < CRITICAL。你可以设置“最低输出级别”,比它级别低的消息会被自动过滤。

import logging

logging.debug('调试信息,变量值为 x=%s', 42)
logging.info('服务正常启动')
logging.warning('磁盘使用超过 80%')
logging.error('数据库连接失败,重试一次')
logging.critical('内存耗尽,进程即将退出')

通常在不同环境选择不同级别

  • 开发时设为 DEBUG,看到全部细节。
  • 生产环境设为 INFOWARNING,避免日志洪水,同时不漏掉关键告警。

基础配置:快速上手

最简单的全局配置用 logging.basicConfig(),通常写在程序入口处。

import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('app.log', encoding='utf-8'),  # 持续写入文件
        logging.StreamHandler()                            # 同时在控制台输出
    ]
)

logger = logging.getLogger(__name__)
logger.info('应用启动')

格式化常用字段

  • %(asctime)s:时间戳
  • %(name)s:logger 的名字,通常用 name 对应模块
  • %(levelname)s:级别名(DEBUG/INFO/WARNING/ERROR/CRITICAL)
  • %(message)s:日志消息本身
  • %(filename)s:%(lineno)d:文件名和行号,调试时极其有用

进阶用法:logger 对象与层次结构

在大型项目中,推荐每个模块显式创建自己的 logger,而非直接 logging.info()。logger 名称使用点号分隔的层级(如 myapp.auth),子 logger 默认继承父 logger 的配置,你可以精细控制不同模块的日志级别。

# modules/user.py
import logging
logger = logging.getLogger(__name__)  # __name__ = 'modules.user'

logger.debug('用户登录检查')

然后在主入口统一配置:

logging.basicConfig(level=logging.WARNING)          # 全局只显示 WARNING 以上
logging.getLogger('modules.user').setLevel(logging.DEBUG)  # 但 user 模块显示 DEBUG

日志轮转:防止单个日志文件无限膨胀

生产环境的应用程序一直运行,日志文件可能会大到难以处理。logging.handlers 提供了两种常用轮转方式:

1. 按文件大小轮转 RotatingFileHandler
当文件达到指定大小后,自动将当前文件重命名(如 app.log 变成 app.log.1),并新建一个空的 app.log 继续写入。

from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler(
    'app.log', maxBytes=10*1024*1024, backupCount=5, encoding='utf-8'
)
# maxBytes=10MB, 最多保留5个旧文件

2. 按时间轮转 TimedRotatingFileHandler
按天、小时或分钟分割日志,适合按日期归档的场景。

from logging.handlers import TimedRotatingFileHandler

handler = TimedRotatingFileHandler(
    'app.log', when='midnight', backupCount=30, encoding='utf-8'
)
# 每天午夜轮转一次,保留最近30天的日志

这些 handler 可以直接加到 basicConfighandlers 参数里,也可以通过 addHandler() 添加。

一个生产级配置示例

结合前面知识,一个常见项目日志配置可能长这样:

import logging
from logging.handlers import RotatingFileHandler

def setup_logging():
    logger = logging.getLogger()          # 根 logger
    logger.setLevel(logging.DEBUG)

    # 控制台只输出 INFO 以上
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    console.setFormatter(logging.Formatter('%(levelname)s - %(message)s'))
    
    # 文件输出所有 DEBUG 以上,并按大小轮转
    file_handler = RotatingFileHandler(
        'logs/app.log', maxBytes=5*1024*1024, backupCount=5, encoding='utf-8'
    )
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(
        logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
        )
    )
    
    logger.addHandler(console)
    logger.addHandler(file_handler)

setup_logging()

# 后续模块中直接使用
logger = logging.getLogger(__name__)

实用建议

  • 日志不要用字符串拼接,使用 %s 格式化,让 logging 模块在不需要输出时可以省去格式化开销。logger.debug('x=%s', x) 是高效且安全的。
  • 合理控制级别:别把正常用户操作记成 ERROR,否则真正错误会被淹没。访问日志用 INFO,业务异常用 WARNINGERROR
  • 避免打印敏感信息:密码、token、手机号等应在日志中脱敏或禁止输出。
  • 异步日志:高并发场景下,日志写磁盘可能成为瓶颈,可以用 QueueHandler 配合后台线程异步写入,或者用第三方库 loguru(其在真项目中的使用也越来越广泛,但标准库 logging 是最基础、无依赖的选择)。

掌握 standard library 的 logging 模块,就足以应对大多数项目的日志需求,而且没有额外依赖,稳定可靠。