如何配置 Java Util Logging 以将日志记录显示为 log4j?

问题描述 投票:0回答:1

我尝试了几个选项(我真的尝试过),但我无法获得类似于 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

即:

  • 单行日志。
  • 本地时间戳(无时区)
  • 5 个字母的级别。 “INFO”用一个空格填充。
  • 类名(无包)和行号。
  • 留言。

我可以使用可部署 JAR 中的内部配置文件,或使用内部 Java 编程配置。只要我不需要部署适用于我们环境的外部“额外”文件。

java configuration java.util.logging
1个回答
0
投票

日志消息的格式为

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...
© www.soinside.com 2019 - 2024. All rights reserved.