
理解 SimpleFormatter.format 配置
java.util.logging.simpleformatter 是 java 日志框架中一个常用的格式化器,它允许开发者通过配置 format 属性来定义日志输出的样式。这个 format 属性实际上遵循 java.util.formatter 的语法规则,其中 %n$ 这样的占位符用于引用日志记录中的特定数据字段。理解这些索引字段的含义是实现日志自定义的关键。
索引字段详解
SimpleFormatter 的 format 字符串中,%n$ 形式的占位符 n 代表一个数字,从1到6,每个数字都对应 LogRecord 对象中的一个特定信息。这些字段的顺序和含义如下:
-
%1$:日期 (Date)
- 表示日志事件发生的时间。它是一个 java.util.Date 对象,对应 LogRecord.getMillis() 返回的时间戳。通常与日期/时间转换符(如 %tc 表示完整日期时间,%tF 表示年-月-日,%tT 表示时:分:秒)结合使用。
- 示例: %1$tc 会输出 星期二 12月 19 10:30:45 CST 2023 这样的格式。
-
%2$:来源 (Source)
- 表示日志事件的调用者信息(如果可用),否则为记录器(Logger)的名称。这有助于追溯日志的产生位置。
-
%3$:记录器名称 (Logger Name)
立即学习“Java免费学习笔记(深入)”;
- 直接表示产生此日志记录的 Logger 实例的名称。
-
%4$:日志级别 (Log Level)
- 表示日志记录的级别,如 INFO, WARNING, SEVERE 等,通常是其本地化名称。
-
%5$:日志消息 (Message)
- 这是通过 Formatter.formatMessage(LogRecord) 方法处理后的最终日志消息。它支持 java.text.MessageFormat 风格的格式化,但与 java.util.Formatter 的格式参数不同。这意味着你在日志消息本身中使用的占位符(如 Hello {0})会在这里被解析。
- 示例: 如果日志代码是 logger.info("User {0} logged in", "Alice"),那么 %5$ 将输出 User Alice logged in。
-
%6$:异常信息 (Thrown)
- 如果日志记录关联了 Throwable 对象(即记录了异常),则此字段会包含异常的堆栈跟踪信息,以换行符开头。如果没有任何异常,则为空字符串。
自定义日志格式示例
假设我们希望日志输出格式为:[日期时间] [来源] 级别: 消息 异常堆栈,我们可以这样配置 format 属性。
示例配置:logging.properties 文件
# 默认的 ConsoleHandler 使用 SimpleFormatter java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter # 自定义 SimpleFormatter 的格式 # %1$tc: 日期时间 (完整格式) # %2$s: 来源 # %4$s: 日志级别 # %5$s: 日志消息 # %6$s: 异常信息 # %n: 换行符 java.util.logging.SimpleFormatter.format = [%1$tc] [%2$s] %4$s: %5$s%6$s%n
代码示例:在程序中设置格式
import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
public class CustomLogFormatter {
private static final Logger logger = Logger.getLogger(CustomLogFormatter.class.getName());
public static void main(String[] args) {
// 移除默认的 ConsoleHandler,避免重复输出
Logger rootLogger = Logger.getLogger("");
for (java.util.logging.Handler handler : rootLogger.getHandlers()) {
if (handler instanceof ConsoleHandler) {
rootLogger.removeHandler(handler);
}
}
// 创建并配置自定义的 ConsoleHandler
ConsoleHandler consoleHandler = new ConsoleHandler();
// 设置 SimpleFormatter 的格式
SimpleFormatter formatter = new SimpleFormatter();
// 注意:在代码中设置格式需要通过系统属性或者自定义Formatter类
// 最直接的方式是通过logging.properties文件,或者创建一个匿名子类
// 为了演示,我们假设SimpleFormatter的format属性可以通过某种方式被设置
// 实际应用中,通常会通过logging.properties或者自定义Formatter类来实现
// 假设我们通过系统属性设置了格式 (在运行前设置 -Djava.util.logging.SimpleFormatter.format=...)
// 或者创建一个匿名SimpleFormatter子类来硬编码格式
SimpleFormatter customFormatter = new SimpleFormatter() {
private static final String FORMAT = "[%1$tc] [%2$s] %4$s: %5$s%6$s%n";
@Override
public String format(java.util.logging.LogRecord record) {
return String.format(FORMAT,
new java.util.Date(record.getMillis()), // 1$
record.getSourceClassName() != null ? record.getSourceClassName() + "." + record.getSourceMethodName() : record.getLoggerName(), // 2$
record.getLoggerName(), // 3$
record.getLevel().getLocalizedName(), // 4$
formatMessage(record), // 5$
record.getThrown() != null ? formatThrowable(record.getThrown()) : "" // 6$
);
}
private String formatThrowable(Throwable thrown) {
java.io.StringWriter sw = new java.io.StringWriter();
java.io.PrintWriter pw = new java.io.PrintWriter(sw);
pw.println(); // Add a newline before stack trace
thrown.printStackTrace(pw);
pw.close();
return sw.toString();
}
};
consoleHandler.setFormatter(customFormatter);
logger.addHandler(consoleHandler);
logger.setLevel(Level.INFO); // 设置日志级别
logger.info("这是一个普通的信息日志。");
logger.warning("发现一个警告!");
try {
throw new IOException("文件读取失败!");
} catch (IOException e) {
logger.log(Level.SEVERE, "发生严重错误", e);
}
}
}预期输出示例 (取决于日期和具体来源信息):
[2023年12月19日 下午03时30分45秒 CST] [CustomLogFormatter.main] 信息: 这是一个普通的信息日志。
[2023年12月19日 下午03时30分45秒 CST] [CustomLogFormatter.main] 警告: 发现一个警告!
[2023年12月19日 下午03时30分45秒 CST] [CustomLogFormatter.main] 严重: 发生严重错误
java.io.IOException: 文件读取失败!
at CustomLogFormatter.main(CustomLogFormatter.java:50)注意事项
- 换行符 (%n): 在 format 字符串中,%n 是平台无关的换行符,推荐使用它而不是硬编码 \n。
- 类型转换符: 每个索引字段后面都需要一个类型转换符,最常用的是 %s (字符串)。对于日期字段 %1$,则需要使用日期/时间转换符,如 %tc、%tF、%tT 等。
- %5$ 的特殊性: 5$ 字段的日志消息已经过 Formatter.formatMessage 处理,这意味着它内部可能已经包含了参数替换。因此,在 format 字符串中,不应再对 5$ 字段应用额外的 java.text.MessageFormat 格式化。
- JavaDocs 是权威参考: 尽管本文提供了详细解释,但 Oracle 官方的 JavaDocs 始终是理解这些字段最权威和最新的来源。建议在遇到疑问时查阅相关文档。
- 性能考量: 过于复杂的格式化字符串可能会对日志性能产生轻微影响,但在大多数应用中,这种影响可以忽略不计。
总结
通过掌握 java.util.logging.SimpleFormatter 的 format 属性及其六个核心索引字段,开发者可以高度定制化 Java 日志的输出格式。无论是为了提高日志的可读性、便于问题排查,还是为了集成到特定的日志分析工具中,理解并灵活运用这些格式化选项都是一项宝贵的技能。通过配置文件或代码动态设置,可以根据项目需求轻松调整日志的呈现方式。










