001package com.randomnoun.common.log4j2;
002
003import java.util.Properties;
004
005import org.apache.logging.log4j.core.config.Configuration;
006import org.apache.logging.log4j.core.config.Configurator;
007import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
008
009/** Configures log4j for command-line interface programs.
010 * 
011 * <p>This class configured the log4j2 (two!) framework, using log4j1 (one!) properties.
012 * 
013 * <p>It uses a modified Log4j1ConfigurationParser (from the log4j2 framework) to do this, 
014 * which maps some well-known log4j1 appenders to log4j2 appenders, and does a pretty ham-fisted job of everything else.
015 * 
016 * <p>By default, everything's sent to stdout, using the following log4j initialisation properties:
017 * 
018 * <pre>
019log4j.rootCategory=DEBUG, CONSOLE
020log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
021log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
022log4j.appender.CONSOLE.layout.ConversionPattern=%d{ABSOLUTE} %-5p %c - %m %n
023
024log4j.logger.org.springframework=INFO
025
026# log4j.appender.FILE=com.randomnoun.common.log4j.CustomRollingFileAppender
027# log4j.appender.FILE.File=c:\\another.log
028# log4j.appender.FILE.MaxBackupIndex=100
029# log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
030# log4j.appender.FILE.layout.ConversionPattern=%d{dd/MM/yy HH:mm:ss.SSS} %-5p %c - %m %n
031   </pre>
032 * 
033 * <p>with an optional line prefix before the <code>%d{ABSOLUTE}</code> in the ConversionPattern.
034 * 
035 * @see <a href="http://logging.apache.org/log4j/1.2/manual.html">http://logging.apache.org/log4j/1.2/manual.html</a>
036 * @see <a href="http://www.randomnoun.com/wp/2013/01/13/logging/">http://www.randomnoun.com/wp/2013/01/13/logging/</a>
037 * @author knoxg
038 */
039public class Log4j2CliConfiguration {
040    
041        /** Create a new Log4jCli2Configuration instance */
042        public Log4j2CliConfiguration() {
043        }
044        
045        /** Initialise log4j.
046         *
047         * <p>The properties file supplied is in log4j format.
048         * 
049         * @param logFormatPrefix a string prefixed to each log. Useful for program identifiers;
050         *   e.g. "[programName] "
051         * @param override if non-null, additional log4j properties. Any properties contained in 
052         *   this object will override the defaults.
053         * 
054         */
055        public void init(String logFormatPrefix, Properties override) {
056                
057                if (logFormatPrefix==null) { 
058                        logFormatPrefix = ""; 
059                } else {
060                        logFormatPrefix += " ";
061                }
062                
063                Properties props = new Properties();
064                props.put("log4j.rootCategory", "DEBUG, CONSOLE");
065                
066                // log4j1 class names - the Log4j1ConfigurationParser recognises a limited set of these
067                // and converts them into the log4j2 equivalents. 
068                // Unknown appenders are ignored, which is obviously what people want to happen.
069                props.put("log4j.appender.CONSOLE", "org.apache.log4j.ConsoleAppender");
070                props.put("log4j.appender.CONSOLE.layout", "org.apache.log4j.PatternLayout");
071
072                // log4j2 class names - the Log4j1ConfigurationParser doesn't recognise these
073                // props.put("log4j.appender.CONSOLE", "org.apache.logging.log4j.core.appender.ConsoleAppender");
074                // props.put("log4j.appender.CONSOLE.layout", "org.apache.logging.log4j.core.layout.PatternLayout");
075                props.put("log4j.appender.CONSOLE.layout.ConversionPattern", logFormatPrefix + "%d{ABSOLUTE} %-5p %c - %m%n");
076                props.put("log4j.logger.org.springframework", "INFO"); // since Spring is a bit too verbose for my liking at DEBUG level
077                if (override!=null) { 
078                        props.putAll(override);
079                }
080
081                /*
082                // to use the bundled Log41ConfigurationParser, had to do this:
083                try {
084                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
085                        props.store(baos,  null);
086                        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
087                        
088                        // in log4j-1.2.jar 
089                        // Log4j1ConfigurationParser lcp = new Log4j1ConfigurationParser();
090                        
091                        Log4j1ConfigurationParser lcp = new Log4j1ConfigurationParser();
092                        ConfigurationBuilder<?> builder = lcp.buildConfigurationBuilder(bais);
093                } catch (IOException e) {
094                        throw new IllegalStateException("IOException in log4j initiialisation", e);
095                }
096                */
097
098                // our Log4j1ConfigurationParser can take a Properties object instead
099                Log4j1ConfigurationParser lcp = new Log4j1ConfigurationParser();
100                ConfigurationBuilder<?> builder = lcp.buildConfigurationBuilder(props);
101                
102                // this needs to happen before Configurator.initialize()
103                // LogManager.shutdown();
104                Configuration config = builder.build();
105
106                // so this doesn't work, because the (ctx.getState() == LifeCycle.State.INITIALIZED)) condition
107                // in Log4jContextFactory.getContext() prevents the configuration from taking effect 
108                /*LoggerContext ctx; =*/ // Configurator.initialize(config);
109                
110                // so you need to do this,
111                // which is not what https://logging.apache.org/log4j/2.x/manual/customconfig.html 
112                // fucking tells you to do. This took about two hours to figure out.
113                /*LoggerContext ctx; =*/  Configurator.reconfigure(config);
114
115                
116                // incidentally, did you know that every time you create a Logger, it's introspecting the stack for no good reason ?
117                // *slow hand clap*, log4j.
118                
119                
120                
121                // log4j doesn't have ThrowableRenderers, it has ExtendedStackTraceElements instead
122                // which I can't find a working example of anywhere
123                /*
124                String highlightPrefix = props.getProperty("log4j.revisionedThrowableRenderer.highlightPrefix");
125                if (highlightPrefix != null) {
126                        LoggerRepository repo = LogManager.getLoggerRepository();
127                        if (repo instanceof ThrowableRendererSupport) {
128                    // if null, log4j will use a DefaultThrowableRenderer
129                    // ThrowableRenderer renderer = ((ThrowableRendererSupport) repo).getThrowableRenderer();
130                    ((ThrowableRendererSupport) repo).setThrowableRenderer(new RevisionedThrowableRenderer(highlightPrefix));
131                }
132                }
133                
134                PropertyConfigurator.configure(props);
135        */
136        }
137}
138
139