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