日志体系:类型、Schema 与安全基线
日志是可观测性的事件记录,不是调试输出的堆放处。本文定义日志类型、级别、统一字段与安全边界,并给出从应用到平台的职责划分。
目录
| 章节 | 说明 |
|---|---|
| 日志类型与保留目标 | 区分运行、业务与审计记录 |
| 级别与事件命名 | 选择恰当的严重程度和稳定事件名 |
| 统一日志 Schema | 字段、命名和基数约束 |
| Java 编码基线(阿里规约) | 参数化日志、异常与性能约束 |
| 应用与平台的职责 | stdout、采集、存储与查询的边界 |
| 安全、隐私与完整性 | 禁止记录、脱敏与抗注入要求 |
日志类型与保留目标
| 类型 | 记录对象 | 主要消费者 | 可靠性与存储目标 |
|---|---|---|---|
| 运行日志 | 请求、依赖调用、异常、性能事件 | 开发、SRE | 可接受经评估的短暂丢失;进入日志平台 |
| 业务事件 | 下单、支付、退款等领域事实 | 产品、运营、客服 | 按消费场景决定:分析事件可采样,关键事件需可重放 |
| 审计记录 | 谁在何时对什么资源做了何种操作及结果 | 安全、合规、审计 | append-only、权限隔离、可验证完整性;保留期由制度与法规决定 |
“审计日志绝对不可丢”是目标,不是能力描述。要达到该目标,需要持久化确认、重试/Outbox、备份和访问审计共同保证。
级别与事件命名
SLF4J/Logback 常用级别从低到高为:TRACE → DEBUG → INFO → WARN → ERROR。FATAL 不是 SLF4J/Logback 的标准级别;若底层框架支持,也应先明确它与 ERROR 的告警语义差异。
| 级别 | 含义 | 生产使用原则 |
|---|---|---|
TRACE / DEBUG | 诊断细节 | 默认关闭;不得依赖它承载关键事实 |
INFO | 预期内的关键状态变化 | 输出低频、可行动的领域或生命周期事件 |
WARN | 可恢复、需关注但未造成服务失败的异常 | 避免把可预期用户输入当成告警 |
ERROR | 请求/任务失败或需要人工处理的异常 | 必须带上下文与异常对象;是否告警由规则决定,不由级别单独决定 |
事件名应稳定且低基数,例如 order.placed、payment.failed;订单号、用户 ID 放字段中,不能拼入事件名。
统一日志 Schema
推荐应用使用 JSON 输出,并以 OpenTelemetry 语义约定为基础。团队可选用 ECS 或 OTel 作为外部 Schema,但必须在所有服务中只维护一份映射表。
| 逻辑含义 | 推荐字段 | 约束 |
|---|---|---|
| 事件时间 | @timestamp | ISO 8601,UTC 或带偏移量 |
| 严重程度与正文 | log.level、message | 级别大写;正文供人阅读 |
| 服务身份 | service.name、service.version、deployment.environment.name | 静态或低基数字段 |
| 链路关联 | trace_id、span_id、request_id | trace_id 为 32 位十六进制,span_id 为 16 位;request_id 不等于 trace ID |
| 业务关联 | biz.type、biz.id | 仅记录排障需要的标识,注意权限与保留期 |
| 异常 | error.type、error.message、error.stack_trace | message 与堆栈都须脱敏 |
示例:
{
"@timestamp": "2026-08-10T10:23:45.123+08:00",
"log.level": "INFO",
"service.name": "order-service",
"deployment.environment.name": "production",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"event.name": "order.placed",
"biz.type": "order",
"biz.id": "98765",
"message": "Order placed successfully"
}
Elasticsearch 可索引结构化字段;Loki 主要索引 label。不要把
user_id、trace_id、订单号等高基数字段升级为 Loki label,应在日志正文或解析字段中查询。
Java 编码基线(阿里规约)
将《阿里巴巴 Java 开发手册》的日志思想落实为可检查的团队规则:
| 规则 | 合格写法 | 原因 |
|---|---|---|
| 使用统一日志门面 | private static final Logger log = LoggerFactory.getLogger(...); | 便于替换实现和统一治理 |
| 参数化而非字符串拼接 | log.info("orderId={} status={}", id, status); | 未命中级别时避免无意义的字符串构造 |
| 正确输出异常 | log.error("payment failed, orderId={}", id, ex); | 保留完整堆栈和业务上下文 |
| 避免循环高频 INFO | 汇总、采样或降为 DEBUG | 降低 I/O、存储和检索噪音 |
不用 System.out / printStackTrace | 统一经 SLF4J 输出 | 保证级别、格式、采集和关联字段一致 |
复杂参数在日志级别未开启时应显式保护:
if (log.isDebugEnabled()) {
log.debug("rule evaluation detail={}", expensiveToRender(ruleResult));
}
这不是要求“少打日志”,而是要求每条日志可被定位、检索和行动。规则应通过代码扫描、日志 schema 测试和 code review 固化,不能仅依赖约定。
应用与平台的职责
flowchart LR
A["应用<br/>结构化事件"] --> B["stdout / 文件"]
B --> C["Agent / Collector<br/>解析、缓冲、脱敏"]
C --> D["日志存储"]
D --> E["查询、关联、告警"]
应用负责:事件语义、级别、Schema 和安全字段。运行平台负责:输出路由、采集重试/背压、保留/归档、访问控制和告警。容器环境通常写 stdout;传统主机可写滚动文件,但两者都不应由业务代码管理采集和归档策略。
安全、隐私与完整性
禁止记录:密码、PIN、会话令牌、JWT、API Key、私钥、完整支付卡数据、CVV、未经授权的 PII 和用户 opt-out 后的追踪数据。
记录前检查:
- 白名单选择业务字段;不要“先全量序列化、再尝试过滤”。
- 手机号等 PII 使用掩码或不可逆关联标识;快照字段加密、限长、分权访问。
- 将外部输入写入日志前处理换行符与控制字符,防止伪造日志行。
- 审计记录与普通运行日志分库/分权限;记录谁读取或导出了审计数据。
参考资料
- OpenTelemetry Trace API
- OWASP Logging Cheat Sheet
- 阿里巴巴 Java 开发手册(P3C)
- The 12-Factor App — XI. Logs
- 业务日志设计(领域与审计事件的落地)
- 分布式链路追踪与日志关联(日志关联字段的产生与传播)
评论 (0)