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}