001package apps.util; 002 003import java.awt.GraphicsEnvironment; 004 005import apps.SystemConsole; 006 007import java.io.*; 008import java.nio.file.Files; 009import java.util.*; 010 011import javax.annotation.CheckForNull; 012import javax.annotation.Nonnull; 013 014import jmri.util.FileUtil; 015import jmri.util.exceptionhandler.UncaughtExceptionHandler; 016import jmri.util.swing.JmriJOptionPane; 017 018import org.apache.logging.log4j.Level; 019import org.apache.logging.log4j.Logger; 020import org.apache.logging.log4j.LogManager; 021import org.apache.logging.log4j.core.Appender; 022import org.apache.logging.log4j.core.appender.FileAppender; 023import org.apache.logging.log4j.core.appender.RollingFileAppender; 024import org.apache.logging.log4j.core.config.Configurator; 025 026/** 027 * Common utility methods for working with Log4J. 028 * <p> 029 * Two system properties influence how logging is configured in JMRI: 030 * <dl> 031 * <dt>jmri.log</dt><dd>The logging control file. If this file is not an 032 * absolute path, this file is searched for in the following order:<ol> 033 * <li>JMRI settings directory</li> 034 * <li>JMRI installation (program) directory</li> 035 * </ol> 036 * If this property is not specified, the logging control file 037 * <i>default_lcf.xml</i> is used, following the above search order to find it. 038 * </dd> 039 * <dt>jmri.log.path</dt><dd>The directory for storing logs. If not specified, 040 * logs are stored in the JMRI preferences directory.</dd> 041 * </dl> 042 * <p> 043 * See also jmri.util.TestingLoggerConfiguration in the Test code for 044 * Tests Logging Setup. 045 * @author Bob Jacobsen Copyright 2009, 2010 046 * @author Randall Wood Copyright 2014, 2020 047 */ 048public class Log4JUtil { 049 050 public static final String DEFAULT_LCF_NAME = "default_lcf.xml"; 051 public static final String SYS_PROP_LCF_LOCATION = "jmri.log"; 052 public static final String SYS_PROP_LOG_PATH = "jmri.log.path"; 053 054 private static final String LOG_HEADER = "****** JMRI log *******"; 055 056 /** 057 * Initialize logging from a default control file. 058 * <p> 059 * Primary functions: 060 * <ul> 061 * <li>Initialize the JMRI System Console. 062 * <li>Set up the slf4j j.u.logging to log4J bridge. 063 * <li>Start log4j. 064 * <li>Initialize a default exception handler. 065 * </ul> 066 * 067 */ 068 static public void initLogging() { 069 initLogging(System.getProperty(SYS_PROP_LCF_LOCATION, DEFAULT_LCF_NAME)); 070 } 071 072 /** 073 * Initialize logging, specifying a control file. 074 * <p> 075 * Generally, only used for unit testing. Much better to use allow this 076 * class to find the control file using a set of conventions. 077 * 078 * @param controlfile the logging control file 079 */ 080 static public void initLogging(@Nonnull String controlfile) { 081 initLog4J(controlfile); 082 } 083 084 /** 085 * Initialize Log4J. 086 * <p> 087 * Use the logging control file specified in the <i>jmri.log</i> property 088 * or, if none, the default_lcf.xml file. If the file is absolute and cannot be 089 * found, look for the file first in the settings directory and then in the 090 * installation directory. 091 * 092 * @param logFile the logging control file 093 * @see jmri.util.FileUtil#getPreferencesPath() 094 * @see jmri.util.FileUtil#getProgramPath() 095 */ 096 static void initLog4J(@Nonnull String logFile) { 097 Logger logger = LogManager.getLogger(); 098 Map<String, Appender> appenderMap = ((org.apache.logging.log4j.core.Logger) logger).getAppenders(); 099 if ( appenderMap.size() > 1 ) { 100 log.debug("initLog4J already initialized!"); 101 return; 102 } 103 // Initialise JMRI System Console 104 // Need to do this before initialising log4j so that the new 105 // stdout and stderr streams are set up and usable by the ConsoleAppender 106 if (!GraphicsEnvironment.isHeadless()) { 107 SystemConsole.getInstance(); 108 } 109 110 // initialize the java.util.logging to log4j bridge 111 initializeJavaUtilLogging(); 112 113 // initialize log4j - from logging control file (lcf) only 114 String loggingControlFileLocation = getLoggingConfig(logFile); 115 if ( loggingControlFileLocation != null ) { 116 configureLogging(loggingControlFileLocation); 117 } else { 118 Configurator.reconfigure(); 119 Configurator.setRootLevel(Level.INFO); 120 log.error("Unable to load Configuration {}", logFile); 121 if (!GraphicsEnvironment.isHeadless()) { 122 JmriJOptionPane.showMessageDialog(null, 123 "Could not locate Logging Configuration file " + logFile, 124 "Could not Locate Logging Configuration File", 125 JmriJOptionPane.ERROR_MESSAGE); 126 } 127 } 128 // install default exception handler so uncaught exceptions are logged, not printed 129 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler()); 130 } 131 132 @CheckForNull 133 public static String getLoggingConfig(@Nonnull String logFileLocation) { 134 if (new File(logFileLocation).isAbsolute() && new File(logFileLocation).canRead()) { 135 return logFileLocation; 136 } else if ( new File(FileUtil.getPreferencesPath() + logFileLocation).canRead()) { 137 return FileUtil.getPreferencesPath() + logFileLocation; 138 } else if ( new File(FileUtil.getProgramPath() + logFileLocation).canRead()) { 139 return FileUtil.getProgramPath() + logFileLocation; 140 } else { 141 return null; 142 } 143 } 144 145 static void initializeJavaUtilLogging() { 146 // Optionally remove existing handlers attached to j.u.l root logger 147 org.slf4j.bridge.SLF4JBridgeHandler.removeHandlersForRootLogger(); // (since SLF4J 1.6.5) 148 149 // add SLF4JBridgeHandler to j.u.l's root logger, should be done once during 150 // the initialization phase of your application 151 org.slf4j.bridge.SLF4JBridgeHandler.install(); 152 } 153 154 @SuppressWarnings("unchecked") 155 @Nonnull 156 static public String startupInfo(@Nonnull String program) { 157 log.info(LOG_HEADER); 158 Logger logger = LogManager.getLogger(); 159 Map<String, Appender > appenderMap = ((org.apache.logging.log4j.core.Logger) logger).getAppenders(); 160 appenderMap.forEach((key, a) -> { 161 if (a instanceof RollingFileAppender) { 162 RollingFileAppender rf = (RollingFileAppender)a; 163 String fileName = rf.getFileName(); 164 if ( fileName.equals(rf.getFilePattern()) ) { 165 log.info("This log is stored in file: {}", fileName); 166 } else { 167 log.info("This log is appended to file: {}", fileName); 168 } 169 } else if (a instanceof FileAppender) { 170 log.info("This log is stored in file: {}", ((FileAppender) a).getFileName()); 171 } 172 }); 173 return (program + " version " + jmri.Version.name() 174 + " starts under Java " + System.getProperty("java.version", "<unknown>") 175 + " on " + System.getProperty("os.name", "<unknown>") 176 + " " + System.getProperty("os.arch", "<unknown>") 177 + " v" + System.getProperty("os.version", "<unknown>") 178 + " at " + (new java.util.Date())); 179 } 180 181 /** 182 * Configure Log4J using the specified properties file. 183 * <p> 184 * This method sets the system property <i>jmri.log.path</i> to the JMRI 185 * preferences directory if not specified. 186 * 187 * @see jmri.util.FileUtil#getPreferencesPath() 188 */ 189 static private void configureLogging(@Nonnull String configFile) { 190 // System.out.println("Log4JUtil configureLogging " + configFile); 191 192 // set the log4j config file location programatically 193 // so that JUL adapter is enabled first 194 // and Jython / JavaScript use the same LoggerContext 195 System.setProperty("log4j2.configurationFile", configFile); 196 197 // ensure the logging directory exists 198 // if it's not writable, the console will get the error from log4j, so 199 // we don't need to explictly test for that here, just make sure the 200 // directory is created if need be. 201 if (System.getProperty(SYS_PROP_LOG_PATH) == null ) { 202 System.setProperty(SYS_PROP_LOG_PATH, FileUtil.getPreferencesPath() + "log" + File.separator); 203 } 204 File logDir = new File(System.getProperty(SYS_PROP_LOG_PATH)); 205 String createLogErr = null; 206 if (!logDir.exists()) { 207 try { 208 Files.createDirectories(logDir.toPath()); 209 } catch ( IOException ex ) { 210 createLogErr = "Could not create directory for log files, " + ex.getMessage(); 211 } 212 } 213 try { 214 Configurator.initialize(null, configFile); 215 log.debug("Logging initialised with {}", configFile); 216 } catch ( Exception ex ) { 217 Configurator.reconfigure(); 218 Configurator.setRootLevel(Level.INFO); 219 if (!GraphicsEnvironment.isHeadless()) { 220 JmriJOptionPane.showMessageDialog(null, 221 "Could not Initialise Logging " + ex.getMessage(), 222 configFile, 223 JmriJOptionPane.ERROR_MESSAGE); 224 } 225 } 226 if (createLogErr!=null) { // wait until Logging init 227 log.error("Could not create directory for log files at {} {}", 228 System.getProperty(SYS_PROP_LOG_PATH), createLogErr); 229 } 230 } 231 232 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Log4JUtil.class); 233 234}