19

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.

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Majid Alfifi
  • 568
  • 2
  • 5
  • 18
  • I believe, using log4j or slf4j will be cleaner than the suggested solutions in the answers. :) – Diablo Dec 28 '16 at 13:16

8 Answers8

10

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.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • It's true that `SimpleFormatter` does not use the thread ID. But [it's easy to define](https://stackoverflow.com/questions/6889057/printing-thread-name-using-java-util-logging/55880722#55880722) a custom formatter and get the thread name via the thread ID. No need for embarrassment :-) – Matthias Braun Apr 27 '19 at 13:01
8

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

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
3

I had similar problem. As answered here How to align log messages using java.util.logging you can extend java.util.logging.Formatter but instead getting LogRecord#getThreadID() you can get thread name by invoking Thread.currentThread().getName() like this:

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();
    }

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

}
Community
  • 1
  • 1
l245c4l
  • 4,135
  • 9
  • 35
  • 40
  • 11
    Won't this put in the thread name of the thread calling the formatter and not the thread name of the thread that created the log entry? I guess they could be the same in some situations but there's no guarantee. – pillingworth Jan 18 '13 at 10:01
2

Some application servers implicitly log the thread ID (I know of WebSphere). You can create your own LogFormatter. The records passed to the formatter contain the Thread ID, see here. I implemented that approach for Tomcat several times, but it'll work in Java SE environments as well.

BTW: The Thread name is not available to LogRecord.

home
  • 12,468
  • 5
  • 46
  • 54
2

java.util.logging has many curious peculiarities. you can add a facade API to tweak its behaviors

public class Log

    Logger logger;

    static public Log of(Class clazz)
        return new Log( Logger.getLogger( clazz.getName() ));

    public void error(Throwable thrown, String msg, Object... params)
    {
        log(ERROR, thrown, msg, params);
    }

    void log(Level level, Throwable thrown, String msg, Object... params)
    {
        if( !logger.isLoggable(level) ) return;

        // bolt on thread name somewhere
        LogRecord record = new LogRecord(...);
        record.setXxx(...);
        ...
        logger.log(record);
    }

----

static final Log log = Log.of(Foo.class);
....
log.error(...);

People use java's logging mostly because they don't want to have 3rd party dependencies. That's also why they can't depend on existing logging facades like apache's or slf4j.

irreputable
  • 44,725
  • 9
  • 65
  • 93
0

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.

  • 1
    I was under the impression that it was best to not let constructors throw exceptions, as per [Be wary of letting constructors throw exceptions](https://wiki.sei.cmu.edu/confluence/display/java/OBJ11-J.+Be+wary+of+letting+constructors+throw+exceptions) – Scratte Jul 16 '20 at 07:25
  • I do agree on that. Thanks for pointing it out @Scratte – Shanmugavel Jul 16 '20 at 07:53
-2

A couple of the answers above suggest that LogRecord.getThreadId() returns a meaningful thread ID, and all we're missing is a way to correlate that to the thread's name.

Unfortunately LogRecord.getThreadId() returns an int value which does not correspond to the long id of the thread which induced the log message.

So we cannot just use ManagementFactory.getThreadMXBean() to resolve the thread name. It results in random thread names.

If you are certain that your Logging facility always formats in the same thread as the caller, then you can create a custom Formatter as proposed above, and call Thread.currentThread().getName().

It seems that a Logging facade or a third party library are the only completely safe options.

  • `LogRecord.getThreadID()` **does** return a meaningful thread ID. You can correlate the ID with the `Thread` object using, for example, [`Thread.getAllStackTraces`](https://stackoverflow.com/questions/6889057/printing-thread-name-using-java-util-logging/55880722#55880722). – Matthias Braun Apr 29 '19 at 09:08
-2

To complement @l245c4l answer: Instead of using SimpleFormatter() use:

//fileHandler.setFormatter(new SimpleFormatter());

class MyFormatter extends Formatter {
    private final MessageFormat messageFormat = new MessageFormat("{0,date}, {0,time} {1} {2}: {3} [T:{4}] {5}\n");

    public String format(LogRecord record)
    {
        Object[] arguments = new Object[6];
        arguments[0] = new Date( record.getMillis() );
        arguments[1] = record.getSourceClassName();
        arguments[2] = record.getSourceMethodName();
        arguments[3] = record.getLevel();
        arguments[4] = Long.toString( Thread.currentThread().getId() );
        arguments[5] = record.getMessage();

        return messageFormat.format(arguments);
    }
}

fileHandler.setFormatter( new MyFormatter() ); 

Logger myLogger = Logger.getLogger("<LOGGER_NAME>");
myLogger.addHandler(fileHandler);

where T:{4} is the thread id (argument 4).

Pedro Reis
  • 1,587
  • 1
  • 19
  • 19