日志系统

深入理解 Python 日志系统:从基础到生产实践

在 Python 开发中,日志系统是最基础也最重要的工具之一。无论是调试问题、监控应用状态,还是审计用户行为,日志都扮演着不可或缺的角色。本文将深入探讨 Python 日志系统的核心概念、最佳实践以及生产环境中的应用。


一、为什么需要日志?

1.1 日志的价值

  • 问题诊断:当应用出现异常时,日志是第一时间获取现场信息的窗口
  • 性能监控:通过日志可以追踪关键操作的执行时间
  • 安全审计:记录用户操作,满足合规要求
  • 业务分析:从日志中提取业务指标和趋势

1.2 日志 vs print

很多初学者习惯使用 print 进行调试,但生产环境中应该使用日志系统:

python 复制代码
# ❌ 不推荐
print(f"用户 {user_id} 登录成功")

# ✅ 推荐
import logging
logging.info(f"用户 {user_id} 登录成功")

日志系统的优势:

  • 可配置的输出级别(DEBUG, INFO, WARNING, ERROR, CRITICAL)
  • 支持多输出目标(控制台、文件、邮件等)
  • 可自定义格式
  • 可动态调整日志级别无需重启应用

二、日志系统核心概念

2.1 Logger、Handler、Formatter

Python 日志系统由三个核心组件构成:

ini 复制代码
import logging

# 1. Logger - 日志记录器
logger = logging.getLogger(__name__)

# 2. Handler - 处理器,决定日志输出到哪里
console_handler = logging.StreamHandler()
file_handler = logging.FileHandler('app.log')

# 3. Formatter - 格式化器,决定日志长什么样
formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# 配置
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.setLevel(logging.INFO)

# 使用
logger.info("应用启动成功")
logger.error("发生错误")

2.2 日志级别

级别 数值 用途
DEBUG 10 详细的调试信息
INFO 20 一般信息
WARNING 30 警告信息
ERROR 40 错误信息
CRITICAL 50 严重错误

三、实际代码示例

3.1 基础日志配置

python 复制代码
import logging
import os
from logging.handlers import RotatingFileHandler

def setup_logger(name, log_file=None, level=logging.INFO):
    """
    配置日志系统
    
    Args:
        name: 日志记录器名称
        log_file: 日志文件路径
        level: 日志级别
    
    Returns:
        配置好的 logger 对象
    """
    logger = logging.getLogger(name)
    logger.setLevel(level)
    
    # 避免重复添加 handler
    if logger.handlers:
        return logger
    
    # 创建 formatter
    formatter = logging.Formatter(
        '%(asctime)s | %(name)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    
    # 控制台 handler
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)
    
    # 文件 handler(如果指定)
    if log_file:
        # 确保日志目录存在
        os.makedirs(os.path.dirname(log_file), exist_ok=True)
        
        # 使用 RotatingFileHandler,日志文件超过 10MB 自动轮转
        file_handler = RotatingFileHandler(
            log_file,
            maxBytes=10*1024*1024,  # 10MB
            backupCount=5           # 保留 5 个备份文件
        )
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)
    
    return logger

# 使用示例
logger = setup_logger('my_app', 'logs/app.log')
logger.info("应用初始化完成")

3.2 异常日志记录

python 复制代码
def process_user_data(user_id, data):
    """处理用户数据"""
    logger = logging.getLogger('user_processor')
    
    try:
        logger.info(f"开始处理用户 {user_id} 的数据")
        
        # 模拟处理逻辑
        if not data:
            raise ValueError("数据不能为空")
        
        result = transform_data(data)
        logger.info(f"用户 {user_id} 数据处理成功,结果:{result}")
        return result
        
    except ValueError as e:
        logger.warning(f"用户 {user_id} 数据验证失败:{e}")
        raise
        
    except Exception as e:
        # 记录完整的异常堆栈
        logger.error(f"处理用户 {user_id} 数据时发生异常", exc_info=True)
        raise

def transform_data(data):
    """转换数据"""
    return data.upper()

# 测试
if __name__ == "__main__":
    setup_logger('test', 'logs/test.log')
    try:
        process_user_data(123, "")
    except:
        pass

3.3 结构化日志

对于机器可读的日志,可以使用结构化格式(如 JSON):

python 复制代码
import json
from datetime import datetime

class JSONFormatter(logging.Formatter):
    """自定义 JSON 格式日志"""
    
    def format(self, record):
        log_record = {
            'timestamp': datetime.utcnow().isoformat(),
            'level': record.levelname,
            'logger': record.name,
            'message': record.getMessage(),
            'module': record.module,
            'function': record.funcName,
            'line': record.lineno,
        }
        
        # 添加额外字段
        if hasattr(record, 'user_id'):
            log_record['user_id'] = record.user_id
        
        if hasattr(record, 'request_id'):
            log_record['request_id'] = record.request_id
        
        if record.exc_info:
            log_record['exception'] = self.formatException(record.exc_info)
        
        return json.dumps(log_record, ensure_ascii=False)

