日志系统

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

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

相关推荐
linyanRPA4 分钟前
影刀RPA+Python店群自动化实战:自研环境隔离引擎,200店铺并发不卡不串号
python·自动化·rpa
土狗TuGou29 分钟前
SQL内功笔记 · 第8篇:事务的四大特性与隔离级别
数据库·笔记·后端·sql·mysql·oracle
ZengLiangYi39 分钟前
React Query + REST API 最佳实践
javascript·后端·react.js
星浩AI44 分钟前
项目实战:合同智能审批 · LangGraph + HITL 人机协同方案 [有源码]
后端·langchain·agent
JavaGuide1 小时前
Codex 接入第三方模型 DeepSeek、GLM、Kimi 教程:CC-Switch 和 Codex++ 两种方案对比
后端·ai编程
ZengLiangYi1 小时前
Fastify 加 Electron:把 Web 服务嵌进桌面应用
前端·javascript·后端
李白你好1 小时前
页面资产梳理 · 技术指纹识别 · Spring 端点探测
java·后端·spring
用户1753721240331 小时前
02《面向对象设计原则:SOLID原则实战解析》
后端
我是一颗柠檬1 小时前
【Java后端技术亮点】热Key探测与本地缓存二级防护:Redis热点问题的终极解决方案
java·redis·后端·缓存·中间件