2013-05-08 65 views
2

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

特别是,当测试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时,应该重新初始化日志记录属性,并从先前命名的文件重新读取日志记录配置。如果是这样,为什么严重3只显示一次(由于程序中存在两个LOGGER和转发行为,我希望显示两次),并且严重缺失4(我希望显示一次)?任何人都可以帮我解决这个问题吗?

回答

2

我用readConfiguration方法遇到了更多的错误。这不是你所期望的。我创建了一个小单元测试来说明这一点:

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

当启动单元测试中,默认logging.properties读取(默认一切信息记录)。我登录各个层面一次,然后在自己的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级别。当我通过下列参数的单元测试:

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 
+0

我同意你的说法,这种方法并不符合你的期望。我没有依赖这种方法来访问我的应用程序中包含日志记录属性的文件,而是转向了一个解决方案,其中包括以编程方式定义我的Logger和与之相关的所有组件。我对此感到更自信和舒适。 – 2013-06-06 07:59:33

3

据我所见,readConfiguration方法只有一个缺陷,我通过JDK代码调试发现,因为我也错过了日志消息。他们不加载每个记录器处理程序。如果你不使用per-logger处理程序,readConfiguration方法应该可以正常工作。 readConfiguration首先重置所有记录器,除其他事物之外的处理器,然后忘记检查每个记录器处理程序。 因为你错过了你的日志消息。你最初有三个处理程序,根处理程序,包的处理程序和类的处理程序。然后关闭你的班级上的useParentHandlers,并调用readConfiguration。现在 - 由于useParentHandlers未被重置,可能它应该 - 而且您的每个日志记录器处理程序不再设置,严重程度3仅通过根处理程序记录一次,严重程度4根本不记录,因为useParentHandlers为false,因此不会回退到根目录处理程序完成。

Dieter描述的“更多的错误”是顺便说一句。完全一样的问题。

如果您更喜欢使用日志记录配置文件,则还可以轻松解决该错误。在调用readConfiguration之后,重复遍历已存在的记录器,并为它们中的每一个调用LogManager.loadLoggerHandlers。在Groovy,这将是

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

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

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

在JDK 9,JDK-8033661: readConfiguration does not cleanly reinitialize the logging system已被标记为已解决。在LogManager初始化期间,LogManager.readConfiguration已被重新指定为只设置记录器。

要初始化后更新记录器,应该使用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<String, BiFunction<String,String,String>> 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); 
    } 
} 
相关问题