# 使用
logger = logging.getLogger('structured_app')
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)

# 记录带额外字段的日志
logger.info("用户操作", extra={'user_id': 123, 'request_id': 'req-456'})

3.4 日志上下文管理

使用上下文管理器来自动记录请求上下文:

python 复制代码
from contextlib import contextmanager
import uuid

@contextmanager
def request_context(request_id=None):
    """请求上下文管理器"""
    if request_id is None:
        request_id = str(uuid.uuid4())[:8]
    
    # 创建子 logger 并添加上下文
    logger = logging.getLogger('request')
    old_handlers = logger.handlers.copy()
    
    # 添加带上下文的 formatter
    class ContextFormatter(logging.Formatter):
        def format(self, record):
            record.request_id = request_id
            return super().format(record)
    
    # 使用
    logger.info(f"请求开始", extra={'request_id': request_id})
    try:
        yield request_id
        logger.info(f"请求完成", extra={'request_id': request_id})
    except Exception as e:
        logger.error(f"请求失败:{e}", extra={'request_id': request_id}, exc_info=True)
        raise

# 使用示例
with request_context() as req_id:
    print(f"处理请求 {req_id}")

四、生产环境最佳实践

4.1 日志配置管理

使用配置文件管理日志:

python 复制代码
# logging_config.py
LOGGING_CONFIG = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'default': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        },
        'detailed': {
            'format': '%(asctime)s | %(name)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'INFO',
            'formatter': 'default',
        },
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'level': 'DEBUG',
            'formatter': 'detailed',
            'filename': 'logs/app.log',
            'maxBytes': 10485760,
            'backupCount': 5,
        },
        'error_file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'level': 'ERROR',
            'formatter': 'detailed',
            'filename': 'logs/error.log',
            'maxBytes': 10485760,
            'backupCount': 5,
        },
    },
    'loggers': {
        '': {  # root logger
            'handlers': ['console', 'file', 'error_file'],
            'level': 'DEBUG',
            'propagate': True,
        },
        'uvicorn': {
            'handlers': ['console'],
            'level': 'INFO',
            'propagate': False,
        },
    },
}

# 在应用启动时加载
import logging.config
logging.config.dictConfig(LOGGING_CONFIG)

4.2 敏感信息脱敏

python 复制代码
import re

class SensitiveDataFilter(logging.Filter):
    """敏感信息过滤器"""
    
    PATTERNS = [
        (r'password['"]?\s*[:=]\s*["']?[^"\s]+', 'password=***'),
        (r'api_key['"]?\s*[:=]\s*["']?[^"\s]+', 'api_key=***'),
        (r'\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b', '****-****-****-****'),  # 信用卡号
        (r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Z|a-z]{2,}\b', '[EMAIL]'),  # 邮箱
    ]
    
    def filter(self, record):
        message = record.getMessage()
        for pattern, replacement in self.PATTERNS:
            message = re.sub(pattern, replacement, message, flags=re.IGNORECASE)
        record.msg = message
        return True

# 使用
logger = logging.getLogger('secure_app')
logger.addFilter(SensitiveDataFilter())
logger.info("用户登录,password=secret123, email=user@example.com")
# 输出:用户登录,password=***, email=[EMAIL]

五、总结

Python 日志系统是一个强大而灵活的工具。掌握以下要点:

  1. 选择合适的日志级别:DEBUG 用于开发,INFO 用于一般操作,WARNING/ERROR 用于问题
  2. 配置多个输出:控制台用于实时查看,文件用于持久化
  3. 使用结构化日志:便于日志分析和监控
  4. 敏感信息脱敏:保护用户隐私
  5. 日志轮转:避免日志文件过大

记住:好的日志系统能让你在问题发生时快速定位原因,是生产环境不可或缺的工具。

相关推荐
言乐642 分钟前
Python实现可运行解密游戏游戏框架
python·游戏·小程序·游戏程序·关卡设计
YUS云生1 小时前
Python学习笔记·第31天:FastAPI入门——路由、路径参数、查询参数与请求体
笔记·python·学习
智写-AI1 小时前
真实有效的免费降英文AI工具服务商
人工智能·python
yuhuofei20212 小时前
【Python入门】了解掌握Python中函数的基本使用
python
考虑考虑2 小时前
Sentinel安装
java·后端·微服务
IT_陈寒2 小时前
SpringBoot自动配置失灵?你可能忘了这个关键注解
前端·人工智能·后端
碎碎念_4922 小时前
SpringBoot + Vue 前后端分离从 0 到 1 完整环境配置流程
vue.js·spring boot·后端
逝水无殇3 小时前
C# 文件的输入与输出详解
开发语言·数据库·后端·c#
YIAN3 小时前
从手写 Agent 工具到 MCP 协议:一文搞懂大模型跨进程跨语言工具调用
后端·agent·mcp