在记录活动中使用readConfiguration方法

为了在小型Java桌面应用程序中使用日志记录,我试图深入理解某些方法的操作。 我使用一个非常愚蠢的小程序来测试它们。

特别是,在测试LogManager.readConfiguration()方法的行为时,我发现了一些奇怪的东西。 在所有测试中,LogManager从位于JRE目录中的lib / logging.properties中的属性文件中读取其配置。 这时,该文件的内容如下:

handlers=java.util.logging.ConsoleHandler myapp2.handlers=java.util.logging.ConsoleHandler myapp2.MyApp2.handlers=java.util.logging.ConsoleHandler .level=OFF java.util.logging.ConsoleHandler.level=ALL java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n myapp2.level=WARNING myapp2.MyApp2.level=INFO 

java程序的代码是:

 package myapp2; import java.io.IOException; import java.util.logging.LogManager; import java.util.logging.Logger; public class MyApp2 { private static final Logger LOGGER = Logger.getLogger(MyApp2.class.getPackage().getName()); private static final Logger LOGGER1 = Logger.getLogger(MyApp2.class.getName()); /** * @param args the command line arguments */ public static void main(String[] args) { LOGGER.severe("severe at MyApp2"); LOGGER.warning("warning at MyApp2"); LOGGER.info("info at MyApp2"); LOGGER1.severe("severe1 at MyApp2"); LOGGER1.warning("warning1 at MyApp2"); LOGGER1.info("info1 at MyApp2"); LOGGER1.setLevel(null); LOGGER1.setUseParentHandlers(false); LOGGER1.severe("severe2 at MyApp2"); LOGGER1.warning("warning2 at MyApp2"); LOGGER1.info("info2 at MyApp2"); try { LogManager.getLogManager().readConfiguration(); } catch (IOException ex) { System.out.println("I/O Exception found"); } catch (SecurityException ex) { System.out.println("Error SecurityException found"); } LOGGER.severe("severe3 at MyApp2"); LOGGER1.severe("severe4 at MyApp2"); } } 

如果我们执行它而没有围绕readConfiguration()的try-catch,按预期工作,输出如下:

 SEVERE: severe at MyApp2 [dc. maig 08 14:45:38 CEST 2013] SEVERE: severe at MyApp2 [dc. maig 08 14:45:38 CEST 2013] WARNING: warning at MyApp2 [dc. maig 08 14:45:38 CEST 2013] WARNING: warning at MyApp2 [dc. maig 08 14:45:38 CEST 2013] SEVERE: severe1 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] SEVERE: severe1 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] SEVERE: severe1 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] WARNING: warning1 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] WARNING: warning1 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] WARNING: warning1 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] INFO: info1 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] INFO: info1 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] INFO: info1 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] SEVERE: severe2 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] WARNING: warning2 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] SEVERE: severe3 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] SEVERE: severe3 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] SEVERE: severe4 at MyApp2 [dc. maig 08 14:45:38 CEST 2013] 

但是,如果我们使用try-catch执行,则输出为:

 SEVERE: severe at MyApp2 [dc. maig 08 14:46:51 CEST 2013] SEVERE: severe at MyApp2 [dc. maig 08 14:46:51 CEST 2013] WARNING: warning at MyApp2 [dc. maig 08 14:46:51 CEST 2013] WARNING: warning at MyApp2 [dc. maig 08 14:46:51 CEST 2013] SEVERE: severe1 at MyApp2 [dc. maig 08 14:46:51 CEST 2013] SEVERE: severe1 at MyApp2 [dc. maig 08 14:46:51 CEST 2013] SEVERE: severe1 at MyApp2 [dc. maig 08 14:46:51 CEST 2013] WARNING: warning1 at MyApp2 [dc. maig 08 14:46:51 CEST 2013] WARNING: warning1 at MyApp2 [dc. maig 08 14:46:51 CEST 2013] WARNING: warning1 at MyApp2 [dc. maig 08 14:46:51 CEST 2013] INFO: info1 at MyApp2 [dc. maig 08 14:46:51 CEST 2013] INFO: info1 at MyApp2 [dc. maig 08 14:46:51 CEST 2013] INFO: info1 at MyApp2 [dc. maig 08 14:46:51 CEST 2013] SEVERE: severe2 at MyApp2 [dc. maig 08 14:46:51 CEST 2013] WARNING: warning2 at MyApp2 [dc. maig 08 14:46:51 CEST 2013] SEVERE: severe3 at MyApp2 [dc. maig 08 14:46:51 CEST 2013] 

读取readConfiguration()方法的API时,应该重新初始化日志记录属性并从先前命名的文件重新读取日志记录配置。 如果是这样,为什么strict3只显示一次(由于程序中存在两个LOGGER并且转发行为,我希望显示两次)并且缺少strict4(我希望显示一次)? 愿任何人帮我解决这个问题吗?

我使用readConfiguration方法遇到了更多错误。 它不是你期望它做的。 我创建了一个小unit testing来说明这一点:

 package com.demo; import java.io.IOException; import java.io.InputStream; import java.util.logging.LogManager; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author dhubau */ public class LogTest { private static final Logger logger = LoggerFactory.getLogger(LogTest.class.getCanonicalName()); @Test public void testLogs() throws IOException { logger.trace("BEFORE"); logger.debug("BEFORE"); logger.info("BEFORE"); logger.warn("BEFORE"); logger.error("BEFORE"); InputStream is = LogTest.class.getResourceAsStream("/logging.properties"); LogManager.getLogManager().readConfiguration(is); logger.trace("AFTER"); logger.debug("AFTER"); logger.info("AFTER"); logger.warn("AFTER"); logger.error("AFTER"); } } 

