How to Re-Execute Log4j “Default Initialization Procedure”?

前端 未结 3 1498
暖寄归人
暖寄归人 2021-02-02 11:36

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

Log4j

3条回答
  •  伪装坚强ぢ
    2021-02-02 12:12

    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!

提交回复
热议问题