目录

Python 日志处理 logging

学习目标

  • 掌握 logging 模块的基本用法
  • 理解日志级别和日志记录器的层次结构
  • 学会配置日志处理器和格式化器
  • 掌握日志配置文件的使用

1. 基础用法

1.1 简单日志输出

import logging

# 基本配置(一次性)
logging.basicConfig(level=logging.INFO)

# 不同级别的日志
logging.debug("调试信息")     # 最详细
logging.info("普通信息")      # 一般信息
logging.warning("警告信息")   # 警告
logging.error("错误信息")     # 错误
logging.critical("严重错误")  # 最严重

# 输出:
# WARNING:root:警告信息
# ERROR:root:错误信息
# CRITICAL:root:严重错误
# (默认级别是 WARNING,低于此级别的不会显示)

1.2 基本配置

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

logging.info("程序启动")
logging.debug("调试: x = %d", 42)

# 输出:
# 2024-01-15 14:30:00 - INFO - 程序启动
# 2024-01-15 14:30:00 - DEBUG - 调试: x = 42

2. 日志级别

级别 数值 用途
DEBUG 10 详细的调试信息
INFO 20 确认程序按预期运行
WARNING 30 警告,但程序仍继续运行
ERROR 40 错误,某项功能无法正常执行
CRITICAL 50 严重错误,程序可能无法继续
import logging

# 设置级别
logger = logging.getLogger('myapp')
logger.setLevel(logging.DEBUG)

# 检查级别
print(logger.isEnabledFor(logging.DEBUG))   # True
print(logger.isEnabledFor(logging.INFO))    # True
print(logger.isEnabledFor(logging.WARNING)) # True

3. 日志记录器层次结构

import logging

# 创建命名记录器
# 层次: root -> app -> app.module -> app.module.sub
logger = logging.getLogger('app.module')

# 子记录器继承父记录器的配置
parent = logging.getLogger('app')
child = logging.getLogger('app.module')
grandchild = logging.getLogger('app.module.sub')

# 设置父记录器级别
parent.setLevel(logging.WARNING)

# 子记录器继承级别
print(child.level)       # 0(继承父级别)
print(child.getEffectiveLevel())  # 30(WARNING)

# 子记录器可独立设置
child.setLevel(logging.DEBUG)
print(child.getEffectiveLevel())  # 10(DEBUG)

4. 处理器和格式化器

4.1 输出到控制台

import logging

# 创建记录器
logger = logging.getLogger('mylogger')
logger.setLevel(logging.DEBUG)

# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# 创建格式化器
formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console_handler.setFormatter(formatter)

# 添加处理器
logger.addHandler(console_handler)

# 使用
logger.info("普通信息")
logger.debug("调试信息(不会显示,因为处理器级别是 INFO)")

4.2 输出到文件

import logging

logger = logging.getLogger('filelogger')
logger.setLevel(logging.DEBUG)

# 文件处理器
file_handler = logging.FileHandler('app.log', encoding='utf-8')
file_handler.setLevel(logging.DEBUG)

# 格式化器
formatter = logging.Formatter(
    '%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d - %(message)s'
)
file_handler.setFormatter(formatter)

logger.addHandler(file_handler)

logger.info("写入日志文件")
logger.error("发生错误")

4.3 多处理器组合

import logging

logger = logging.getLogger('multi')
logger.setLevel(logging.DEBUG)

# 控制台处理器 - 只显示 WARNING 及以上
console = logging.StreamHandler()
console.setLevel(logging.WARNING)
console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))

# 文件处理器 - 记录所有级别
file_handler = logging.FileHandler('debug.log', encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(
    '%(asctime)s [%(levelname)s] %(message)s'
))

# 添加两个处理器
logger.addHandler(console)
logger.addHandler(file_handler)

# 使用
logger.debug("调试信息(只在文件)")
logger.info("普通信息(只在文件)")
logger.warning("警告(文件+控制台)")
logger.error("错误(文件+控制台)")

5. 日志轮转

5.1 按大小轮转

import logging
from logging.handlers import RotatingFileHandler

logger = logging.getLogger('rotating')
logger.setLevel(logging.DEBUG)

# 文件最大 1MB,保留 3 个备份
handler = RotatingFileHandler(
    'app.log',
    maxBytes=1024 * 1024,  # 1MB
    backupCount=3,
    encoding='utf-8'
)
handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
logger.addHandler(handler)

