日志、监控与错误处理体系
构建生产级应用的可观测性体系,让问题能被快速发现和定位
- 掌握 Python logging 模块的正确用法
- 学会结构化日志和日志聚合
- 理解重试、熔断、降级模式
- 掌握错误处理的最佳实践
为什么 print 不够用?
开发时 print 没问题,但生产环境你需要:日志级别(调试/信息/警告/错误)、输出到文件+控制台、自动轮转、结构化格式、按模块控制级别。Python 内置的 logging 模块就是干这个的。
结构化日志
生产环境的日志通常会被收集到 ELK/Datadog/ Loki 等系统里搜索分析。纯文本日志不好查,结构化日志(JSON)能按字段过滤。
# 用 python-json-logger 输出 JSON
from pythonjsonlogger import jsonlogger
handler = logging.StreamHandler()
handler.setFormatter(jsonlogger.JsonFormatter(
"%(asctime)s %(levelname)s %(name)s %(message)s"
))
logging.getLogger().addHandler(handler)
# 输出:{"asctime": "...", "levelname": "ERROR", "name": "payment", ...}
# 更简单:用 structlog 或 loguru
# loguru 示例:
from loguru import logger
logger.add("app.json", serialize=True) # 自动 JSON
logger.add("app.log", rotation="10 MB", retention="30 days")
logger.bind(user_id=123, order_id=456, amount=99.9).error("支付失败")不要在日志中记录密码、Token、身份证号、信用卡号等敏感信息。日志文件的访问权限要控制。GDPR/个人信息保护法要求用户数据可以被删除,但日志中的数据很难清理——所以一开始就不要记。
错误处理体系
# 1. 自定义异常层次
class AppError(Exception):
"""应用异常基类"""
code = "APP_ERROR"
class PaymentError(AppError):
code = "PAYMENT_ERROR"
class InsufficientBalanceError(PaymentError):
code = "INSUFFICIENT_BALANCE"
# 2. 精确捕获,不要裸 except
try:
process_payment(order)
except InsufficientBalanceError:
return {"error": "余额不足"}, 400
except PaymentError as e:
logger.error(f"支付失败: {e}")
return {"error": "支付服务暂时不可用"}, 502
except Exception:
logger.exception("未预期的错误") # 记录完整堆栈
raise # 重新抛出,不要吞掉
# 3. 永远不要这样写:
try:
risky_operation()
except: # 裸 except 会捕获 KeyboardInterrupt 和 SystemExit!
pass # 吞掉所有错误,问题被隐藏,调试噩梦
# 4. finally 确保资源释放
try:
f = open("data.txt")
process(f)
finally:
f.close() # 即使出错也会关闭
# 更好:用上下文管理器
with open("data.txt") as f:
process(f) # 自动关闭重试、熔断与降级
# 重试:网络请求可能因临时故障失败,重试可以解决
import tenacity
@tenacity.retry(
stop=tenacity.stop_after_attempt(3), # 最多重试3次
wait=tenacity.wait_exponential(min=1, max=10), # 指数退避1s,2s,4s
retry=tenacity.retry_if_exception_type(NetworkError),
)
def call_external_api():
return requests.get("https://api.example.com/data", timeout=5)
# 熔断:当依赖服务持续失败时,快速失败而不是一直等待
# 用 circuitbreaker 库或手动实现
class CircuitBreaker:
def __init__(self, threshold=5, reset_timeout=30):
self.failures = 0
self.threshold = threshold
self.reset_timeout = reset_timeout
self.opened = False
def call(self, func, *args):
if self.opened:
raise Exception("熔断器开启,快速失败")
try:
result = func(*args)
self.failures = 0 # 成功则重置
return result
except Exception:
self.failures += 1
if self.failures >= self.threshold:
self.opened = True
raise
# 降级:主服务不可用时提供备用方案
def get_user_recommendations(user_id):
try:
return ai_service.get_recommendations(user_id)
except Exception:
logger.warning("AI推荐不可用,降级为热门推荐")
return get_popular_items() # 降级方案生产系统的可观测性包括:Logs(日志)——离散事件记录;Metrics(指标)——聚合数值(QPS、延迟P99、错误率);Traces(链路追踪)——一个请求经过的所有服务。三者结合才能快速定位分布式系统的问题。
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
从输入网址到看到结果,大致是:DNS 解析拿到 IP → TCP 三次握手 →(HTTPS 还要 TLS 协商)→ 发出请求 → 服务端经过中间件、路由、业务逻辑、查库 → 返回响应 → 连接复用或四次挥手。理解状态码、幂等方法(GET/PUT/DELETE)与非幂等(POST),是做 Web 和联调的基本功。
挑战任务
实现重试机制
写个带指数退避的重试装饰器,最多重试3次,只对网络异常重试。
课后作业
构建日志体系
给你的项目配完整日志体系:控制台彩色输出、文件轮转、JSON格式、按模块级别控制,登录、支付、错误这些关键操作都加日志。