Java 日志
日志门面与实现、级别与结构化字段:如何让排障信息可检索且可控量。
#concept
#growing
#dev
#java
[!info] related notes
Java 日志
这个概念为什么出现
生产日志需要级别、结构、上下文和输出策略;容器将日志送到 stdout/stderr 是常见做法,但随处 println 不提供这些治理能力。日志提供运行轨迹:请求 id、错误栈、关键业务字段。Java 生态常见 门面(SLF4J)+ 实现(Logback/Log4j2)。
[!abstract] 一句话理解 业务代码依赖日志门面;级别控制噪音;用参数化消息与关联 ID,让日志可检索并避免无用字符串拼接成本。
最小可运行示例
场景:下单失败时记录可检索错误
import java.util.logging.Level;
import java.util.logging.Logger;
public class OrderLogDemo {
private static final Logger log = Logger.getLogger(OrderLogDemo.class.getName());
static void place(String orderId, boolean ok) {
if (!ok) {
// JUL 参数化消息;这仍是文本,不会自动生成独立 JSON 字段
log.log(Level.WARNING, "place failed orderId={0}", orderId);
return;
}
log.info(() -> "place ok orderId=" + orderId);
}
public static void main(String[] args) {
place("ORD-1", false);
place("ORD-2", true);
}
}
建议运行:
javac OrderLogDemo.java && java OrderLogDemo
默认 JUL 配置下,WARNING 与 INFO 消息含对应 orderId,通常由 ConsoleHandler 写入 stderr。时间、级别显示语言和格式取决于配置,不应比对整行固定文本。
结合场景再看三个关注点
- 统一日志 API 与实现;SLF4J 是常见门面,本例使用 JDK 自带的 JUL,无需额外依赖。
- 级别名称依 API 而异:SLF4J 有 ERROR/WARN/INFO/DEBUG/TRACE;JUL 使用 SEVERE/WARNING/INFO/FINE 等。
- 敏感数据脱敏。
核心概念与准确模型
1. 门面 vs 实现
- 门面稳定 API
- 后端实现负责输出与格式,异步和滚动等能力取决于后端及配置;SLF4J API 自身不是完整后端。避免多个 provider 或双向桥接造成冲突/循环。
2. 结构化
结构化输出需后端/编码器把字段写成可解析结构;"orderId={}" 只是参数化消息。按需要记录 traceId 等关联字段,敏感标识需最小化或脱敏。
3. 配置
Logback 常用 logback.xml,JUL 等后端有自己的配置机制;动态调整级别是否可用取决于实现和部署配置。
边界情况与反直觉行为
- 错误日志打两次(框架+业务)。
- 参数化消息不会延迟 Java 实参求值:即使级别关闭,
log.debug("x={}", expensive())仍先调用 expensive。可用级别检查或对应 API 的 Supplier 支持;本例 JUL 的 info Supplier 可避免未启用 INFO 时生成字符串。 - 丢弃异常对象只打消息。
常见误区
[!warning] 常见误区:e.printStackTrace() 难重定向、难关联请求;走日志框架。
工程实践
- 统一门面。
- 每个请求相关日志带 correlation id。
- 访问日志与应用日志分工。
- 采样与级别策略防爆盘。
本节总结
- 门面+实现
- 级别+字段
- 为可观测打底
自测题
- 为什么要用参数化
log.info("x={}", id)? - WARN 与 ERROR 如何粗分?
参考答案
- 可把消息格式化延后到启用级别时,但不会跳过实参表达式本身的计算,也不自动产生结构化字段。
- ERROR:需立即关注的失败;WARN:异常但可恢复/降级。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| SLF4J Manual | 官方 | 门面 |
| java.util.logging | API | JDK 日志 |