0Pricing
Java Academy · 课时

参数化日志记录

高效格式化消息

参数化日志记录 是 CoddyKit 上的免费 Java Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。

字符串拼接的问题

拼接日志消息时,即使该级别已被禁用,也会在调用之前构造完整字符串。

log.debug("id=" + id + " name=" + name) 会在 DEBUG 关闭时浪费 CPU,因为无论如何都会构造字符串。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        String name = "Ada"; int id = 7;
        // Bad: string is built even if DEBUG is disabled
        log.debug("id=" + id + " name=" + name);
    }
}

使用 {} 占位符

SLF4J 使用 {} 作为占位符。请将值作为额外参数传入。

只有启用该级别时才会组装消息,因此被禁用的日志几乎不会产生开销。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        String name = "Ada"; int id = 7;
        // Good: no string built unless DEBUG is on
        log.debug("id={} name={}", id, name);
    }
}

多个占位符

占位符会按顺序填充。第一个 {} 使用第一个参数,第二个使用第二个参数,依此类推。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        log.info("order {} for user {} totaling {} cents", "A-1", 42, 1599);
    }
}

为什么这样高效

框架会先检查级别。如果级别已禁用,就会立即返回,不会对任何参数调用 toString()。

开销较大的格式化操作会延迟执行,只有在消息确实要输出时才会进行。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    static String describe(Object o) { System.out.println("toString called"); return o.toString(); }
    public static void main(String[] args) {
        // Note: passing describe(...) still evaluates eagerly; pass the raw object instead
        log.trace("value={}", new int[]{1, 2, 3});
    }
}

传入原始对象,而不是预格式化字符串

要获得延迟执行的好处,请传入原始对象,不要传入会对其进行格式化的方法调用。

如果写成 log.debug("{}", buildReport()),buildReport() 仍会立即执行。请传入由日志记录器自行格式化的参数。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        Object user = new Object();
        // Logger calls user.toString() only if DEBUG is enabled
        log.debug("current user: {}", user);
    }
}

使用延迟供应器处理繁重工作

当格式化操作确实开销很大时,请使用 SLF4J 2.x 的流式 API和 Supplier,以延迟执行相关工作。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    static String heavyReport() { return "...expensive..."; }
    public static void main(String[] args) {
        log.atDebug()
           .setMessage("report: {}")
           .addArgument(() -> heavyReport())
           .log();
    }
}

转义字面量大括号

如果消息中需要包含字面量 {},请使用反斜杠进行转义:\\{}。

这种情况并不常见,但记录类似 JSON 的文本时很有用。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        // Logs a literal {} then the value
        log.info("empty object is \\{} and id is {}", 7);
    }
}

异常与占位符结合使用

您可以将占位符与末尾的异常结合使用。SLF4J 会检测最后一个参数是否为 Throwable,并输出其堆栈跟踪。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        int id = 99;
        try {
            throw new RuntimeException("db down");
        } catch (Exception e) {
            log.error("failed processing record {}", id, e);
        }
    }
}

避免使用过多参数

如果占位符数量与参数数量不一致,SLF4J 会尽力处理,但输出结果可能看起来不正确。

请保持二者数量一致。如果值超过两三个,可以考虑改用结构化上下文(MDC)。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        // Two placeholders, two arguments: correct
        log.info("user {} did {}", "ada", "login");
    }
}

使用 MDC 添加上下文

对于许多日志行都会共享的值(例如请求 ID),请将它们放入 MDC,而不要在每条消息中重复传入。

这样,模式布局就可以自动在每一行输出 MDC 中的值。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        MDC.put("requestId", "req-1234");
        try {
            log.info("handling request");
            log.info("request done");
        } finally {
            MDC.clear();
        }
    }
}

最佳实践总结

高效记录日志的规则:

  • 始终使用 {} 占位符,绝不要使用 + 拼接。
  • 传入原始对象,而不是预先构造的字符串。
  • 对于真正开销很大的工作,请使用供应器或 isXEnabled()。
  • 使用 MDC 保存跨多个部分共享的上下文。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        log.info("processed {} items in {} ms", 1000, 42);
    }
}

快速检查

测试您对参数化日志记录的了解。

回顾

您已经学习了参数化日志记录:

  • 将 {} 占位符与额外参数结合使用。
  • 消息会延迟构造,仅在级别启用时执行。
  • 传入原始对象;对于开销较大的工作,请使用供应器。
  • 使用 MDC 保存共享的上下文值。

接下来将学习配置 Logback 后端。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        log.info("Parameterized logging recap: {}", "done");
    }
}

常见问题解答

「参数化日志记录」课时是免费的吗?

是的 — 「参数化日志记录」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。

「参数化日志记录」这节课中我会学到什么?

高效格式化消息 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Java Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「参数化日志记录」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Java Academy 课中编写并运行代码吗?

能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 结构化日志为何重要
  2. SLF4J 外观与日志记录器
  3. 参数化日志记录
  4. 配置 Logback
← 返回 Java Academy