# 大量日志测试
for i in range(10000):
    logger.info(f"日志行 {i}: " + "x" * 100)

5.2 按时间轮转

import logging
from logging.handlers import TimedRotatingFileHandler

logger = logging.getLogger('timed')
logger.setLevel(logging.DEBUG)

# 每天轮转,保留 7 天
handler = TimedRotatingFileHandler(
    'app.log',
    when='midnight',  # 每天午夜轮转
    interval=1,
    backupCount=7,
    encoding='utf-8'
)
handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
logger.addHandler(handler)

6. 配置方式

6.1 代码配置

import logging

def setup_logging():
    """通过代码配置日志"""
    
    # 根记录器
    root = logging.getLogger()
    root.setLevel(logging.DEBUG)
    
    # 控制台处理器
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
    root.addHandler(console)
    
    # 文件处理器
    file_handler = logging.FileHandler('app.log', encoding='utf-8')
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(logging.Formatter(
        '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
    ))
    root.addHandler(file_handler)

setup_logging()
logging.info("日志已配置")

6.2 字典配置(推荐)

import logging
import logging.config

LOGGING_CONFIG = {
    'version': 1,
    'disable_existing_loggers': False,
    
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
        'simple': {
            'format': '%(levelname)s: %(message)s'
        },
    },
    
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'INFO',
            'formatter': 'simple',
        },
        'file': {
            'class': 'logging.FileHandler',
            'filename': 'app.log',
            'level': 'DEBUG',
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
    },
    
    'loggers': {
        'myapp': {
            'level': 'DEBUG',
            'handlers': ['console', 'file'],
            'propagate': False,
        },
    },
    
    'root': {
        'level': 'WARNING',
        'handlers': ['console'],
    },
}

logging.config.dictConfig(LOGGING_CONFIG)

logger = logging.getLogger('myapp')
logger.info("使用字典配置")

6.3 配置文件

# logging.conf
[loggers]
keys=root,myapp

[handlers]
keys=console,file

[formatters]
keys=standard,simple

[logger_root]
level=WARNING
handlers=console

[logger_myapp]
level=DEBUG
handlers=console,file
qualname=myapp
propagate=0

[handler_console]
class=StreamHandler
level=INFO
formatter=simple
args=(sys.stdout,)

[handler_file]
class=FileHandler
level=DEBUG
formatter=standard
args=('app.log', 'a', 'utf-8')

[formatter_standard]
format=%(asctime)s [%(levelname)s] %(name)s: %(message)s

[formatter_simple]
format=%(levelname)s: %(message)s
# 使用配置文件
import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('myapp')
logger.info("使用配置文件")

7. 实用技巧

7.1 异常信息记录

import logging

logging.basicConfig(level=logging.ERROR)

try:
    1 / 0
except Exception:
    # 自动记录异常 traceback
    logging.exception("发生错误")

# 输出包含完整的堆栈信息

7.2 自定义过滤器

import logging

class LevelFilter(logging.Filter):
    """只允许指定级别的日志"""
    
    def __init__(self, level):
        self.level = level
    
    def filter(self, record):
        return record.levelno == self.level

# 使用
logger = logging.getLogger('filtered')
handler = logging.StreamHandler()
handler.addFilter(LevelFilter(logging.WARNING))
logger.addHandler(handler)

7.3 上下文信息

import logging

logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger('context')

# 使用 extra 添加上下文
logger.info('用户操作', extra={'user_id': '12345'})

本节小结

  • 级别:DEBUG < INFO < WARNING < ERROR < CRITICAL
  • 记录器:通过 getLogger(name) 获取,支持层次结构
  • 处理器StreamHandler(控制台)、FileHandler(文件)、RotatingFileHandler(轮转)
  • 格式化器:控制日志输出格式,支持多种字段
  • 配置方式:代码配置、字典配置(推荐)、配置文件
  • 最佳实践:使用模块级记录器,使用 __name__ 作为记录器名

练习

  1. 配置日志同时输出到控制台(INFO+)和文件(DEBUG+),文件按天轮转
  2. 编写一个装饰器,自动记录函数的入参、出参和执行时间
  3. 创建一个自定义处理器,将 ERROR 以上级别的日志发送到邮件
  4. 实现一个日志过滤器,只记录包含特定关键词的日志