使用 java.util.logging 打印线程名称
问题的出现原因是在使用java.util.logging时,想要在日志消息中打印线程名称,但默认的日志格式化器无法提供这个功能。
根据stackoverflow上的回答,可以通过扩展java.util.logging.Formatter来解决这个问题。扩展的Formatter类中,可以通过Thread.currentThread().getName()方法获取线程名称,并将其添加到日志消息中。
下面是解决该问题的代码示例:
public class MyLogFormatter extends Formatter { private static final MessageFormat messageFormat = new MessageFormat("[{3,date,hh:mm:ss} {2} {0} {5}]{4} \n"); public MyLogFormatter() { super(); } public String format(LogRecord record) { Object[] arguments = new Object[6]; arguments[0] = record.getLoggerName(); arguments[1] = record.getLevel(); arguments[2] = Thread.currentThread().getName(); arguments[3] = new Date(record.getMillis()); arguments[4] = record.getMessage(); arguments[5] = record.getSourceMethodName(); return messageFormat.format(arguments); } }
这样,在日志记录时,会将线程名称添加到日志消息中,以便区分不同线程的日志记录。
使用Java的日志记录(logging)库来打印线程名字,可以通过自定义Formatter来实现。在LogRecord对象中包含了产生日志消息的线程的ID,我们可以通过自定义Formatter来获取LogRecord对象,然后通过线程ID获取线程名字。
下面是一个自定义Formatter的示例,它只打印线程名字和日志消息:
private static Formatter getMinimalFormatter() { return new Formatter() { public String format(LogRecord record) { int threadId = record.getThreadID(); String threadName = getThread(threadId) .map(Thread::getName) .orElseGet(() -> "Thread with ID " + threadId); return threadName + ": " + record.getMessage() + "\n"; } }; }
要使用自定义的Formatter,可以修改默认的ConsoleHandler。下面是一个示例代码:
public static void main(final String... args) { getDefaultConsoleHandler().ifPresentOrElse( consoleHandler -> consoleHandler.setFormatter(getMinimalFormatter()), () -> System.err.println("Could not get default ConsoleHandler")); Logger log = Logger.getLogger(MyClass.class.getName()); log.info("Hello from the main thread"); SwingUtilities.invokeLater(() -> log.info("Hello from the event dispatch thread")); } static OptionalgetDefaultConsoleHandler() { var rootLogger = Logger.getLogger(""); return first(Arrays.asList(rootLogger.getHandlers())); } static Optional first(List list) { return list.isEmpty() ? Optional.empty() : Optional.ofNullable(list.get(0)); }
使用上述的自定义Formatter,可以得到包含线程名字的日志消息:
main: Hello from the main thread
和
AWT-EventQueue-0: Hello from the event dispatch thread
此外,还可以自定义更多的内容来打印日志消息,下面是一个示例的Formatter:
private static Formatter getCustomFormatter() { return new Formatter() { public String format(LogRecord record) { var dateTime = ZonedDateTime.ofInstant(record.getInstant(), ZoneId.systemDefault()); int threadId = record.getThreadID(); String threadName = getThread(threadId) .map(Thread::getName) .orElse("Thread with ID " + threadId); var formatString = "%1$tF %1$tT %2$-7s [%3$s] %4$s.%5$s: %6$s %n%7$s"; return String.format( formatString, dateTime, record.getLevel().getName(), threadName, record.getSourceClassName(), record.getSourceMethodName(), record.getMessage(), stackTraceToString(record) ); } }; } private static String stackTraceToString(LogRecord record) { final String throwableAsString; if (record.getThrown() != null) { var stringWriter = new StringWriter(); var printWriter = new PrintWriter(stringWriter); printWriter.println(); record.getThrown().printStackTrace(printWriter); printWriter.close(); throwableAsString = stringWriter.toString(); } else { throwableAsString = ""; } return throwableAsString; }
此Formatter将产生类似下面的日志消息:
2019-04-27 13:21:01 INFO [AWT-EventQueue-0] package.ClassName.method: The log message
通过自定义Formatter,我们可以方便地打印线程名字和其他所需的信息来进行日志记录。
使用java.util.logging库时,无法直接打印线程名称,这是一个很尴尬的问题。默认的java.util.logging.SimpleFormatter根本不支持记录线程名称,而java.util.logging.FileHandler只支持一些模板占位符,其中没有线程名称。
最接近的是java.util.logging.XMLFormatter,但它只记录线程ID,而不是线程名称。
如果你认为我们已经接近解决方法了,那你就错了。LogRecord类只保存线程ID,而不是线程名称,这样并没有什么用处。
确实,SimpleFormatter不使用线程ID。但是,可以很容易地定义一个自定义格式化器,并通过线程ID获取线程名称。没有什么可尴尬的了。
解决方法是定义一个自定义格式化器,通过线程ID获取线程名称。以下是一个示例代码:
import java.util.logging.Formatter; import java.util.logging.LogRecord; public class CustomFormatter extends Formatter { @Override public String format(LogRecord record) { String threadName = Thread.currentThread().getName(); return "[" + threadName + "] " + record.getMessage() + "\n"; } }
然后,可以将自定义格式化器应用于日志记录器:
import java.util.logging.ConsoleHandler; import java.util.logging.Logger; public class Main { private static final Logger LOGGER = Logger.getLogger(Main.class.getName()); public static void main(String[] args) { ConsoleHandler consoleHandler = new ConsoleHandler(); consoleHandler.setFormatter(new CustomFormatter()); LOGGER.addHandler(consoleHandler); LOGGER.info("Test"); } }
通过这种方式,就可以在日志中打印线程名称了。