    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}