Printing thread name using java.util.logging

Viewed 22910

Is it possible to print the thread name in the log statements generated by java.util.logging.Logger?

One alternative is to do something like the following:

logger.info(thread.getName() + " some useful info");

but it's repetitive and the logging framework should handle it.

8 Answers

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

As mentioned in the previous answers, LogRecord only has the ThreadID information and you have to iterate over the thread list to get the thread name. Suprisingly enough, the thread might not be alive at the time the Logger logs the message in some cases.

What i suggest is to write a Wrapper which will allow you to send the thread name along with the message itself.

package com.sun.experiments.java.logging;

    import java.util.logging.Level;
    
    public class ThreadLogger {
    
        public static void main(String[] args) {
            Logger log = Logger.getLogger(ThreadLogger.class.getName());//Invokes the static method of the below Logger class
            log.log(Level.INFO, "Logging main message");
            new Thread(()-> {Logger.getLogger(ThreadLogger.class.getName());log.log(Level.INFO, "Logging thread message");}).start();
        }
    
        public static class Logger{
            private final java.util.logging.Logger log;
            private Logger() {  log = null;}//Shouldn't use this. The log is initialized to null and thus it will generate Null Pointer exception when accessing methods using it.
            private Logger(String name) {
                log = java.util.logging.Logger.getLogger(name);
            }   
            private static Logger getLogger(String name) {
                return new Logger(name);
            }
            public void log(Level level,String message)
            {
                message = "["+Thread.currentThread().getName()+"]: "+message;
                log.log(level,message);
            }
            public void log(Level level,String message,Throwable e)
            {
                message = "["+Thread.currentThread().getName()+"]: "+message;
                log.log(level,message,e);
            }
        }
    }

And the output will be :

Jul 16, 2020 12:06:24 PM com.sun.experiments.java.logging.ThreadLogger$Logger log
INFO: [main]:     Logging main message
Jul 16, 2020 12:06:24 PM com.sun.experiments.java.logging.ThreadLogger$Logger log
INFO: [Thread-1]: Logging thread message

Instead of extending the entire Logger class i went for wrapping it, as i don't want to override all the methods. I only require a few methods.

Related