At runtime I often create/modify log4j Loggers, Appenders, Levels, Layouts, and time to time need to reset everything back to defaults.
Log4j
Jan Zyka's solution pointed me in the right direction, but I'm using an XML configuration file rather than a properties files. Here is the code that worked for me:
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");
I get a properly formatted log4j log entry with "abc123" as the log message.
According to the documentation for the doConfigure
method:
Read configuration from a file. The existing configuration is not cleared nor reset. If you require a different behavior, then call resetConfiguration
method before calling doConfigure.
So I belive that calling LogManager.resetConfiguration()
and recall PropertyConfigurator.configure()
with the same files as on startup will do what you want.
The resetConfiguration()
method is documented in Hierarchy class.
This question is related to skiphoppy's question which I answered earlier today. The bounty question he added to this one here calls for a more tricky solution than Jan Zyka's:
Because the default initialisation is a hard-coded static block executed only once during class-loading of LogManager
, you need AOP (aspect-oriented programming), more specifically AspectJ in order to intercept the static initialiser. I explained how to do that in my answer to skiphoppy's other question.
Okay, so now we can intercept the static initialisation and trick LogManager into telling us the URL, but in order to re-execute the whole code block we need another trick called a worker object pattern. Here is the sample code, the explanation follows:
Sample application class:
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 intercepting static initialisation of 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;
}
}
How it works:
It is beyond the scope of this answer to explain the general concept of AOP, so I assume you know it or are going to read something in order to understand it.
Log4jAspect
intercepts LogManager
's static initialisation in an around()
advice.proceed()
call (i.e. the execution of the static initialisation) is packaged inside a worker object implemented by an anonymous Runnable
instance. This effectively wraps the call into an object with a run()
method which can be issued at will. (Aha, here is our trick! In more dynamic languages like Scala you would call this a lexical scope.)Runnable
instance to another class's public static member so it becomes accessible outside the aspect.run()
method.So far, so good, now the LogManager
class has been loaded and initialised properly, just as if no aspect did exist. But now look at Log4jDemo.main
:
run()
method.If you use command line argument -Dlog4j.debug=true
, you will see something like this:
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! As you can see, the default initialisation has indeed been executed twice. The log output proves it. For example you see Using URL [file:/(...)]
twice in the log.
Conclusion:
While this is not a particularly nice way to re-issue log4j default initialisation compared to the more desireable situation that it not be hard-coded but exposed to the user via an API call, the facts are as they are and we need this trick. I do doubt that it should be necessary to re-run the full default initialisation block in any given situation, but because the question was asked, I wanted to answer it precisely and not by suggesting a workaround. Enjoy!