在运行时,我经常创建/修改 log4j 记录器,附加器,级别,布局以及需要将所有内容重置为默认值的时间。
Log4j 系统已明确定义 Default Initialization Procedure ,当 log4j 类加载到内存时执行。有没有办法在以后的运行时以编程方式重新执行整个过程?
我在 log4j 文档中找到了几种resetConfiguration()
方法,但不确定是否有任何方法会执行 Default Initialization Procedure 所做的操作:
BasicConfigurator.resetConfiguration();
Hierarchy.resetConfiguration();
LogManager.resetConfiguration();
欢迎任何有关重置 log4j 配置的其他建议!谢谢。
答案 0 :(得分:11)
根据doConfigure
方法的documentation:
从文件中读取配置。 现有配置未清除或重置。如果您需要不同的行为,请在调用doConfigure 之前调用resetConfiguration
方法。
所以我相信,调用LogManager.resetConfiguration()
并使用与启动时相同的文件调用PropertyConfigurator.configure()
将会执行您想要的操作。
Hierarchy类记录了resetConfiguration()
方法。
答案 1 :(得分:7)
这个问题与我今天早些时候回答的skiphoppy's question有关。他在这里加入的赏金问题需要一个比Jan Zyka更棘手的解决方案:
因为默认初始化是在LogManager
的类加载期间仅执行一次的硬编码静态块,所以需要AOP(面向方面编程),更具体地说是AspectJ以拦截静态初始化器。我在my answer中解释了如何做到这一点,以跳过其他问题。
好的,现在我们可以拦截静态初始化并欺骗LogManager告诉我们URL,但是为了重新执行整个代码块,我们需要另一个叫做 worker object pattern 的技巧。以下是示例代码,解释如下:
示例应用程序类:
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的一般概念超出了这个答案的范围,所以我假设你知道它或者要阅读某些内容以便理解它。
Log4jAspect
在LogManager
建议中拦截around()
的静态初始化。proceed()
调用(即执行静态初始化)打包在由匿名Runnable
实例实现的工作对象中。这有效地将调用包装到具有run()
方法的对象中,该方法可以随意发布。 (啊哈,这是我们的伎俩!在像Scala这样的更动态的语言中,你会称之为词法范围。)Runnable
实例分配给另一个类的公共静态成员,以便它可以在方面之外访问。run()
方法继续进行静态初始化。到目前为止,非常好,现在LogManager
类已经被正确加载和初始化,就好像没有方面确实存在一样。但现在看看Log4jDemo.main
:
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!如您所见,默认初始化确实已执行两次。日志输出证明了这一点。例如,您在日志中看到Using URL [file:/(...)]
两次。
<强>结论:强>
虽然这不是一个特别好的方法来重新发布log4j默认初始化,而不是更加可取的情况,它不是硬编码但是通过API调用暴露给用户,事实就像它们一样,我们需要这招。我怀疑在任何给定的情况下都应该重新运行完整的默认初始化块,但是因为问了这个问题,我想准确地回答它,而不是建议一个解决方法。享受!
答案 2 :(得分:0)
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”作为日志消息。