001/* 002 * Forge Mod Loader 003 * Copyright (c) 2012-2013 cpw. 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser Public License v2.1 006 * which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 008 * 009 * Contributors: 010 * cpw - implementation 011 */ 012 013package cpw.mods.fml.relauncher; 014 015import java.io.ByteArrayOutputStream; 016import java.io.File; 017import java.io.FileInputStream; 018import java.io.FileNotFoundException; 019import java.io.IOException; 020import java.io.PrintStream; 021import java.util.concurrent.Executors; 022import java.util.concurrent.LinkedBlockingQueue; 023import java.util.logging.ConsoleHandler; 024import java.util.logging.FileHandler; 025import java.util.logging.Handler; 026import java.util.logging.Level; 027import java.util.logging.LogManager; 028import java.util.logging.LogRecord; 029import java.util.logging.Logger; 030 031public class FMLRelaunchLog 032{ 033 034 private static class ConsoleLogWrapper extends Handler 035 { 036 @Override 037 public void publish(LogRecord record) 038 { 039 boolean currInt = Thread.interrupted(); 040 try 041 { 042 ConsoleLogThread.recordQueue.put(record); 043 } 044 catch (InterruptedException e) 045 { 046 e.printStackTrace(errCache); 047 } 048 if (currInt) 049 { 050 Thread.currentThread().interrupt(); 051 } 052 } 053 054 @Override 055 public void flush() 056 { 057 058 } 059 060 @Override 061 public void close() throws SecurityException 062 { 063 } 064 065 } 066 private static class ConsoleLogThread implements Runnable 067 { 068 static ConsoleHandler wrappedHandler = new ConsoleHandler(); 069 static LinkedBlockingQueue<LogRecord> recordQueue = new LinkedBlockingQueue<LogRecord>(); 070 @Override 071 public void run() 072 { 073 do 074 { 075 LogRecord lr; 076 try 077 { 078 lr = recordQueue.take(); 079 wrappedHandler.publish(lr); 080 } 081 catch (InterruptedException e) 082 { 083 e.printStackTrace(errCache); 084 Thread.interrupted(); 085 // Stupid 086 } 087 } 088 while (true); 089 } 090 } 091 private static class LoggingOutStream extends ByteArrayOutputStream 092 { 093 private Logger log; 094 private StringBuilder currentMessage; 095 096 public LoggingOutStream(Logger log) 097 { 098 this.log = log; 099 this.currentMessage = new StringBuilder(); 100 } 101 102 @Override 103 public void flush() throws IOException 104 { 105 String record; 106 synchronized(FMLRelaunchLog.class) 107 { 108 super.flush(); 109 record = this.toString(); 110 super.reset(); 111 112 currentMessage.append(record.replace(FMLLogFormatter.LINE_SEPARATOR, "\n")); 113 // Are we longer than just the line separator? 114 int lastIdx = -1; 115 int idx = currentMessage.indexOf("\n",lastIdx+1); 116 while (idx > 0) 117 { 118 log.log(Level.INFO, currentMessage.substring(lastIdx+1,idx)); 119 lastIdx = idx; 120 idx = currentMessage.indexOf("\n",lastIdx+1); 121 } 122 if (lastIdx >= 0) 123 { 124 currentMessage.setLength(0); 125 } 126 } 127 } 128 } 129 /** 130 * Our special logger for logging issues to. We copy various assets from the 131 * Minecraft logger to achieve a similar appearance. 132 */ 133 public static FMLRelaunchLog log = new FMLRelaunchLog(); 134 135 static File minecraftHome; 136 private static boolean configured; 137 138 private static Thread consoleLogThread; 139 140 private static PrintStream errCache; 141 private Logger myLog; 142 143 private static FileHandler fileHandler; 144 145 private static FMLLogFormatter formatter; 146 147 private FMLRelaunchLog() 148 { 149 } 150 /** 151 * Configure the FML logger 152 */ 153 private static void configureLogging() 154 { 155 LogManager.getLogManager().reset(); 156 Logger globalLogger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); 157 globalLogger.setLevel(Level.OFF); 158 159 log.myLog = Logger.getLogger("ForgeModLoader"); 160 161 Logger stdOut = Logger.getLogger("STDOUT"); 162 stdOut.setParent(log.myLog); 163 Logger stdErr = Logger.getLogger("STDERR"); 164 stdErr.setParent(log.myLog); 165 log.myLog.setLevel(Level.ALL); 166 log.myLog.setUseParentHandlers(false); 167 consoleLogThread = new Thread(new ConsoleLogThread()); 168 consoleLogThread.setDaemon(true); 169 consoleLogThread.start(); 170 formatter = new FMLLogFormatter(); 171 try 172 { 173 File logPath = new File(minecraftHome, FMLRelauncher.logFileNamePattern); 174 fileHandler = new FileHandler(logPath.getPath(), 0, 3) 175 { 176 public synchronized void close() throws SecurityException { 177 // We don't want this handler to reset 178 } 179 }; 180 } 181 catch (Exception e) 182 { 183 } 184 185 resetLoggingHandlers(); 186 187 // Set system out to a log stream 188 errCache = System.err; 189 190 System.setOut(new PrintStream(new LoggingOutStream(stdOut), true)); 191 System.setErr(new PrintStream(new LoggingOutStream(stdErr), true)); 192 193 configured = true; 194 } 195 private static void resetLoggingHandlers() 196 { 197 ConsoleLogThread.wrappedHandler.setLevel(Level.parse(System.getProperty("fml.log.level","INFO"))); 198 // Console handler captures the normal stderr before it gets replaced 199 log.myLog.addHandler(new ConsoleLogWrapper()); 200 ConsoleLogThread.wrappedHandler.setFormatter(formatter); 201 fileHandler.setLevel(Level.ALL); 202 fileHandler.setFormatter(formatter); 203 log.myLog.addHandler(fileHandler); 204 } 205 206 public static void loadLogConfiguration(File logConfigFile) 207 { 208 if (logConfigFile!=null && logConfigFile.exists() && logConfigFile.canRead()) 209 { 210 try 211 { 212 LogManager.getLogManager().readConfiguration(new FileInputStream(logConfigFile)); 213 resetLoggingHandlers(); 214 } 215 catch (Exception e) 216 { 217 log(Level.SEVERE, e, "Error reading logging configuration file %s", logConfigFile.getName()); 218 } 219 } 220 } 221 public static void log(String logChannel, Level level, String format, Object... data) 222 { 223 makeLog(logChannel); 224 Logger.getLogger(logChannel).log(level, String.format(format, data)); 225 } 226 227 public static void log(Level level, String format, Object... data) 228 { 229 if (!configured) 230 { 231 configureLogging(); 232 } 233 log.myLog.log(level, String.format(format, data)); 234 } 235 236 public static void log(String logChannel, Level level, Throwable ex, String format, Object... data) 237 { 238 makeLog(logChannel); 239 Logger.getLogger(logChannel).log(level, String.format(format, data), ex); 240 } 241 242 public static void log(Level level, Throwable ex, String format, Object... data) 243 { 244 if (!configured) 245 { 246 configureLogging(); 247 } 248 log.myLog.log(level, String.format(format, data), ex); 249 } 250 251 public static void severe(String format, Object... data) 252 { 253 log(Level.SEVERE, format, data); 254 } 255 256 public static void warning(String format, Object... data) 257 { 258 log(Level.WARNING, format, data); 259 } 260 261 public static void info(String format, Object... data) 262 { 263 log(Level.INFO, format, data); 264 } 265 266 public static void fine(String format, Object... data) 267 { 268 log(Level.FINE, format, data); 269 } 270 271 public static void finer(String format, Object... data) 272 { 273 log(Level.FINER, format, data); 274 } 275 276 public static void finest(String format, Object... data) 277 { 278 log(Level.FINEST, format, data); 279 } 280 public Logger getLogger() 281 { 282 return myLog; 283 } 284 public static void makeLog(String logChannel) 285 { 286 Logger l = Logger.getLogger(logChannel); 287 l.setParent(log.myLog); 288 } 289}