如何重新执行Log4j“默认初始化过程”?

在运行时,我经常创建/修改log4j Loggers,Appenders,Levels,Layouts以及需要将所有内容重置为默认值的时间。

Log4j系统具有明确定义的默认初始化过程 ,该过程在将log4j类加载到内存时执行。 有没有办法在以后的运行时以编程方式重新执行整个过程?

我在log4j文档中找到了几个resetConfiguration()方法,但不确定它们中的任何一个是否会执行默认初始化过程

  • BasicConfigurator.resetConfiguration();
  • Hierarchy.resetConfiguration();
  • LogManager.resetConfiguration();

关于重置log4j配置的任何其他建议都欢迎! 谢谢。

根据doConfigure方法的文档 :

从文件中读取配置。 现有配置不会被清除也不会被重置 。 如果需要不同的行为, 请在调用doConfigure之前调用resetConfiguration方法

因此我相信调用LogManager.resetConfiguration()并使用与启动时相同的文件调用PropertyConfigurator.configure()将执行您想要的操作。

Hierarchy类中记录了resetConfiguration()方法。

这个问题与我今天早些时候回答的skiphoppy问题有关。 他在这里添加的赏金问题需要一个比Jan Zyka更棘手的解决方案:

因为默认初始化是在LogManager类加载期间仅执行一次的硬编码静态块,所以需要AOP(面向方面​​编程),更具体地说是AspectJ以拦截静态初始化器。 我在回答 skiphoppy的另一个问题时解释了如何做到这一点。

好的,现在我们可以拦截静态初始化并欺骗LogManager告诉我们URL,但是为了重新执行整个代码块,我们需要另一种叫做工作对象模式的技巧。 以下是示例代码,说明如下:

示例应用程序类:

 import org.apache.log4j.BasicConfigurator; import org.apache.log4j.Logger; public class Log4jDemo { public static Runnable log4jDefaultInitCmd; private static Logger logger = Logger.getLogger("scrum-master.de"); public static void main(String[] args) throws InterruptedException { BasicConfigurator.configure(); logger.info("Hello world!"); logger.info("Now sleeping for 2 sec..."); Thread.sleep(2000); logger.info("I am awake again!"); if (log4jDefaultInitCmd != null) { logger.info("Re-running log4j default initialisation"); log4jDefaultInitCmd.run(); } logger.info("Done"); } } 

Aspect拦截LogManager静态初始化:

 import org.apache.log4j.LogManager; public aspect Log4jAspect { Object around() : staticinitialization(LogManager) { System.out.println("log4j static initialisation"); Log4jDemo.log4jDefaultInitCmd = new Runnable() { @Override public void run() { proceed(); } }; Log4jDemo.log4jDefaultInitCmd.run(); return null; } } 

怎么运行的:

解释AOP的一般概念超出了这个答案的范围,所以我假设你知道它或者要读一些东西以便理解它。

  • Log4jAspectaround()建议中拦截LogManager的静态初始化。
  • 在通知中, proceed()调用(即静态初始化的执行)被打包在由匿名Runnable实例实现的worker对象中。 这有效地使用run()方法将调用包装到一个对象中,该方法可以随意发出。 (啊哈,这是我们的伎俩!在像Scala这样的更动态的语言中,你会称之为词法范围。)
  • 在包装静态初始化之后,我们将Runnable实例分配给另一个类的公共静态成员,以便在方面之外可以访问它。
  • 仍然在建议中,我们通过调用worker对象的run()方法继续静态初始化。

到目前为止,非常好,现在LogManager类已经正确加载和初始化,就好像没有方面确实存在一样。 但现在看看Log4jDemo.main

  • 我们初始化记录器并记录一些事件。
  • 我们等待2秒钟(足够的时间检查控制台输出到目前为止发生了什么)。
  • 我们通过再次调用worker对象的run()方法继续并重新发出默认初始化

