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