使用java.util.logging打印线程名称

时间:2011-07-31 11:04:12

标签: java logging

是否可以在java.util.logging.Logger

生成的日志语句中打印线程名称

一种替代方法是执行以下操作:

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

但它是重复的,日志框架应该处理它。

8 个答案:

答案 0 :(得分:7)

令人尴尬,但看起来java.util.logging无法做到这一点......

默认java.util.logging.SimpleFormatter根本无法记录线程名称。 java.util.logging.FileHandler支持很少的模板占位符,它们都不是线程名称。

java.util.logging.XMLFormatter是最接近的,但只记录线程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>

如果你认为我们正在接近 - 我们不是。 LogRecord类只保存线程ID,而不是它的名称 - 不是很有用。

答案 1 :(得分:3)

我有类似的问题。正如此处How to align log messages using java.util.logging所述,您可以扩展java.util.logging.Formatter,但是获取LogRecord#getThreadID(),您可以通过调用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();
    }

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

}

答案 2 :(得分:2)

某些应用程序服务器隐式记录线程ID(我知道WebSphere)。您可以创建自己的LogFormatter。传递给格式化程序的记录包含线程ID,请参阅here。我多次为Tomcat实现了这种方法,但它也适用于Java SE环境。

BTW:线程名称不可用于LogRecord。

答案 3 :(得分:2)

java.util.logging有许多好奇的特点。您可以添加外观API来调整其行为

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

人们使用java的日志记录主要是因为他们不想拥有第三方依赖项。这也是为什么他们不能依赖现有的登录外观,如apache或slf4j。

答案 4 :(得分:1)

使用自定义Formatter

幸运的是,LogRecord包含产生日志消息的线程的ID。编写自定义LogRecord时,我们可以掌握此Formatter。一旦有了该名称,我们只需通过其ID获取线程名称。

a couple of ways用来获取与该ID对应的Thread对象,这是我的:

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

以下是最小的Formatter,仅显示线程名称和日志消息:

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

要使用自定义格式器,请再次使用different options,一种方法是修改默认的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));
}

您的最小Formatter然后应生成包含线程名称的以下日志消息:

  

main:主线程打招呼

  

AWT-EventQueue-0:事件分发线程打招呼


这是Formatter,它显示了如何记录更多的线程名称和日志消息:

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

Formatter产生如下日志消息:

  

2019-04-27 13:21:01 INFO [AWT-EventQueue-0]包。ClassName.method:日志消息

答案 5 :(得分:0)

上面的几个答案表明LogRecord.getThreadId()返回一个有意义的线程ID,而我们所缺少的只是一种将它与线程名称相关联的方法。

不幸的是,LogRecord.getThreadId()返回一个int值,该值与引发日志消息的线程的long id不对应。

因此我们不能只使用ManagementFactory.getThreadMXBean()来解析线程名称。它会产生随机的线程名称。

如果您确定您的Logging工具始终与调用者在同一个线程中进行格式化,那么您可以按照上面的建议创建自定义Formatter,并调用Thread.currentThread()。getName()。

似乎Logging facade或第三方库是唯一完全安全的选项。

答案 6 :(得分:0)

如前面的回答中所述,LogRecord仅具有ThreadID信息,您必须遍历线程列表以获取线程名称。 令人惊讶的是,在某些情况下,在Logger记录消息时,该线程可能未处于活动状态。

我建议编写一个包装器,使您可以将线程名称与消息本身一起发送。

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

输出将是:

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

我没有扩展整个Logger类,而是打包了它,因为我不想覆盖所有方法。我只需要几种方法。

答案 7 :(得分:-2)

补充@ l245c4l答案:使用:

而不是使用SimpleFormatter()
//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);

其中T:{4}是线程id(参数4)。