日志系统

深入理解 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. 日志轮转:避免日志文件过大

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

相关推荐
alwaysrun1 小时前
Rust之异步框架Tokio
后端·编程语言
CodeSheep1 小时前
中国编程第一人,一人抵一城!
前端·后端·程序员
2401_846339561 小时前
html标签如何表示计量值_meter标签使用条件【方法】
jvm·数据库·python
Randyliu1 小时前
20260511-Pydantic和SQLalchemy
后端·python
yexuhgu1 小时前
如何为禁用按钮点击添加提示文案
jvm·数据库·python
水木流年追梦1 小时前
大模型入门-应用篇1-prompt技术
开发语言·python·算法·prompt
smallYoung1 小时前
【学习笔记】中间件-RabbitMQ
后端
莫生灬灬1 小时前
ElementUI封装 共91个组件 支持易语言/火山/C#/Python
开发语言·c++·python·ui·elementui·c#
三千星1 小时前
Java开发者转型AI工程化Week 3:从LangChain4j到AI Agent
后端·langchain