package jaci.openrio.toast.lib.log; import jaci.openrio.toast.core.ToastBootstrap; import jaci.openrio.toast.lib.util.Pretty; import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Vector; /** * A logging class for the robot. This class prefixes different attributes to the message, such as time, * thread, and logger ID. This makes logs easy to read and debug. * * {@link jaci.openrio.toast.lib.log.LogHandler} can also be used in order to detect when a message is logged, * and do the appropriate action. This allows teams running Custom Driver Station software to view the console * in real time * * @author Jaci */ public class Logger { public static final int ATTR_TIME = 1; public static final int ATTR_THREAD = 2; public static final int ATTR_COLOR = 4; public static final int ATTR_DEFAULT = ATTR_TIME | ATTR_THREAD | ATTR_COLOR; public static final LogLevel INFO = new LogLevel("INFO"); public static final LogLevel WARN = new LogLevel("WARN").setPrintStream(System.err).setColor(Pretty.Colors.RED); public static final LogLevel ERROR = new LogLevel("ERROR").setPrintStream(System.err).setColor(Pretty.Colors.RED); public static final LogLevel SEVERE = new LogLevel("SEVERE").setPrintStream(System.err).setColor(Pretty.Colors.RED); public DateFormat dateFormat = new SimpleDateFormat("dd/MM/yy-hh:mm:ss"); public static Vector<LogHandler> handlers = new Vector<LogHandler>(); int attr; String name; /** * Registers a {@link jaci.openrio.toast.lib.log.LogHandler} with all loggers */ public static void addHandler(LogHandler handler) { handlers.add(handler); } /** * Create a new Logger Object * @param name The name of the logger * @param attributes A Binary Or operation of the attributes to use with the logger. This should be * ATTR_TIME | ATTR_THREAD, or ATTR_DEFAULT by shorthand, unless you want to omit this * data */ public Logger(String name, int attributes) { this.attr = attributes; this.name = name; } /** * Formats the current time data of the system into the [dd/MM/yy-hh:mm:ss] format */ String getTime() { return dateFormat.format(new Date()); } /* LOGGING BEGIN */ /** * Log a new message on the selected printstream with the given method and level. This is where all other * 'log' type methods in this class delegate to. */ private void log(String message, String level, String levelColor, PrintStream ps) { StringBuilder builder = new StringBuilder(); if (!level.equals("raw")) builder.append(getPrefix(level, levelColor)); builder.append(message); String ts = builder.toString(); ps.println(ts); for (LogHandler hand : handlers) hand.onLog(level, message, ts, this); } /** * Get the prefix for all messages on this logger. This adds the DateTime, as well as the * Thread ID and Logger Name if they are enabled in the Logger Attributes */ public String getPrefix(String level, String levelColor) { StringBuilder builder = new StringBuilder(); if ((attr & ATTR_TIME) == ATTR_TIME) builder.append(tag(getTime(), "green", "gray")); builder.append(tag(name, "green", "magenta")); if ((attr & ATTR_THREAD) == ATTR_THREAD) builder.append(tag(Thread.currentThread().getName(), "green", "cyan")); builder.append(tag(level, "green", levelColor)); return builder.toString(); } /** * Return a tag, maybe formatted with color */ public String tag(String inner, String c1, String c2) { boolean color = ((attr & ATTR_COLOR) == ATTR_COLOR) && ToastBootstrap.color; String s = "["; if (color) s += "<" + c1 + ">"; s += inner; if (color) s += "<" + c2 + ">"; s += "] "; if (color) s += "<" + c1 + ">"; return color ? Pretty.format(s) : s; } /** * Format and print a stack trace on the Logger Object. This calls the StackTrace directly, meaning * underlying causes and other factors are included in the log. This is the equivalent of calling * e.printStackTrace(), but redirecting it to the logger object. */ public void exception(Throwable e) { StringWriter writer = new StringWriter(); e.printStackTrace(new PrintWriter(writer)); error(writer.toString()); } /** * Log a message at the specified level */ public void log(String message, LogLevel level) { log(message, level.getName().toUpperCase(), level.getColor().name().toLowerCase(), level.getPrintSteam()); } /** * Log a message without any padding or prefixes. */ public void raw(String message) { log(message, "raw", "none", System.out); } /** * Log an 'INFO' message. This is the default for logging things * that are not errors. * * This goes to System.out */ public void info(String message) { log(message, INFO); } /** * Log a 'WARNING' message. This is for errors that aren't critical * or too big of an issue, or if you hurt the robot's feelings * * This goes to System.err */ public void warn(String message) { log(message, WARN); } /** * Log an 'ERROR' message. This is the default for errors that affect * robot action. * * This goes to System.err */ public void error(String message) { log(message, ERROR); } /** * Log a 'SEVERE' message. This is for errors that deeply affect robot * action and are critical * * This goes to System.err */ public void severe(String message) { log(message, SEVERE); } }