占位符日志的核心是“按需计算”——仅当日志级别允许输出时才执行参数表达式,避免无效cpu消耗和对象构造;slf4j对{}特殊处理,未启用debug时跳过参数求值;高成本操作需用supplier延迟执行,并配合isdebugenabled()双重保险。

用占位符替代字符串拼接,核心在于“按需计算”——日志级别不满足时,参数表达式根本不会执行,自然避免了无效的 CPU 消耗和对象构造。
为什么字符串拼接在日志里很危险
哪怕你把日志级别设为 WARN,只要写了这行代码:
logger.debug("User " + user.getName() + " login at " + new Date());
每次调用时,user.getName() 和 new Date() 都会强制执行,StringBuilder 拼接也会发生。这些操作白做了,但开销已产生——尤其当 user 是远程 DTO、getName() 触发 RPC,或 new Date() 被高频调用时,性能损耗明显。
占位符怎么真正起作用
SLF4J(Logback/Log4j2 底层常用)对 {} 占位符做了特殊处理:只有当前 logger 级别允许输出该日志(比如 DEBUG 已启用),才会去求值参数;否则直接跳过整个参数计算流程。
- ✅ 安全写法:logger.debug("User {} login at {}", user.getName(), LocalDateTime.now());
- ⚠️ 注意:这里 user.getName() 和 LocalDateTime.now() 仍会在 DEBUG 启用时执行 —— 如果它们本身开销大,还需进一步优化(见下一点)
复杂参数要再加一层延迟
当某个参数涉及 JSON 序列化、集合遍历、数据库查询等高成本操作时,仅靠 {} 不够。此时应封装成 Supplier,让计算真正延迟到“确定要打印”那一刻:
- ✅ 推荐:logger.debug("Order detail: {}", () -> JSON.toJSONString(order));
- ❌ 避免:logger.debug("Order detail: {}", JSON.toJSONString(order));(即使用了 {},序列化仍会立即执行)
配合日志级别开关更稳妥
对 trace/debug/info 这类低级别日志,建议双重保险:先判断是否启用,再输出。这不是冗余,而是堵住 Supplier 以外的隐性开销(比如方法调用本身有副作用):
- ✅ 推荐写法:
if (logger.isDebugEnabled()) {
logger.debug("Processing trade id: {}, symbol: {}", id, symbol);
} - 说明:isDebugEnabled() 是轻量级布尔判断,几乎无开销;加上它,可确保连占位符参数都不会被传入 logger 方法体
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











