How to Re-Execute Log4j “Default Initialization Pr

2019-03-10 22:56发布

问题:

At runtime I often create/modify log4j Loggers, Appenders, Levels, Layouts, and time to time need to reset everything back to defaults.

Log4j system has well defined Default Initialization Procedure that is executed when log4j classes are loaded into memory. Is there any way to re-execute the entire Procedure programmatically later at runtime?

I found several resetConfiguration() methods in log4j documentation, but not sure if any of them will do what the Default Initialization Procedure does:

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

Any other suggestions on resetting log4j configurations are more then welcome! Thank you.

回答1:

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.



回答2:

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.
  • Within the advice, the 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.)
  • After wrapping the static initialisation, we assign the Runnable instance to another class's public static member so it becomes accessible outside the aspect.
  • Still inside the advice, we proceed with the static initialisation by calling the worker object's 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:

  • We initialise the logger and log some events.
  • We wait for 2 seconds (just enough time to check the console output for what has happened so far).
  • We continue and re-issue the default initialisation by calling once more the worker object's 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!



回答3:

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.