我尝试了几个选项(我真的尝试过),但我无法获得类似于 Log4j 的格式。简而言之,我想输出以下格式的日志行:
2014-09-24 14:05:18 INFO CustomerController:96 - Starting purchase order...
------------------- ----- --------------------- --------------------------
Timestamp Level Class & line Message
在 log4j 中我曾经设置模式:
d{YYYY-MM-dd HH:mm:ss} %-5p %C{1}(%L) - %m%n
即:
我可以使用可部署 JAR 中的内部配置文件,或使用内部 Java 编程配置。只要我不需要部署适用于我们环境的外部“额外”文件。
日志消息的格式为
java.util.logging.Formatter
。不幸的是,这两个内置实现似乎都无法提供您想要的东西。您可以获得的最接近的是格式为 SimpleFormatter
的 %1$TF %1$TT %4$-5s - %5$s%6$s
(%6$s
表示例外)。除了类名和行号之外,这将为您提供您想要的一切。您可以修改格式以包含源代码,但似乎没有办法省略包(或方法名称)。并且无法定义包含行号的格式。
我认为您唯一的选择是创建自己的
Formatter
实现(或找到现有的第三方实现)。以下似乎满足您的要求:
package com.example;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.StackWalker.StackFrame;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
public class CustomFormatter extends Formatter {
private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("uuuu-MM-dd hh:mm:ss");
@Override
public String format(LogRecord record) {
var builder = new StringBuilder();
appendDateTime(record, builder);
appendLevel(record, builder);
appendClassNameAndLineNumber(record, builder);
appendMessage(record, builder);
appendThrown(record, builder);
return builder.toString();
}
private void appendDateTime(LogRecord record, StringBuilder builder) {
var zdt = record.getInstant().atZone(ZoneId.systemDefault());
builder.append(DTF.format(zdt));
}
private void appendLevel(LogRecord record, StringBuilder builder) {
var name = record.getLevel().getName();
builder.append(" ").append(name);
if (name.length() < 5) {
builder.append(" ".repeat(5 - name.length()));
}
}
private void appendClassNameAndLineNumber(LogRecord record, StringBuilder builder) {
var frame = new SourceFinder().get();
if (frame != null) {
var className = frame.getClassName();
int index = className.lastIndexOf('.');
if (index != -1) {
className = className.substring(index + 1);
}
builder.append(" ").append(className).append(":").append(frame.getLineNumber());
}
}
private void appendMessage(LogRecord record, StringBuilder builder) {
builder.append(" - ").append(formatMessage(record));
}
private void appendThrown(LogRecord record, StringBuilder builder) {
var thrown = record.getThrown();
if (thrown != null) {
var sw = new StringWriter();
thrown.printStackTrace(new PrintWriter(sw));
builder.append(System.lineSeparator()).append(sw.toString());
}
}
private static class SourceFinder implements Predicate<StackFrame>, Supplier<StackFrame> {
private static final StackWalker WALKER = StackWalker.getInstance();
private boolean foundLogger;
@Override
public StackFrame get() {
return WALKER.walk(s -> s.filter(this).findFirst()).orElse(null);
}
@Override
public boolean test(StackFrame t) {
if (!foundLogger) {
foundLogger = t.getClassName().equals(Logger.class.getName());
return false;
}
return !t.getClassName().startsWith("java.util.logging");
}
}
}
您可能想要增加关卡名称使用的“填充”。在预定义的级别中,有
WARNING
,其长度为 7。这忽略了您是否要修改上面的内容以获取级别的本地化名称。
这是使用上述格式化程序的示例:
package com.example;
import java.util.logging.Logger;
import java.util.logging.StreamHandler;
public class Main {
public static void main(String[] args) {
var logger = Logger.getLogger(Main.class.getName());
// Putting the configuration here to make running the example easier. In real code, you'd want
// to configure JUL wwith a properties file or a configuration class. The following configures
// the logger to *not* use parent handlers to avoid the default handler added to the root
// logger.
logger.setUseParentHandlers(false);
logger.addHandler(new StreamHandler(System.out, new CustomFormatter()));
logger.info("This is a test message..."); // line 18
}
}
输出:
2024-09-24 09:01:38 INFO Main:18 - This is a test message...