启动unit testing时,将读取默认的logging.properties(将所有内容默认为INFO日志记录)。 我记录每个级别一次,然后读入我自己的logging.properties文件:

 # Specify the handlers to create in the root logger handlers = java.util.logging.ConsoleHandler # Set the default logging level for the root logger .level = INFO # Do not use the root handlers com.demo.useParentHandlers = false # DEMO log handlers com.demo.handlers = java.util.logging.ConsoleHandler # DEMO log level com.demo.level = ALL # Set the default logging level for new ConsoleHandler instances java.util.logging.ConsoleHandler.level = ALL 

之后我得到以下输出:

 May 24, 2013 11:27:29 AM com.demo.LogTest testLogs INFO: BEFORE May 24, 2013 11:27:29 AM com.demo.LogTest testLogs WARNING: BEFORE May 24, 2013 11:27:29 AM com.demo.LogTest testLogs SEVERE: BEFORE May 24, 2013 11:27:29 AM com.demo.LogTest testLogs INFO: AFTER May 24, 2013 11:27:29 AM com.demo.LogTest testLogs WARNING: AFTER May 24, 2013 11:27:29 AM com.demo.LogTest testLogs SEVERE: AFTER 

所以你看,com.demo包没有记录TRACE或DEBUG级别。 当我将以下参数传递给unit testing时:

java.util.logging.config.file = C:\ TEMP \ logging.properties

它给了我以下输出:

 May 24, 2013 11:27:29 AM com.demo.LogTest testLogs FINEST: BEFORE May 24, 2013 11:27:29 AM com.demo.LogTest testLogs FINE: BEFORE May 24, 2013 11:27:29 AM com.demo.LogTest testLogs INFO: BEFORE May 24, 2013 11:27:29 AM com.demo.LogTest testLogs WARNING: BEFORE May 24, 2013 11:27:29 AM com.demo.LogTest testLogs SEVERE: BEFORE May 24, 2013 11:27:29 AM com.demo.LogTest testLogs INFO: AFTER May 24, 2013 11:27:29 AM com.demo.LogTest testLogs WARNING: AFTER May 24, 2013 11:27:29 AM com.demo.LogTest testLogs SEVERE: AFTER 

据我所知, readConfiguration方法只有一个缺陷,我通过JDK代码调试发现,因为我也错过了日志消息。 它们不会加载每个记录器处理程序。 如果不使用per-logger处理程序, readConfiguration方法应该可以正常工作。 readConfiguration首先重置所有记录器,删除处理程序等,然后忘记检查每个记录器处理程序。 因此,您错过了日志消息。 您最初有三个处理程序,根处理程序,程序包上的处理程序以及类上的处理程序。 然后你关闭了类上的useParentHandlers并调用了readConfiguration 。 现在 – 因为useParentHandlers没有重置,可能它应该 – 并且你的per-logger处理程序不再设置,strict3只通过root处理程序记录一次,而且就不会记录strict4,因为useParentHandlers是false,所以没有回退到root处理程序完成。

迪特描述的“更多的错误”是顺便说一句。 完全相同的问题。

如果您更喜欢使用日志记录配置文件,也可以轻松解决该问题。 只需在调用readConfiguration后遍历已存在的记录器,并为每个记录器调用LogManager.loadLoggerHandlers。 在Groovy中,这将是

 def logManager = LogManager.logManager logManager.loggerNames.each { logManager.loadLoggerHandlers logManager.getLogger(it), it, "${it}.handlers" } 

我测试了它,它的工作原理。 对于Java,您必须使用reflection,因为它是一个私有方法。 应该是这样的

 LogManager logManager = LogManager.getLogManager(); Method loadLoggerHandlers = LogManager.class.getDeclaredMethod("loadLoggerHandlers", Logger.class, String.class, String.class); loadLoggerHandlers.setAccessible(true); for (String loggerName : logManager.getLoggerNames()) { loadLoggerHandlers.invoke(logManager, logManager.getLogger(loggerName), loggerName, loggerName + ".handlers"); } 

在JDK 9中, JDK-8033661:readConfiguration没有干净地重新初始化已经标记为已解决的日志记录系统 。 LogManager.readConfiguration已重新指定为仅在LogManager初始化期间设置记录器。

要在初始化后更新记录器,应该使用LogManager.updateConfiguration(java.util.function.Function)方法。 可以在HandlersOnComplexResetUpdate.java测试中找到示例重映射函数:

 static void updateConfigurationWith(Properties propertyFile, boolean append) { try { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); propertyFile.store(bytes, propertyFile.getProperty("test.name")); ByteArrayInputStream bais = new ByteArrayInputStream(bytes.toByteArray()); Function> remapper = append ? (x) -> ((o, n) -> n == null ? o : n) : (x) -> ((o, n) -> n); LogManager.getLogManager().updateConfiguration(bais, remapper); } catch (IOException ex) { throw new RuntimeException(ex); } }