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

是否可以在java.util.logging.Logger生成的日志语句中打印线程名?

另一种方法是执行如下操作:


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



时间:

很尴尬,但是,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,而不是它的名称-不太有用。

某些应用程序服务器隐式地记录线程ID (我知道WebSphere ),你可以创建自己的logFormatter ,传递给格式化程序的记录包含线程标识,请参见这里 ,在Tomcat中,我多次实现了这种方法,但是它同样适用于Java SE环境。

BTW :LogRecord的线程名不可用。

java.util.logging有许多奇怪的特性。 你可以添加一个facade 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。

使用自定义Formatter

幸好,LogRecord包含生成日志消息的线程的ID ,我们可以在编写定制的Formatter时获取这个LogRecord ,一旦我们使用它,我们只需要通过它ID获取线程名称。

有几个方法来获取与该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";


 }


 };


}



要使用自定义格式化程序,还有不同选项,其中一种方法是修改默认的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应随后生成包含线程名称的下列日志消息:

主:来自主线程的问候

AWT-EventQueue-0 :来自事件调度线程的hello

这是一个演示如何记录超过线程名称和日志消息的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] package.ClassName.method : the log message


//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 )。

...