001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.services;
019
020import io.stallion.Context;
021import io.stallion.exceptions.ConfigException;
022import io.stallion.monitoring.HealthTracker;
023import io.stallion.settings.Settings;
024import org.apache.commons.io.FilenameUtils;
025
026import java.io.Console;
027import java.io.File;
028import java.io.IOException;
029import java.text.MessageFormat;
030import java.util.logging.*;
031
032import static io.stallion.utils.Literals.*;
033
034public class Log {
035    //private static Logger logger = LogManager.getLogManager().getLogger("stallion");
036    private static Logger logger;
037    private static Handler handler;
038    private static Handler fileHandler;
039    private static boolean alwaysIncludeLineNumber = true;
040
041    static {
042
043
044
045        logger = Logger.getLogger("io.stallion");
046        logger.config("");
047
048        //TODO: configure the logger from settingsp
049        logger.setUseParentHandlers(false);
050        handler = new ConsoleHandler();
051        //Level defaultLevel = Level.INFO;
052        //handler.setLevel(defaultLevel);
053        //logger.setLevel(defaultLevel);
054
055        handler.setFormatter(new LogFormatter());
056        logger.addHandler(handler);
057        // Hack to fix a bug where ScssStylesheet will reset the global logger
058        System.setProperty("java.util.logging.config.file", "noop");
059    }
060
061    /**
062     * Disable logging to the console
063     */
064    public static void disableConsoleHandler() {
065        Log.info("Silencing console logger.");
066        logger.removeHandler(handler);
067        handler.close();
068    }
069
070    /**
071     * Start logging to the file defined by "logFile" in settings files
072     */
073    public static void enableFileLogger() {
074        logger.removeHandler(handler);
075        handler.close();
076        String logPath = Settings.instance().getLogFile();
077        if (!new File(logPath).getParentFile().isDirectory()) {
078            new File(logPath).getParentFile().mkdirs();
079        }
080        try {
081            fileHandler = new FileHandler(logPath, 50000000, 7, true);
082        } catch (IOException e) {
083            throw new ConfigException("Invalid log file path: " + logPath);
084        }
085        fileHandler.setFormatter(new LogFormatter());
086        handler.setLevel(logger.getLevel());
087        logger.addHandler(fileHandler);
088        System.out.println("----->  Logging to file " + logPath + " at level " + logger.getLevel() + " ---->");
089    }
090
091
092
093    public static void setLogLevel(Level level) {
094        handler.setLevel(level);
095        logger.setLevel(level);
096        if (fileHandler != null) {
097            fileHandler.setLevel(level);
098        }
099    }
100
101    public static Level getLogLevel() {
102
103        if (logger == null) {
104            return Level.OFF;
105        } else if (logger.getLevel() == null) {
106
107            return Level.INFO;
108        } else {
109            return logger.getLevel();
110        }
111    }
112
113
114    public static void setLogLevelFromSettings() {
115        if (!empty(Context.getSettings().getLogLevel())) {
116            Level level = Level.parse(Context.getSettings().getLogLevel());
117            handler.setLevel(level);
118            logger.setLevel(level);
119            if (fileHandler != null) {
120                fileHandler.setLevel(level);
121            }
122        }
123        if (!empty(Context.getSettings().getPackageLogLevels())) {
124            LogFilter filter = new LogFilter(logger.getLevel(), Context.getSettings().getPackageLogLevels());
125            logger.setFilter(filter);
126        }
127    }
128
129    public static void fine(String message, Object ... args) {
130        if (getLogLevel().intValue() > Level.FINE.intValue()) {
131            return;
132        }
133        //System.out.println(message);
134        if (alwaysIncludeLineNumber) {
135            Throwable t = new Throwable();
136            t.getStackTrace()[2].toString();
137            String clz = t.getStackTrace()[2].getClassName().replace("io.stallion.", "");
138            String method = t.getStackTrace()[2].getMethodName();
139            logger.logp(Level.FINE, clz, method, message, args);
140        } else {
141            logger.logp(Level.FINE, "", "", message, args);
142        }
143
144    }
145
146    public static void finer(String message, Object ... args) {
147        if (getLogLevel().intValue() > Level.FINER.intValue()) {
148            return;
149        }
150
151        //System.out.println(message);
152
153        Throwable t = new Throwable();
154        t.getStackTrace()[2].toString();
155        String clz = t.getStackTrace()[2].getClassName().replace("io.stallion.", "");
156        String method = t.getStackTrace()[2].getMethodName();
157        logger.logp(Level.FINER, clz, method, message, args);
158
159    }
160
161    public static void finest(String message, Object ... args) {
162        if (getLogLevel().intValue() > Level.FINEST.intValue()) {
163            return;
164        }
165        //System.out.println(message);
166        Throwable t = new Throwable();
167        t.getStackTrace()[2].toString();
168        String clz = t.getStackTrace()[2].getClassName().replace("io.stallion.", "");
169        String method = t.getStackTrace()[2].getMethodName();
170        logger.logp(Level.FINEST, clz, method, message, args);
171
172    }
173
174    public static void info(String message, Object ... args) {
175        if (getLogLevel().intValue() > Level.INFO.intValue()) {
176            return;
177        }
178        // Info statements don't include the class and line number, since that kills performance
179        //System.out.println(message);
180        if (alwaysIncludeLineNumber) {
181            Throwable t = new Throwable();
182            StackTraceElement stackTraceElement = t.getStackTrace()[1];
183            String clz = stackTraceElement.getClassName().replace("io.stallion.", "");
184            String method = stackTraceElement.getMethodName() + ":" + t.getStackTrace()[1].getLineNumber();
185            logger.logp(Level.INFO, clz, method, message, args);
186        } else {
187            logger.logp(Level.INFO, "", "", message, args);
188        }
189
190    }
191
192    public static void warning(String message, Object ... args) {
193        if (getLogLevel().intValue() > Level.WARNING.intValue()) {
194            return;
195        }
196        Throwable t = new Throwable();
197        StackTraceElement stackTraceElement = t.getStackTrace()[1];
198        String clz = stackTraceElement.getClassName().replace("io.stallion.", "");
199        String method = stackTraceElement.getMethodName() + ":" + t.getStackTrace()[1].getLineNumber();
200        logger.logp(Level.WARNING, clz, method, message, args);
201
202    }
203
204
205
206    public static void warn(String message, Object ... args) {
207        if (getLogLevel().intValue() > Level.WARNING.intValue()) {
208            return;
209        }
210        Throwable t = new Throwable();
211        StackTraceElement stackTraceElement = t.getStackTrace()[1];
212        String clz = stackTraceElement.getClassName().replace("io.stallion.", "");
213        String method = stackTraceElement.getMethodName() + ":" + t.getStackTrace()[1].getLineNumber();
214        logger.logp(Level.WARNING, clz, method, message, args);
215
216    }
217
218    /**
219     * Logs a message, setting the class, method and source line number using the stack frame index passed in.
220     * This is useful if you are wrapping a logging class, and want to include the line number from where
221     * the wrapper method is called.
222     *
223     * @param frame - the stack frame to use. Use '2' to log to one level above the caller
224     * @param level
225     * @param message
226     * @param args
227     */
228    public static void logForFrame(int frame, Level level, String message, Object ...args) {
229        if (getLogLevel().intValue() > level.intValue()) {
230            return;
231        }
232        Throwable t = new Throwable();
233        StackTraceElement stackTraceElement = t.getStackTrace()[frame];
234        String clz = stackTraceElement.getClassName().replace("io.stallion.", "");
235        String method = stackTraceElement.getMethodName() + ":" + t.getStackTrace()[frame].getLineNumber();
236        logger.logp(level, clz, method, message, args);
237    }
238
239
240    public static void exception(Throwable ex, String message, Object ... args) {
241        HealthTracker.instance().logException(ex);
242        if (getLogLevel().intValue() > Level.SEVERE.intValue()) {
243            return;
244        }
245        if (args.length > 0) {
246            message = MessageFormat.format(message, args);
247        }
248        // TODO: WTF -- figure out how the hell the handler got removed;
249        if (logger.getHandlers().length == 0) {
250            logger.addHandler(handler);
251        }
252        Throwable t = new Throwable();
253        StackTraceElement stackTraceElement = t.getStackTrace()[1];
254        String clz = stackTraceElement.getClassName().replace("io.stallion.", "");
255        String method = stackTraceElement.getMethodName() + ":" + t.getStackTrace()[1].getLineNumber();
256        logger.logp(Level.SEVERE, clz, method, message, ex);
257    }
258
259    public static boolean isAlwaysIncludeLineNumber() {
260        return alwaysIncludeLineNumber;
261    }
262
263    public static void setAlwaysIncludeLineNumber(boolean alwaysIncludeLineNumber) {
264        Log.alwaysIncludeLineNumber = alwaysIncludeLineNumber;
265    }
266}