深入理解 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 日志系统是一个强大而灵活的工具。掌握以下要点:
- 选择合适的日志级别:DEBUG 用于开发,INFO 用于一般操作,WARNING/ERROR 用于问题
- 配置多个输出:控制台用于实时查看,文件用于持久化
- 使用结构化日志:便于日志分析和监控
- 敏感信息脱敏:保护用户隐私
- 日志轮转:避免日志文件过大
记住:好的日志系统能让你在问题发生时快速定位原因,是生产环境不可或缺的工具。