先说为什么:把模糊的错变成可追踪的事件

如果把系统比作一家快餐店,异常就是顾客反映的“菜有问题”。没有记录,你只能靠回忆;有了规范记录,你能知道是谁点的、哪个厨师做的、什么时候出的问题、菜谱里哪一步出错。日志就是那张详尽的订单和监控台,异常捕获是前台接待,把信息标准化并送到后台处理。
总体设计原则(简单到复杂按步走)
- 统一入口:应用层和框架层应有统一的异常拦截和处理点,避免异常“漏网”。
- 结构化日志:尽量用JSON或键值对格式,便于搜索和聚合。
- 上下文丰富:每条日志应带请求ID、用户ID(脱敏)、服务名、环境、耗时等字段。
- 异步写入:写日志时尽量不阻塞主流程,使用缓冲和批量发送。
- 分级与采样:ERROR/ WARN/ INFO 分级,低频业务可以采样,避免日志爆炸。
- 集中化和可视化:集中式日志平台(ELK、Loki、Sentry等)用于检索、告警和报表。
- 安全与合规:敏感信息脱敏、访问控制、日志保留策略要符合法规要求。
从请求到日志:推荐的数据流与拦截点
把系统拆成几个节点来考虑:边缘(API 网关/负载均衡)、业务服务、后台任务、第三方调用。每个节点都需要:
- 请求进入时生成或传递 Request-ID(唯一追踪ID)。
- 异常发生时走统一异常处理流程,输出结构化错误日志,并上报到集中平台。
- 关键路径记录耗时与资源指标(CPU、内存、DB耗时)。
示例拦截顺序
- 网关层:验证、限流、添加 Request-ID、基础审计日志。
- 服务层:入口中间件捕获异常,记录上下文,转成标准错误响应。
- 框架/库层:底层错误向上抛并尽量保留原始堆栈信息。
- 后台任务:独立的捕获与重试策略,失败持久化到错误队列。
日志格式与字段建议
下面是一个实用的日志字段表,既能满足排查也便于聚合统计。
| 字段 | 说明 |
| timestamp | UTC 时间戳,统一时区便于比对 |
| service | 服务名(例如:api-gateway、user-service) |
| env | 运行环境(prod/staging/dev) |
| level | 日志级别(ERROR/WARN/INFO/DEBUG) |
| request_id | 全链路唯一ID |
| user_id | 用户ID(必要时脱敏) |
| error_code | 业务或通用错误码,便于聚合统计 |
| message | 简短描述 |
| stack | 堆栈信息(只在ERROR或调试时完整记录) |
| duration_ms | 耗时(毫秒),用于性能分析 |
示例日志行(JSON)
{
"timestamp": "2026-06-30T09:12:34.123Z",
"service": "order-service",
"env": "prod",
"level": "ERROR",
"request_id": "req-20260630-abc123",
"user_id": "u-9876",
"error_code": "ORDER_CREATE_DB_FAIL",
"message": "插入订单到数据库失败",
"stack": "java.sql.SQLException: ...",
"duration_ms": 1234
}
如何实现:不同语言/框架的关键点
这里用三个常见栈举例,说明要点(不是完整代码,但能直接落地):
Java(Spring Boot)
- 使用@ControllerAdvice 或 Filter 在最外层捕获异常,统一格式化返回。
- 用 MDC(Mapped Diagnostic Context)传递 request_id、user_id,在日志框架(Logback/Log4j2)里输出。
- 异步写入可用 Logstash appender 或 Kafka appender,把日志交给集中系统。
Node.js(Express/Koa)
- 应用级中间件捕获同步/异步异常(Promise rejection、async/await 错误)。
- 使用 pino/winston 等结构化日志库,支持异步批量发送。
- 在跨服务调用时,把 request-id 传入 header,日志记录中包含该 ID。
Python(Flask/FastAPI)
- 中间件或异常处理器捕获异常并记录,使用 structlog 或 logging 配合 JSON 格式化器。
- 异步框架中(如FastAPI+uvicorn),确保日志写入不会阻塞事件循环,使用队列或后台任务。
集中式平台与告警策略
集中平台负责存储、索引、检索与告警。常见组合:
- 日志搜索与分析:ELK(Elasticsearch + Logstash + Kibana)或 EFK(Fluentd/Fluentbit + Elasticsearch + Kibana),Loki + Grafana 更适合按流式索引。
- 异常跟踪:Sentry/Jaeger 用于错误聚合与分布式追踪。
- 告警:基于错误速率、噪声控制规则配置分级告警(P0/P1/P2),先用短期告警阈值触发,再结合抑制与恢复策略。
告警设置思路
- 单条 ERROR 不一定告警,关注错误增长速率或对用户影响的错误码。
- 为关键路径(登录、下单、支付)设专门指标与更严格阈值。
- 结合 SLA 与业务峰值设置动态阈值,避免节假日误报。
性能与可靠性权衡
日志系统不能为了追踪每个细节而拖垮业务。关键策略:
- 批量与异步:采集端做缓冲和批量发送,网络或后端不可用时降级到本地文件并限速。
- 采样:对高流量的INFO级日志做采样,对ERROR保留100%。
- 回压与熔断:如果日志后端不可用,限速并抛弃低价值日志,记录后端故障告警。
隐私与合规(不要拿敏感数据冒险)
日志里常见的用户信息、身份证号、银行卡等必须脱敏或掩码,只保留用于排查的最小信息。敏感数据的访问要有严格的权限审计,日志导出需审批流程。
测试与演练(把流程练成肌肉记忆)
- 单元测试:断言异常处理器返回预期格式,MDC 字段正确设置。
- 集成测试:模拟 DB、缓存、第三方失败,验证日志与告警触发。
- 故障演练(Chaos):定期做部分流量故障,检查日志链路与告警响应。
常见误区与实践小贴士
- 误区:把所有日志都设置为 DEBUG;结果是日志量爆炸且关键信息被淹没。
- 实践:对每个服务维护一份“日志契约”,说明哪些字段必须包含、哪些可选,以及错误码字典。
- 误区:只记错误码不记录上下文,导致无法回放问题。
- 实践:在关键业务操作处记录“事件日志”(业务语义强),便于业务分析与审计。
示例:快连快可落地的路线(阶段化推进)
- 阶段一(1-2 周):统一 Request-ID、引入 MDC/上下文、实现应用层统一异常捕获,输出结构化日志。
- 阶段二(2-4 周):接入集中式日志后端(先用轻量方案),配置基本索引与错误率告警。
- 阶段三(1-3 月):引入分布式追踪、错误聚合平台(Sentry/Jaeger),完善采样与回压策略,做隐私合规检查。
- 长期:定期复盘日志契约、告警有效性和存储成本,做容量规划与归档策略。
把事情做得不完美但可持续
最后提醒一句,日志与异常捕获不是一次性工程,而是持续演进的能力。先把最关键的“看到问题”做牢,再逐步丰富“为什么会发生”和“如何快速修复”的能力。总会有些小细节没想全,这很正常——重要的是保持可观测性、可追溯性和可操作性,让团队在真实问题面前不慌。
