Java tutorial
/* * Copyright 2015-2016 Austin Keener & Michael Ritter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.dv8tion.jda.utils; import net.dv8tion.jda.entities.impl.JDAImpl; import org.apache.commons.lang3.exception.ExceptionUtils; import javax.swing.*; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.text.SimpleDateFormat; import java.util.*; public class SimpleLog { /** * The global LOG-level that is used as standard if not overwritten */ public static Level LEVEL = Level.INFO; /** * If this boolean is set to true, if there is no console present, messages are shown as message-dialogs */ public static boolean ENABLE_GUI = false; private static final String FORMAT = "[%time%] [%level%] [%name%]: %text%"; private static final String MSGFORMAT = "%text%"; private static final SimpleDateFormat DFORMAT = new SimpleDateFormat("HH:mm:ss"); private static final Map<String, SimpleLog> LOGS = new HashMap<>(); private static final Set<LogListener> listeners = new HashSet<>(); /** * Will get the LOG with the given LOG-name or create one if it didn't exist * * @param name the name of the LOG * @return SimpleLog with given LOG-name */ public static SimpleLog getLog(String name) { synchronized (LOGS) { if (!LOGS.containsKey(name.toLowerCase())) { LOGS.put(name.toLowerCase(), new SimpleLog(name)); } } return LOGS.get(name.toLowerCase()); } private static PrintStream origStd = null; private static PrintStream origErr = null; private static FileOutputStream stdOut = null; private static FileOutputStream errOut = null; private static Map<Level, Set<File>> fileLogs = new HashMap<>(); /** * Will duplicate the output-streams to the specified Files. * This will catch everything that is sent to sout and serr (even stuff not logged via SimpleLog). * * @param std * The file to use for System.out logging, or null to not LOG System.out to a file * @param err * The file to use for System.err logging, or null to not LOG System.err to a file * @throws IOException * If an IO error is encountered while dealing with the file. Most likely * to be caused by a lack of permissions when creating the log folders or files. */ @SuppressWarnings("ResultOfMethodCallIgnored") public static void addFileLogs(File std, File err) throws IOException { if (std != null) { if (origStd == null) origStd = System.out; if (!std.getAbsoluteFile().getParentFile().exists()) { std.getAbsoluteFile().getParentFile().mkdirs(); } if (!std.exists()) { std.createNewFile(); } FileOutputStream fOut = new FileOutputStream(std, true); System.setOut(new PrintStream(new OutputStream() { @Override public void write(int b) throws IOException { origStd.write(b); fOut.write(b); } })); if (stdOut != null) stdOut.close(); stdOut = fOut; } else if (origStd != null) { System.setOut(origStd); stdOut.close(); origStd = null; } if (err != null) { if (origErr == null) origErr = System.err; if (!err.getAbsoluteFile().getParentFile().exists()) { err.getAbsoluteFile().getParentFile().mkdirs(); } if (!err.exists()) { err.createNewFile(); } FileOutputStream fOut = new FileOutputStream(err, true); System.setErr(new PrintStream(new OutputStream() { @Override public void write(int b) throws IOException { origErr.write(b); fOut.write(b); } })); if (errOut != null) errOut.close(); errOut = fOut; } else if (origErr != null) { System.setErr(origErr); errOut.close(); origErr = null; } } /** * Sets up a File to log all messages that are not visible via sout and serr and meet a given log-level criteria. * All logs that would not be printed and are above given logLevel are printed to that File. * If you want to log Logs printed to sout and serr to file(s), use {@link #addFileLogs(File, File)} instead. * * @param logLevel * The log-level criteria. Only logs equal or above this level are printed to the File * @param file * The File where the logs should be printed * @throws IOException * If the File can't be canonically resolved (access denied) */ public static void addFileLog(Level logLevel, File file) throws IOException { File canonicalFile = file.getCanonicalFile(); if (!fileLogs.containsKey(logLevel)) { fileLogs.put(logLevel, new HashSet<>()); } fileLogs.get(logLevel).add(canonicalFile); } /** * Removes all File-logs created via {@link #addFileLog(Level, File)} with given Level. * To remove the sout and serr logs, call {@link #addFileLogs(File, File)} with null args. * * @param logLevel * The level for which all fileLogs should be removed */ public static void removeFileLog(Level logLevel) { fileLogs.remove(logLevel); } /** * Removes all File-logs created via {@link #addFileLog(Level, File)} with given File. * To remove the sout and serr logs, call {@link #addFileLogs(File, File)} with null args. * * @param file * The file to remove from all FileLogs (except sout and serr logs) * @throws IOException * If the File can't be canonically resolved (access denied) */ public static void removeFileLog(File file) throws IOException { File canonicalFile = file.getCanonicalFile(); Iterator<Map.Entry<Level, Set<File>>> setIter = fileLogs.entrySet().iterator(); while (setIter.hasNext()) { Map.Entry<Level, Set<File>> set = setIter.next(); Iterator<File> fileIter = set.getValue().iterator(); while (fileIter.hasNext()) { File logFile = fileIter.next(); if (logFile.equals(canonicalFile)) { fileIter.remove(); break; } } if (set.getValue().isEmpty()) { setIter.remove(); } } } private static Set<File> collectFiles(Level level) { Set<File> out = new HashSet<>(); for (Map.Entry<Level, Set<File>> mapEntry : fileLogs.entrySet()) { if (mapEntry.getKey().getPriority() <= level.getPriority()) out.addAll(mapEntry.getValue()); } return out; } private static void logToFiles(String msg, Level level) { Set<File> files = collectFiles(level); for (File file : files) { try { Files.write(file.toPath(), (msg + '\n').getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.APPEND); } catch (IOException e) { JDAImpl.LOG.fatal("Could not write log to logFile..."); JDAImpl.LOG.log(e); } } } /** * Adds a custom Listener that receives all logs * @param listener the listener to add */ public static void addListener(LogListener listener) { synchronized (listeners) { listeners.add(listener); } } /** * Removes a custom Listener * @param listener the listener to remove */ public static void removeListener(LogListener listener) { synchronized (listeners) { listeners.remove(listener); } } public final String name; private Level level = null; private SimpleLog(String name) { this.name = name; } /** * Set the LOG-level * All messages with lower LOG-level will not be printed * If this level is set to null, the global Log-level ({@link SimpleLog#LEVEL}) will be used * * @param lev the new LOG-level */ public void setLevel(Level lev) { this.level = lev; } /** * Gets the current logging-level of this Logger. * This might return null, if the global logging-level is used. * * @return the logging-level of this Logger or null */ public Level getLevel() { return level; } /** * Gets the effective logging-level of this Logger. * This considers the global logging-level. * * @return the effective logging-level of this Logger */ public Level getEffectiveLevel() { return level == null ? SimpleLog.LEVEL : level; } /** * Will LOG a message with given LOG-level * * @param level The level of the Log * @param msg The message to LOG */ public void log(Level level, Object msg) { synchronized (listeners) { for (LogListener listener : listeners) { listener.onLog(this, level, msg); } } String format = (ENABLE_GUI && !isConsolePresent()) ? MSGFORMAT : FORMAT; format = format.replace("%time%", DFORMAT.format(new Date())).replace("%level%", level.getTag()) .replace("%name%", name).replace("%text%", String.valueOf(msg)); if (level == Level.OFF || level.getPriority() < ((this.level == null) ? SimpleLog.LEVEL.getPriority() : this.level.getPriority())) { logToFiles(format, level); } else { print(format, level); } } public void log(Throwable ex) { synchronized (listeners) { for (LogListener listener : listeners) { listener.onError(this, ex); } } log(Level.FATAL, "Encountered an exception:"); log(Level.FATAL, ExceptionUtils.getStackTrace(ex)); } /** * Will LOG a message with trace level. * * @param msg the object, which should be logged */ public void trace(Object msg) { log(Level.TRACE, msg); } /** * Will LOG a message with debug level * * @param msg the object, which should be logged */ public void debug(Object msg) { log(Level.DEBUG, msg); } /** * Will LOG a message with info level * * @param msg the object, which should be logged */ public void info(Object msg) { log(Level.INFO, msg); } /** * Will LOG a message with warning level * * @param msg the object, which should be logged */ public void warn(Object msg) { log(Level.WARNING, msg); } /** * Will LOG a message with fatal level * * @param msg the object, which should be logged */ public void fatal(Object msg) { log(Level.FATAL, msg); } /** * prints a message to the console or as message-box. * * @param msg the message, that should be displayed * @param level the LOG level of the message */ private void print(String msg, Level level) { if (ENABLE_GUI && !isConsolePresent()) { if (level.isError()) { JOptionPane.showMessageDialog(null, msg, "An Error occurred!", JOptionPane.ERROR_MESSAGE); } else { JOptionPane.showMessageDialog(null, msg, level.getTag(), JOptionPane.INFORMATION_MESSAGE); } } else { if (level.isError()) { System.err.println(msg); } else { System.out.println(msg); } } } /** * Will return whether the program has a console present, or was launched without * * @return boolean true, if console is present */ public static boolean isConsolePresent() { return System.console() != null; } /** * This interface has to be able to register (via {@link SimpleLog#addListener(LogListener)}) and listen to log-messages. */ public interface LogListener { /** * Called on any incoming log-messages (including stacktraces). * This is also called on log-messages that would normally not print to console due to log-level. * @param log * the log this message was sent to * @param logLevel * the level of the message sent * @param message * the message as object */ void onLog(SimpleLog log, Level logLevel, Object message); /** * Called on any incoming error-message (Throwable). * Note: Throwables are also logged with FATAL level. * This is just a convenience Method to do special Throwable handling. * @param log * the log this error was sent to * @param err * the error as Throwable */ void onError(SimpleLog log, Throwable err); } /** * Enum containing all the LOG-levels */ public enum Level { ALL("Finest", 0, false), TRACE("Trace", 1, false), DEBUG("Debug", 2, false), INFO("Info", 3, false), WARNING("Warning", 4, true), FATAL("Fatal", 5, true), OFF("NO-LOGGING", 6, true); private String msg; private int pri; private boolean isError; Level(String message, int priority, boolean isError) { this.msg = message; this.pri = priority; this.isError = isError; } /** * Returns the Log-Tag (e.g. Fatal) * * @return the logTag */ public String getTag() { return msg; } /** * Returns the numeric priority of this loglevel, with 0 being the lowest * * @return the level-priority */ public int getPriority() { return pri; } /** * Returns whether this LOG-level should be treated like an error or not * * @return boolean true, if this LOG-level is an error-level */ public boolean isError() { return isError; } } }