1. 为什么日志与异常处理是Python项目的生命线
上周排查一个线上故障时,我盯着毫无线索的终端输出冷汗直流——服务突然崩溃却找不到任何有效日志。这个惨痛教训让我意识到:良好的日志系统和异常处理机制不是可选项,而是项目的基础设施。就像飞机上的黑匣子,平时看似无用,关键时刻却能救命。
Python作为动态语言,运行时问题往往比编译型语言更难追踪。我在金融、物联网和电商领域十多年的开发经验表明:约70%的线上问题通过完善的日志即可快速定位,剩下30%需要结合异常堆栈。但现实是,大多数开发者直到项目上线才意识到日志的重要性,就像我当年一样。
2. 日志系统设计四层架构
2.1 基础配置:比print专业100倍的logging模块
新手最爱用print调试,但生产环境会教你做人。Python内置的logging模块才是正道:
python复制import logging
# 基础配置(不推荐直接这样用)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
这行简单的配置背后藏着几个关键点:
__name__作为logger名称会自动继承模块路径(如package.module)- 日志级别从低到高:DEBUG < INFO < WARNING < ERROR < CRITICAL
- 生产环境通常设为INFO,开发环境用DEBUG
踩坑提醒:避免在模块级直接执行basicConfig,这会导致后续配置失效。应该在主程序入口统一配置。
2.2 进阶配置:多handler组合拳
真实项目需要更精细的控制。这是我常用的配置模板:
python复制def setup_logging():
logger = logging.getLogger()
logger.setLevel(logging.DEBUG) # 根logger设为最低级别
# 控制台输出
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(console_formatter)
# 文件输出
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG) # 文件记录更详细
file_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(pathname)s:%(lineno)d - %(message)s')
file_handler.setFormatter(file_formatter)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
这种配置实现了:
- 控制台只显示INFO及以上级别,简洁明了
- 文件记录DEBUG级别完整日志,包含文件名和行号
- 不同handler可以独立设置格式和级别
2.3 结构化日志:机器友好的新时代
当需要对接ELK等日志系统时,JSON格式更受欢迎。使用python-json-logger实现:
python复制from pythonjsonlogger import jsonlogger
json_handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
'%(asctime)s %(name)s %(levelname)s %(message)s %(pathname)s %(lineno)d')
json_handler.setFormatter(formatter)
logger.addHandler(json_handler)
输出示例:
json复制{
"asctime": "2023-08-20 14:23:45,678",
"name": "module.core",
"levelname": "ERROR",
"message": "Database connection failed",
"pathname": "/app/module/core.py",
"lineno": 42,
"extra_info": {"db_host": "192.168.1.1", "user": "admin"}
}
2.4 性能优化:避免日志成为瓶颈
在高并发场景下,不当的日志操作可能导致性能问题:
- 避免在热路径中频繁计算日志内容:
python复制# 错误示范(即使不记录也会执行字符串拼接)
logger.debug(f"Value: {expensive_function()}")
# 正确做法
if logger.isEnabledFor(logging.DEBUG):
logger.debug(f"Value: {expensive_function()}")
- 使用RotatingFileHandler防止日志文件过大:
python复制from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
'app.log', maxBytes=10*1024*1024, backupCount=5)
3. 异常处理的艺术
3.1 基础try-except的隐藏陷阱
大多数教程教的异常处理都是错的。看看这个典型错误示例:
python复制try:
do_something()
except Exception as e: # 捕获所有异常是大忌!
print(f"Error occurred: {e}")
问题在于:
- 吞掉了所有异常,包括KeyboardInterrupt等系统信号
- 没有记录异常堆栈
- 没有考虑异常恢复或重试
3.2 异常处理黄金法则
我的异常处理checklist:
- 只捕获你知道如何处理的特定异常
- 记录完整堆栈信息
- 考虑是否需要进行重试
- 必要时进行资源清理
- 向上层传递无法处理的异常
改进后的版本:
python复制import traceback
try:
db_operation()
except (DatabaseTimeout, ConnectionError) as e:
logger.error(
"Database operation failed",
exc_info=True, # 记录完整堆栈
extra={"operation": "update", "retry_count": 3})
retry_operation()
except ValueError as e:
logger.warning(f"Invalid input: {e}")
raise # 重新抛出给上层处理
finally:
cleanup_resources() # 确保资源释放
3.3 自定义异常的进阶技巧
当内置异常不够用时,创建有意义的自定义异常:
python复制class PaymentProcessingError(Exception):
"""支付处理相关异常基类"""
def __init__(self, message, payment_id, user_id):
super().__init__(message)
self.payment_id = payment_id
self.user_id = user_id
class InsufficientBalanceError(PaymentProcessingError):
"""余额不足异常"""
def __init__(self, payment_id, user_id, amount):
super().__init__(
f"User {user_id} has insufficient balance for payment {payment_id}",
payment_id, user_id)
self.amount = amount
使用场景:
python复制try:
process_payment()
except InsufficientBalanceError as e:
logger.error(
f"Payment failed: {e}",
extra={
"payment_id": e.payment_id,
"user_id": e.user_id,
"required_amount": e.amount
})
notify_user_balance_issue(e.user_id)
4. 实战:电商订单系统的日志与异常设计
4.1 场景分析
假设我们有一个订单处理流程:
- 接收订单请求
- 库存检查
- 支付处理
- 物流调度
- 订单完成
每个环节都可能出错,需要不同的处理策略。
4.2 代码实现
python复制class OrderProcessor:
def __init__(self):
self.logger = logging.getLogger("order_processor")
def process_order(self, order_id):
try:
self._validate_order(order_id)
self._check_inventory(order_id)
self._process_payment(order_id)
self._schedule_delivery(order_id)
self._complete_order(order_id)
except InvalidOrderError as e:
self.logger.warning(
f"Invalid order: {order_id}",
exc_info=True,
extra={"order": order_id, "reason": str(e)})
raise
except InsufficientInventoryError as e:
self.logger.error(
"Inventory shortage",
exc_info=True,
extra={
"order": order_id,
"missing_items": e.missing_items
})
self._notify_inventory_team(e.missing_items)
raise
except PaymentProcessingError as e:
self.logger.critical(
"Payment failure",
exc_info=True,
extra={
"order": order_id,
"user": e.user_id,
"payment": e.payment_id,
"amount": e.amount
})
self._trigger_payment_retry(order_id)
raise
except DeliveryScheduleError:
self.logger.exception( # 自动记录完整堆栈
f"Delivery scheduling failed for order {order_id}")
self._revert_order(order_id)
raise
except Exception as e:
self.logger.critical(
"Unexpected order processing error",
exc_info=True,
extra={"order": order_id})
raise OrderProcessingError(
f"Unexpected error processing order {order_id}") from e
def _validate_order(self, order_id):
self.logger.debug(f"Validating order {order_id}")
# 验证逻辑...
if invalid:
raise InvalidOrderError("Invalid order details")
# 其他方法类似...
4.3 关键设计点
-
分层日志:
- DEBUG:详细流程跟踪
- INFO:关键业务节点
- WARNING:可恢复的问题
- ERROR:需要干预的问题
- CRITICAL:系统级故障
-
异常分类处理:
- 输入验证问题:记录后直接拒绝
- 库存不足:通知补货团队
- 支付失败:触发重试机制
- 物流问题:回滚订单
-
上下文信息:
每个异常和日志都携带业务ID等关键上下文,便于追踪
5. 高级技巧与性能优化
5.1 日志采样应对高流量
当QPS很高时,可以采样记录DEBUG日志:
python复制import random
class SamplingFilter(logging.Filter):
def __init__(self, sample_rate=0.1):
self.sample_rate = sample_rate
def filter(self, record):
if record.levelno > logging.DEBUG:
return True
return random.random() < self.sample_rate
logger.addFilter(SamplingFilter())
5.2 异步日志提升性能
使用concurrent.futures实现异步日志:
python复制from concurrent.futures import ThreadPoolExecutor
class AsyncLogHandler(logging.Handler):
def __init__(self, handler):
super().__init__()
self._handler = handler
self._executor = ThreadPoolExecutor(max_workers=1)
def emit(self, record):
self._executor.submit(self._handler.emit, record)
# 包装原有handler
async_handler = AsyncLogHandler(file_handler)
logger.addHandler(async_handler)
5.3 分布式追踪集成
在微服务架构中,需要关联跨服务日志:
python复制from opentelemetry import trace
def process_request(request):
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("order_processing"):
trace_id = trace.get_current_span().get_span_context().trace_id
logger.info(
"Started order processing",
extra={"trace_id": trace_id})
# 处理逻辑...
6. 常见问题排查指南
6.1 日志突然不工作了?
检查清单:
- 是否有多个basicConfig调用(后者会覆盖前者)
- 检查logger的effective level(
logger.getEffectiveLevel()) - 确认handler的level没有过滤掉日志
- 检查文件权限(特别是Docker容器中)
6.2 异常堆栈信息不全?
确保:
- 使用
logger.exception()或exc_info=True - 没有在except块中意外覆盖了
__traceback__ - 没有使用
sys.exc_info()不当
6.3 日志文件占用过大?
解决方案:
- 使用RotatingFileHandler
- 添加TimedRotatingFileHandler按时间切割
- 实现日志清理脚本:
python复制from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
'app.log', maxBytes=10*1024*1024, backupCount=5)
7. 我的日志实践工具箱
经过多年实践,这些工具组合效果最佳:
-
开发环境:
- rich:彩色控制台输出
- loguru:更友好的API
-
生产环境:
- python-json-logger:结构化日志
- sentry-sdk:异常监控
- OpenTelemetry:分布式追踪
-
性能关键场景:
- cysimdjson:快速JSON解析
- concurrent-log-handler:线程安全日志
配置示例:
python复制# production.py
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
"format": "%(asctime)s %(name)s %(levelname)s %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "json",
"level": "INFO"
},
"file": {
"class": "concurrent_log_handler.ConcurrentRotatingFileHandler",
"filename": "/var/log/app.log",
"maxBytes": 10000000,
"backupCount": 5,
"formatter": "json",
"level": "DEBUG"
}
},
"root": {
"handlers": ["console", "file"],
"level": "DEBUG"
}
}
8. 从日志到监控的进阶之路
当项目规模扩大后,需要考虑:
-
日志聚合:
- ELK Stack (Elasticsearch + Logstash + Kibana)
- Loki + Grafana
-
指标监控:
- Prometheus:收集指标
- Grafana:可视化
-
APM工具:
- Sentry:错误跟踪
- Datadog:全栈观测
集成示例:
python复制# sentry集成
import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration
sentry_logging = LoggingIntegration(
level=logging.INFO,
event_level=logging.ERROR
)
sentry_sdk.init(
dsn="your-dsn",
integrations=[sentry_logging],
traces_sample_rate=1.0
)
# prometheus集成
from prometheus_client import start_http_server, Counter
LOG_ERRORS = Counter(
'log_errors_total',
'Total error logs',
['logger', 'level']
)
class PrometheusLogFilter(logging.Filter):
def filter(self, record):
if record.levelno >= logging.ERROR:
LOG_ERRORS.labels(
logger=record.name,
level=record.levelname
).inc()
return True
logger.addFilter(PrometheusLogFilter())
start_http_server(8000)
9. 值得借鉴的开源项目实践
学习优秀项目的实现:
-
Requests:
- 精细的日志分级(连接、请求、响应)
- 可插拔的日志适配器
-
Django:
- 数据库查询日志
- 请求/响应日志中间件
-
Scrapy:
- 组件化日志系统
- 统计信息自动记录
以Requests为例的日志配置:
python复制import logging
from http.client import HTTPConnection
# 启用debug日志
HTTPConnection.debuglevel = 1
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
10. 我踩过的坑与最佳实践
-
时区问题:
- 总是使用UTC记录时间戳
- 格式中明确时区:
%(asctime)s %(tz)s
-
敏感信息泄露:
- 实现过滤器移除密码、token等
python复制class SensitiveDataFilter(logging.Filter): def filter(self, record): if hasattr(record, 'password'): record.password = '***' return True -
多进程日志:
- 使用ConcurrentLogHandler
- 或者每个进程单独日志文件
-
日志性能瓶颈:
- 避免同步写远程存储
- 使用队列异步处理
python复制from logging.handlers import QueueHandler, QueueListener log_queue = Queue() queue_handler = QueueHandler(log_queue) logger.addHandler(queue_handler) file_handler = FileHandler('app.log') listener = QueueListener(log_queue, file_handler) listener.start() -
文化建议:
- 在团队中建立日志规范
- Code Review时检查日志质量
- 定期分析日志模式优化配置