Printing thread name using java.util.logging

Embarrassingly, but looks like java.util.logging can't do this...

The default java.util.logging.SimpleFormatter doesn't have the ability to log thread name at all. The java.util.logging.FileHandler supports few template placeholders, none of them is thread name.

java.util.logging.XMLFormatter is the closest one, but only logs thread id:

<record>
  <date>2011-07-31T13:15:32</date>
  <millis>1312110932680</millis>
  <sequence>0</sequence>
  <logger></logger>
  <level>INFO</level>
  <class>java.util.logging.LogManager$RootLogger</class>
  <method>log</method>
  <thread>10</thread>
  <message>Test</message>
</record>

If you think we're getting close - we're not. LogRecord class only holds the thread ID, not its name - not very useful.


With a custom Formatter

Luckily, LogRecord contains the ID of the thread that produced the log message. We can get hold of this LogRecord when writing a custom Formatter. Once we have that, we only need to get the thread name via its ID.

There are a couple of ways to get the Thread object corresponding to that ID, here's mine:

static Optional<Thread> getThread(long threadId) {
    return Thread.getAllStackTraces().keySet().stream()
            .filter(t -> t.getId() == threadId)
            .findFirst();
}

The following is a minimal Formatter that only prints the thread name and the log message:

private static Formatter getMinimalFormatter() {
    return new Formatter() {

        @Override
        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";
        }
    };
}

To use your custom formatter, there are again different options, one way is to modify the default 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 Optional<Handler> getDefaultConsoleHandler() {
    // All the loggers inherit configuration from the root logger. See:
    // https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html#a1.3
    var rootLogger = Logger.getLogger("")
    // The root logger's first handler is the default ConsoleHandler
    return first(Arrays.asList(rootLogger.getHandlers()));
}

static <T> Optional<T> first(List<T> list) {
    return list.isEmpty() ?
            Optional.empty() :
            Optional.ofNullable(list.get(0));
}

Your minimal Formatter should then produce the folowing log messages containing the thread name:

main: Hello from the main thread

and

AWT-EventQueue-0: Hello from the event dispatch thread


This is a Formatter that shows how to log more than thread name and log message:

private static Formatter getCustomFormatter() {
    return new Formatter() {

        @Override
        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);

            // See also: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html
            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;
}

That Formatter produces log messages like these:

2019-04-27 13:21:01 INFO [AWT-EventQueue-0] package.ClassName.method: The log message

Tags:

Java

Logging