本文目录导读:

- 核心原则
- 日志格式:结构化是关键
- 日志级别:严格定义,一致使用
- 日志内容:具体、可操作、无歧义
- 性能最佳实践
- 实现示例(以 Java + SLF4J + Logback 为例)
- 一张清单检查你的日志规范
这是一个非常核心且重要的问题,不规范的日志输出会导致问题排查困难、监控告警失效、甚至性能下降,规范开源日志的输出,核心目标是可读性、可搜索性、可关联性和可操作性。
以下是一套经过业界实践验证的开源日志输出规范指南,分为原则、格式、级别、内容、性能五个维度。
核心原则
- 机器可读,人也可读:优先让结构化解析器(如 ELK、Splunk)能轻松处理,同时人类工程师也能快速理解。
- 每行日志一个完整事件:避免跨行日志,如果必须跨行,使用特定标识符(如
trace_id)关联。 - 不记录敏感信息:密码、密钥、身份证号、电话号码等绝对禁止输出到日志。
- 只记录有意义的状态变化:不要为了记录而记录,区分“调试信息”和“业务事件”。
日志格式:结构化是关键
强烈推荐使用结构化日志,如 JSON,它天然支持键值对,解析成本最低。
通用JSON日志 Schema 示例:
{
"timestamp": "2024-05-20T14:30:15.123+08:00",
"level": "INFO",
"logger": "com.example.payment.service",
"thread": "pool-1-thread-5",
"trace_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"span_id": "client-9876",
"message": "用户支付请求处理成功",
"context": {
"user_id": "user_9527",
"order_id": "order_20240520_001",
"amount": 199.99,
"payment_method": "wechat"
},
"duration_ms": 1234,
"exception": null
}
字段说明:
| 字段 | 必选 | 说明 |
|---|---|---|
timestamp |
是 | 精确到毫秒或微秒,包含时区,推荐 ISO 8601 格式。 |
level |
是 | 日志级别。 |
logger |
推荐 | 产生日志的类名或模块名,便于定位代码。 |
thread |
推荐 | 生产日志的线程名 (Java) 或 Goroutine ID (Go),便于排查并发问题。 |
trace_id |
强烈推荐 | 全链路追踪ID,贯穿整个请求的生命周期,关联微服务各节点日志。 |
span_id |
推荐 | 当前步骤的ID,通常配合 trace_id 使用。 |
message |
是 | 简洁、清晰的业务描述,使用英文(通用)或中文(团队习惯),固定模板。 |
context |
推荐 | 业务上下文,包含排查问题所需的关键业务字段(用户ID、订单ID等)。 |
duration_ms |
推荐 | 操作耗时,用于性能监控。 |
exception |
有则必填 | 异常时的堆栈信息,作为单独字段存储,不要拼到 message 里。 |
日志级别:严格定义,一致使用
级别定义必须清晰,团队达成共识。滥用级别是日志混乱的最大原因。
| 级别 | 使用场景 | 举例 | 建议 |
|---|---|---|---|
| ERROR | 影响系统正常功能、需要立即人工介入的故障。 | 数据库连接失败、关键业务逻辑异常、第三方API返回500。 | 必须包含堆栈,触发告警。 |
| WARN | 预期内的异常、不影响主流程但值得关注。 | 请求参数不合法(返回用户友好的错误)、重试成功、配置降级、磁盘使用率超过80%。 | 可以不触发告警,但需要记录上下文。 |
| INFO | 关键业务状态变更。 | 用户注册成功、订单状态流转(创建、支付、发货)、定时任务开始/结束。 | 不要滥用,不要记录循环内的每一条记录。 |
| DEBUG | 开发和测试阶段排查问题。 | 函数入口/出口参数、SQL语句、中间结果。 | 生产环境默认关闭,通过动态开关按需开启。 |
| TRACE | 最详细的诊断信息,通常用于极端性能问题。 | 日志框架内部调用、字节码增强。 | 基本只在开发环境使用。 |
规则:
- 不要将ERROR用作WARN。“用户登录失败”如果是密码错误,是WARN;如果是认证服务宕机,是ERROR。
- 不要将INFO当作DEBUG,每条INFO日志都应该能被业务人员理解。
日志内容:具体、可操作、无歧义
- 避免打印大对象/数组,打印用户对象时,只打印ID,不要打印整个User Object的所有字段,打印SQL时,只打印查询条件,不打印全表。
- 包含关键标识符。
user_id,order_id,request_id,ip_address等,方便跨系统追溯。 - 记录入参和出参,在关键业务接口(如支付回调、下单接口)的入口和出口,记录入参(脱敏)、处理结果以及耗时。
- 异常日志的“黄金三要素”:
- 发生了什么?
msg: "Database connection pool exhausted" - 在哪里发生的?
logger: "com.example.dao.UserDao",thread: "http-nio-8080-exec-3" - 有什么影响?
context: { sql: "update users set ..." }(避免记录具体SQL值,只记录模板)
- 发生了什么?
- 不要记录用户敏感信息,对密码、身份证、手机号、信用卡号等进行脱敏(如
password: "***"或phone: "138****1234")。
性能最佳实践
日志输出是I/O密集型操作,处理不当会严重影响系统性能。
- 异步日志:必须使用异步日志记录器(如 Logback 的 AsyncAppender、Log4j2 的 AsyncLogger),避免日志线程阻塞业务线程。
- 避免在日志消息中进行字符串拼接:
- ❌
log.info("User {} processed order {} in {} ms", userId, orderId, costTime); - ✅
log.info("User processed order successfully");+ 通过 MDC 或结构化日志的context字段传递参数。
- ❌
- 合理使用参数化日志(SLF4J风格):
- ❌
log.debug("Value is " + heavyObject.getValue());(即使级别不输出,也会执行字符串拼接) - ✅
log.debug("Value is {}", heavyObject::getValue);(使用延迟求值,仅在DEBUG级别时计算)
- ❌
- 生产环境日志保留策略:
- 日志文件按时间(每天)和大小(每个文件建议 100MB-500MB)滚动。
- 保留时长:ERROR/ WARN 日志建议保留30天以上,DEBUG日志建议保留7天以内。
- 使用磁盘监控,避免日志写满磁盘。
实现示例(以 Java + SLF4J + Logback 为例)
代码层面:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
public class PaymentService {
private static final Logger log = LoggerFactory.getLogger(PaymentService.class);
public void processPayment(String userId, String orderId, double amount) {
// 1. 放入上下文(MDC 可以自动添加到日志模式)
MDC.put("user_id", userId);
MDC.put("order_id", orderId);
long start = System.currentTimeMillis();
try {
// 2. INFO 级别记录关键事件
log.info("开始处理支付请求。");
// 业务逻辑...
// 模拟一个需要关注的事件
if (amount > 10000) {
log.warn("大额支付交易,需人工复核。 context={{}}", Map.of("amount", amount));
}
// 模拟异常
// throw new RuntimeException("支付服务超时");
} catch (Exception e) {
// 3. ERROR 级别记录异常,必须包含堆栈
log.error("支付处理失败。", e);
// 或结构化记录
// log.error("支付处理失败。 context={}", Map.of("errorMsg", e.getMessage()), e);
} finally {
long cost = System.currentTimeMillis() - start;
log.info("支付请求处理完毕。 context={}", Map.of("duration_ms", cost));
MDC.clear(); // 清除上下文
}
}
}
logback-spring.xml 配置(输出JSON格式):
<configuration>
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.json</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.%i.json</fileNamePattern>
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<!-- 包含 MDC 上下文 -->
<includeMdcKeyName>user_id,order_id</includeMdcKeyName>
<fieldNames>
<timestamp>timestamp</timestamp>
<message>message</message>
<thread>thread</thread>
<logger>logger</logger>
<level>level</level>
<mdc>context</mdc> <!-- 将MDC内容放入context字段 -->
</fieldNames>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="JSON_FILE" />
</root>
</configuration>
其他语言类似:
- Python:使用
structlog或python-json-logger。 - Go:使用
zap或logrus,并启用 JSON Formatter。 - Node.js:使用
pino或winston,天然支持 JSON。
一张清单检查你的日志规范
- [ ] 格式是 JSON 吗?包含
timestamp,level,message,context等字段? - [ ] 所有请求都有 trace_id 和 span_id 用于链路追踪?
- [ ] 异常日志是否同时记录了 堆栈 和 上下文?
- [ ] 生产环境下 DEBUG 日志是否默认关闭?
- [ ] 用户密码、密钥等敏感信息是否彻底脱敏或删除?
- [ ] 日志是异步写入的吗?
- [ ] 日志文件有滚动策略(按时间/大小)和清理策略吗?
遵循这套规范,你的开源项目或内部系统的日志将会变得清晰、易于分析,成为排查问题的强大工具,而不是一个令人头疼的负担。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播