3. 日志基础:日志级别与日志格式规范
日志是可观测性的三大支柱之一,也是故障排查、安全审计和业务分析的原始数据来源。在微服务与多租户架构中,日志的级别设计和格式规范直接决定了可观测性系统的有效性与运维效率。本节将深入讲解日志级别的设计原则,并给出标准化的结构化日志格式规范。
3.1 日志级别设计原则
日志级别用于标识日志事件的严重程度与紧急程度。标准日志库(如 SLF4J、Log4j、Logback、Zap)通常提供五个级别:DEBUG、INFO、WARN、ERROR、FATAL。每个级别都有明确的语义和使用场景。
| 级别 | 语义 | 典型使用场景 | 生产环境建议 |
|---|---|---|---|
| DEBUG | 调试信息,用于开发阶段 | 变量值、方法入口/出口、SQL 参数、循环状态 | 关闭(仅开发/测试环境开启) |
| INFO | 正常业务流程的关键节点 | 服务启动/停止、请求开始/结束、租户创建、配置加载 | 开启(默认级别) |
| WARN | 潜在问题或非预期状态,但不影响当前功能 | 配置降级、重试次数超过阈值、资源使用率接近上限、API 弃用 | 开启(需关注) |
| ERROR | 功能异常或业务失败,需要人工介入 | 数据库连接失败、第三方服务超时、数据校验不通过、业务异常 | 开启(需告警) |
| FATAL | 严重错误,服务可能无法继续运行 | 内存溢出、配置错误导致启动失败、关键依赖完全不可用 | 开启(需立即告警) |
设计原则:
- 按环境动态调整:开发环境使用 DEBUG,测试环境使用 INFO,生产环境至少使用 INFO,核心链路可保留 DEBUG 但需限流。
- 避免过度日志:不要在循环体内打印 DEBUG 日志,防止日志风暴导致磁盘 I/O 打满。
- ERROR 不等于异常:业务异常(如用户输入错误)应使用 WARN,系统异常(如数据库不可用)才使用 ERROR。
- FATAL 应极少出现:FATAL 日志通常伴随进程退出,应配合健康检查与自动恢复机制。
3.2 日志级别与租户隔离的关联
在多租户系统中,日志级别需要与租户上下文联动。不同租户可能对日志敏感度不同,且同一租户在不同环境下的日志级别也可能不同。建议采用租户级别日志级别覆盖策略:
- 全局默认级别:INFO(生产环境)
- 租户级别覆盖:通过配置中心为特定租户(如 VIP 租户、测试租户)动态调整日志级别
- 请求级别覆盖:通过请求头(如
X-Log-Level=DEBUG)临时开启调试日志,仅对当前请求生效
最佳实践:在日志框架中集成 MDC(Mapped Diagnostic Context),将租户 ID 和请求 ID 自动注入日志上下文,实现日志级别与租户的自动关联。
3.3 日志格式规范
结构化日志是构建可观测性体系的基础。非结构化的纯文本日志难以被日志平台(如 ELK、Loki、Splunk)解析和检索。标准的结构化日志格式应包含以下核心字段:
| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
| timestamp | ISO 8601 | 事件发生时间,包含时区 | 2025-03-21T14:30:00.123+08:00 |
| level | string | 日志级别 | INFO |
| tenant_id | string | 租户唯一标识 | tenant-abc-123 |
| request_id | string | 请求链路唯一标识 | req-8a9b7c6d |
| service_name | string | 服务名称 | order-service |
| message | string | 日志正文 | 订单创建成功 |
| caller | string | 代码调用位置(文件:行号) | order_handler.go:142 |
| duration_ms | int | 请求耗时(可选) | 235 |
| extra | object | 扩展字段(业务相关) | {"order_id": "ORD-001", "amount": 99.9} |
常见错误:
- 使用本地时间而非 UTC 或 ISO 8601 带时区格式,导致跨时区排查困难。
- 遗漏
tenant_id或request_id,导致无法按租户或请求链路过滤日志。 - 将敏感信息(如密码、身份证号)直接写入日志,违反安全合规要求。
3.4 结构化日志示例(JSON 格式)
推荐使用 JSON 作为结构化日志的序列化格式,因为它具有自描述、易解析、支持嵌套等优点。以下是一个符合规范的结构化日志示例:
{
"timestamp": "2025-03-21T14:30:00.123+08:00",
"level": "INFO",
"tenant_id": "tenant-abc-123",
"request_id": "req-8a9b7c6d",
"service_name": "order-service",
"caller": "order_handler.go:142",
"message": "订单创建成功",
"duration_ms": 235,
"extra": {
"order_id": "ORD-001",
"amount": 99.9,
"payment_method": "credit_card"
}
}
3.5 日志格式规范与租户隔离的集成
在日志格式中强制包含 tenant_id 字段,是实现租户隔离的关键。日志平台(如 Elasticsearch)可以基于 tenant_id 字段建立索引或过滤器,确保不同租户的日志数据在存储和查询层面完全隔离。
下图展示了从日志产生到租户隔离存储的完整流程:
3.6 日志格式规范检查清单
在团队落地日志规范时,建议使用以下检查清单进行代码审查:
- ✅ 时间戳格式:是否使用 ISO 8601 带时区格式?
- ✅ 租户 ID:是否所有日志都包含
tenant_id? - ✅ 请求 ID:是否所有请求链路都传递了
request_id? - ✅ 服务名:是否使用统一的服务注册名称?
- ✅ 日志级别:是否按语义正确使用 DEBUG/INFO/WARN/ERROR/FATAL?
- ✅ 敏感信息:是否过滤了密码、Token、身份证等敏感字段?
- ✅ 结构化格式:是否统一使用 JSON 格式?
- ✅ 调用位置:是否包含文件与行号信息?
总结:日志级别设计要遵循“按环境调整、按语义分级”的原则;日志格式规范要强制包含
timestamp、tenant_id、request_id、service_name 等核心字段,并采用 JSON 结构化输出。只有规范化的日志,才能支撑起多租户环境下的可观测性体系。