如果使用命令行参数-Dlog4j.debug=true ,您将看到如下内容:

 log4j static initialisation log4j: Trying to find [log4j.xml] using context classloader sun.misc.Launcher$AppClassLoader@17182c1. log4j: Trying to find [log4j.xml] using sun.misc.Launcher$AppClassLoader@17182c1 class loader. log4j: Trying to find [log4j.xml] using ClassLoader.getSystemResource(). log4j: Trying to find [log4j.properties] using context classloader sun.misc.Launcher$AppClassLoader@17182c1. log4j: Using URL [file:/C:/Dokumente%20und%20Einstellungen/Robin/Eigene%20Dateien/java-src/dummy2/bin/log4j.properties] for automatic log4j configuration. log4j: Reading configuration from URL file:/C:/Dokumente%20und%20Einstellungen/Robin/Eigene%20Dateien/java-src/dummy2/bin/log4j.properties log4j: Parsing for [root] with value=[debug, stdout]. log4j: Level token is [debug]. log4j: Category root set to DEBUG log4j: Parsing appender named "stdout". log4j: Parsing layout options for "stdout". log4j: Setting property [conversionPattern] to [%d{ABSOLUTE} %5p %c{1}:%L - %m%n]. log4j: End of parsing for "stdout". log4j: Setting property [target] to [System.out]. log4j: Parsed "stdout" options. log4j: Finished configuring. 12:41:22,647 INFO de:13 - Hello world! 0 [main] INFO scrum-master.de - Hello world! 12:41:22,663 INFO de:14 - Now sleeping for 2 sec... 16 [main] INFO scrum-master.de - Now sleeping for 2 sec... 12:41:24,663 INFO de:16 - I am awake again! 2016 [main] INFO scrum-master.de - I am awake again! 12:41:24,663 INFO de:18 - Re-running log4j default initialisation 2016 [main] INFO scrum-master.de - Re-running log4j default initialisation log4j: Trying to find [log4j.xml] using context classloader sun.misc.Launcher$AppClassLoader@17182c1. log4j: Trying to find [log4j.xml] using sun.misc.Launcher$AppClassLoader@17182c1 class loader. log4j: Trying to find [log4j.xml] using ClassLoader.getSystemResource(). log4j: Trying to find [log4j.properties] using context classloader sun.misc.Launcher$AppClassLoader@17182c1. log4j: Using URL [file:/C:/Dokumente%20und%20Einstellungen/Robin/Eigene%20Dateien/java-src/dummy2/bin/log4j.properties] for automatic log4j configuration. log4j: Reading configuration from URL file:/C:/Dokumente%20und%20Einstellungen/Robin/Eigene%20Dateien/java-src/dummy2/bin/log4j.properties log4j: Parsing for [root] with value=[debug, stdout]. log4j: Level token is [debug]. log4j: Category root set to DEBUG log4j: Parsing appender named "stdout". log4j: Parsing layout options for "stdout". log4j: Setting property [conversionPattern] to [%d{ABSOLUTE} %5p %c{1}:%L - %m%n]. log4j: End of parsing for "stdout". log4j: Setting property [target] to [System.out]. log4j: Parsed "stdout" options. log4j: Finished configuring. 12:41:24,663 INFO de:21 - Done 2016 [main] INFO scrum-master.de - Done 

Tadaa! 如您所见,默认初始化确实已执行两次。 日志输出certificate了这一点。 例如,您在日志中看到两次Using URL [file:/(...)]

结论:

虽然这不是一个特别好的方法来重新发布log4j默认初始化,而不是更加可取的情况,它不是硬编码但是通过API调用暴露给用户,事实就是这样,我们需要这个技巧。 我怀疑在任何特定情况下都应该重新运行完整的默认初始化块,但是因为问了这个问题,我想准确地回答它,而不是建议一个解决方法。 请享用!

Jan Zyka的解决方案指出了我正确的方向,但我使用的是XML配置文件而不是属性文件。 这是适合我的代码:

  LogManager.resetConfiguration(); // clear any existing config first LoggerRepository loggerRepository = LogManager.getLoggerRepository(); DOMConfigurator domConfigurator = new DOMConfigurator(); try ( InputStream is = MyClassName.class.getResourceAsStream("/log4j.xml"); ) { domConfigurator.doConfigure(is, loggerRepository); } LOGGER.info("abc123"); 

我得到一个格式正确的log4j日志条目,其中“abc123”作为日志消息。