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 if (currentMessage.lastIndexOf("\n")>=0) 114 { 115 // Are we longer than just the line separator? 116 if (currentMessage.length()>1) 117 { 118 // Trim the line separator 119 currentMessage.setLength(currentMessage.length()-1); 120 log.log(Level.INFO, currentMessage.toString()); 121 } 122 currentMessage.setLength(0); 123 } 124 } 125 } 126 } 127 /** 128 * Our special logger for logging issues to. We copy various assets from the 129 * Minecraft logger to achieve a similar appearance. 130 */ 131 public static FMLRelaunchLog log = new FMLRelaunchLog(); 132 133 static File minecraftHome; 134 private static boolean configured; 135 136 private static Thread consoleLogThread; 137 138 private static PrintStream errCache; 139 private Logger myLog; 140 141 private static FileHandler fileHandler; 142 143 private static FMLLogFormatter formatter; 144 145 private FMLRelaunchLog() 146 { 147 } 148 /** 149 * Configure the FML logger 150 */ 151 private static void configureLogging() 152 { 153 LogManager.getLogManager().reset(); 154 Logger globalLogger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); 155 globalLogger.setLevel(Level.OFF); 156 157 log.myLog = Logger.getLogger("ForgeModLoader"); 158 159 Logger stdOut = Logger.getLogger("STDOUT"); 160 stdOut.setParent(log.myLog); 161 Logger stdErr = Logger.getLogger("STDERR"); 162 stdErr.setParent(log.myLog); 163 log.myLog.setLevel(Level.ALL); 164 log.myLog.setUseParentHandlers(false); 165 consoleLogThread = new Thread(new ConsoleLogThread()); 166 consoleLogThread.setDaemon(true); 167 consoleLogThread.start(); 168 formatter = new FMLLogFormatter(); 169 try 170 { 171 File logPath = new File(minecraftHome, FMLRelauncher.logFileNamePattern); 172 fileHandler = new FileHandler(logPath.getPath(), 0, 3) 173 { 174 public synchronized void close() throws SecurityException { 175 // We don't want this handler to reset 176 } 177 }; 178 } 179 catch (Exception e) 180 { 181 } 182 183 resetLoggingHandlers(); 184 185 // Set system out to a log stream 186 errCache = System.err; 187 188 System.setOut(new PrintStream(new LoggingOutStream(stdOut), true)); 189 System.setErr(new PrintStream(new LoggingOutStream(stdErr), true)); 190 191 configured = true; 192 } 193 private static void resetLoggingHandlers() 194 { 195 ConsoleLogThread.wrappedHandler.setLevel(Level.parse(System.getProperty("fml.log.level","INFO"))); 196 // Console handler captures the normal stderr before it gets replaced 197 log.myLog.addHandler(new ConsoleLogWrapper()); 198 ConsoleLogThread.wrappedHandler.setFormatter(formatter); 199 fileHandler.setLevel(Level.ALL); 200 fileHandler.setFormatter(formatter); 201 log.myLog.addHandler(fileHandler); 202 } 203 204 public static void loadLogConfiguration(File logConfigFile) 205 { 206 if (logConfigFile!=null && logConfigFile.exists() && logConfigFile.canRead()) 207 { 208 try 209 { 210 LogManager.getLogManager().readConfiguration(new FileInputStream(logConfigFile)); 211 resetLoggingHandlers(); 212 } 213 catch (Exception e) 214 { 215 log(Level.SEVERE, e, "Error reading logging configuration file %s", logConfigFile.getName()); 216 } 217 } 218 } 219 public static void log(String logChannel, Level level, String format, Object... data) 220 { 221 makeLog(logChannel); 222 Logger.getLogger(logChannel).log(level, String.format(format, data)); 223 } 224 225 public static void log(Level level, String format, Object... data) 226 { 227 if (!configured) 228 { 229 configureLogging(); 230 } 231 log.myLog.log(level, String.format(format, data)); 232 } 233 234 public static void log(String logChannel, Level level, Throwable ex, String format, Object... data) 235 { 236 makeLog(logChannel); 237 Logger.getLogger(logChannel).log(level, String.format(format, data), ex); 238 } 239 240 public static void log(Level level, Throwable ex, String format, Object... data) 241 { 242 if (!configured) 243 { 244 configureLogging(); 245 } 246 log.myLog.log(level, String.format(format, data), ex); 247 } 248 249 public static void severe(String format, Object... data) 250 { 251 log(Level.SEVERE, format, data); 252 } 253 254 public static void warning(String format, Object... data) 255 { 256 log(Level.WARNING, format, data); 257 } 258 259 public static void info(String format, Object... data) 260 { 261 log(Level.INFO, format, data); 262 } 263 264 public static void fine(String format, Object... data) 265 { 266 log(Level.FINE, format, data); 267 } 268 269 public static void finer(String format, Object... data) 270 { 271 log(Level.FINER, format, data); 272 } 273 274 public static void finest(String format, Object... data) 275 { 276 log(Level.FINEST, format, data); 277 } 278 public Logger getLogger() 279 { 280 return myLog; 281 } 282 public static void makeLog(String logChannel) 283 { 284 Logger l = Logger.getLogger(logChannel); 285 l.setParent(log.myLog); 286 } 287}