package freenet.support; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.regex.PatternSyntaxException; import freenet.support.FileLoggerHook.IntervalParseException; import freenet.support.LoggerHook.InvalidThresholdException; import freenet.support.io.Closer; /** * @author Iakin */ public abstract class Logger { public final static class OSThread { private static boolean getPIDEnabled = false; private static boolean getPPIDEnabled = false; private static boolean logToFileEnabled = false; private static LogLevel logToFileVerbosity = LogLevel.DEBUG; private static boolean logToStdOutEnabled = false; private static boolean procSelfStatEnabled = false; /** * Get the thread's process ID or return -1 if it's unavailable for some reason */ public synchronized static int getPID(Object o) { if (!getPIDEnabled) { return -1; } return getPIDFromProcSelfStat(o); } /** * Get the thread's parent process ID or return -1 if it's unavailable for some reason */ public synchronized static int getPPID(Object o) { if (!getPPIDEnabled) { return -1; } return getPPIDFromProcSelfStat(o); } /** * Get a specified field from /proc/self/stat or return null if * it's unavailable for some reason. */ public synchronized static String getFieldFromProcSelfStat(int fieldNumber, Object o) { String readLine = null; if (!procSelfStatEnabled) { return null; } // read /proc/self/stat and parse for the specified field InputStream is = null; BufferedReader br = null; File procFile = new File("/proc/self/stat"); if (procFile.exists()) { try { is = new FileInputStream(procFile); br = new BufferedReader(new InputStreamReader(is, "ISO-8859-1" /* ASCII */)); } catch (FileNotFoundException e1) { logStatic(o, "'/proc/self/stat' not found", logToFileVerbosity); procSelfStatEnabled = false; br = null; } catch (UnsupportedEncodingException e) { // Impossible. throw new Error(e); } if (null != br) { try { readLine = br.readLine(); } catch (IOException e) { error(o, "Caught IOException in br.readLine() of OSThread.getFieldFromProcSelfStat()", e); readLine = null; } finally { Closer.close(br); } if (null != readLine) { try { String[] procFields = readLine.trim().split(" "); if (4 <= procFields.length) { return procFields[ fieldNumber ]; } } catch (PatternSyntaxException e) { error(o, "Caught PatternSyntaxException in readLine.trim().split(\" \") of OSThread.getFieldFromProcSelfStat() while parsing '"+readLine+"'", e); } } } else { Closer.close(is); } } return null; } /** * Get the thread's process ID using the /proc/self/stat method or * return -1 if it's unavailable for some reason. This is an ugly * hack required by Java to get the OS process ID of a thread on * Linux without using JNI. */ public synchronized static int getPIDFromProcSelfStat(Object o) { int pid = -1; if (!getPIDEnabled) { return -1; } if (!procSelfStatEnabled) { return -1; } String pidString = getFieldFromProcSelfStat(0, o); if (null == pidString) { return -1; } try { pid = Integer.parseInt( pidString.trim() ); } catch (NumberFormatException e) { error(o, "Caught NumberFormatException in Integer.parseInt() of OSThread.getPIDFromProcSelfStat() while parsing '"+pidString+"'", e); } return pid; } /** * Get the thread's parent process ID using the /proc/self/stat * method or return -1 if it's unavailable for some reason. This * is ugly hack required by Java to get the OS parent process ID of * a thread on Linux without using JNI. */ public synchronized static int getPPIDFromProcSelfStat(Object o) { int ppid = -1; if (!getPPIDEnabled) { return -1; } if (!procSelfStatEnabled) { return -1; } String ppidString = getFieldFromProcSelfStat(3, o); if (null == ppidString) { return -1; } try { ppid = Integer.parseInt( ppidString.trim() ); } catch (NumberFormatException e) { error(o, "Caught NumberFormatException in Integer.parseInt() of OSThread.getPPIDFromProcSelfStat() while parsing '"+ppidString+"'", e); } return ppid; } /** * Log the thread's process ID or return -1 if it's unavailable for some reason */ public synchronized static int logPID(Object o) { if (!getPIDEnabled) { return -1; } int pid = getPID(o); String msg; if (-1 != pid) { msg = "This thread's OS PID is " + pid; } else { msg = "This thread's OS PID could not be determined"; } if (logToStdOutEnabled) { System.out.println(msg + ": " + o); } if (logToFileEnabled) { logStatic(o, msg, logToFileVerbosity); } return pid; } /** * Log the thread's process ID or return -1 if it's unavailable for some reason */ public synchronized static int logPPID(Object o) { if (!getPPIDEnabled) { return -1; } int ppid = getPPID(o); String msg; if (-1 != ppid) { msg = "This thread's OS PPID is " + ppid; } else { msg = "This thread's OS PPID could not be determined"; } if (logToStdOutEnabled) { System.out.println(msg + ": " + o); } if (logToFileEnabled) { logStatic(o, msg, logToFileVerbosity); } return ppid; } } /** These indicate the verbosity levels for calls to log() * */ public enum LogLevel { MINIMAL, /** For being used to enable ALL logging. Do not use as log level for actual log messages. */ DEBUG, MINOR, NORMAL, WARNING, ERROR, NONE; /** For being used to disable logging completely. Do not use as log level for actual log messages. */ public boolean matchesThreshold(LogLevel threshold) { return this.ordinal() >= threshold.ordinal(); } @Deprecated public static LogLevel fromOrdinal(int ordinal) { for(LogLevel level : LogLevel.values()) { if(level.ordinal() == ordinal) return level; } throw new RuntimeException("Invalid ordinal: " + ordinal); } } @Deprecated public static final int ERROR = LogLevel.ERROR.ordinal(); @Deprecated public static final int WARNING = LogLevel.WARNING.ordinal(); @Deprecated public static final int NORMAL = LogLevel.NORMAL.ordinal(); @Deprecated public static final int MINOR = LogLevel.MINOR.ordinal(); @Deprecated public static final int DEBUG = LogLevel.DEBUG.ordinal(); @Deprecated public static final int INTERNAL = LogLevel.NONE.ordinal(); /** * Single global LoggerHook. */ static Logger logger = new VoidLogger(); /** Log to standard output. */ public synchronized static FileLoggerHook setupStdoutLogging(LogLevel level, String detail) throws InvalidThresholdException { setupChain(); logger.setThreshold(level); logger.setDetailedThresholds(detail); FileLoggerHook fh; try { fh = new FileLoggerHook(System.out, "d (c, t, p): m", "MMM dd, yyyy HH:mm:ss:SSS", level.name()); } catch (IntervalParseException e) { // Impossible throw new Error(e); } if (detail != null) fh.setDetailedThresholds(detail); ((LoggerHookChain) logger).addHook(fh); fh.start(); return fh; } @Deprecated public synchronized static FileLoggerHook setupStdoutLogging(int level, String detail) throws InvalidThresholdException { return setupStdoutLogging(LogLevel.fromOrdinal(level), detail); } /** Create a LoggerHookChain and set the global logger to be it. */ public synchronized static void setupChain() { logger = new LoggerHookChain(); } // These methods log messages at various priorities using the global logger. public synchronized static void debug(Class<?> c, String s) { logger.log(c, s, LogLevel.DEBUG); } public synchronized static void debug(Class<?> c, String s, Throwable t) { logger.log(c, s, t, LogLevel.DEBUG); } public synchronized static void debug(Object o, String s) { logger.log(o, s, LogLevel.DEBUG); } public synchronized static void debug(Object o, String s, Throwable t) { logger.log(o, s, t, LogLevel.DEBUG); } public synchronized static void error(Class<?> c, String s) { logger.log(c, s, LogLevel.ERROR); } public synchronized static void error(Class<?> c, String s, Throwable t) { logger.log(c, s, t, LogLevel.ERROR); } public synchronized static void error(Object o, String s) { logger.log(o, s, LogLevel.ERROR); } public synchronized static void error(Object o, String s, Throwable e) { logger.log(o, s, e, LogLevel.ERROR); } public synchronized static void minor(Class<?> c, String s) { logger.log(c, s, LogLevel.MINOR); } public synchronized static void minor(Object o, String s) { logger.log(o, s, LogLevel.MINOR); } public synchronized static void minor(Object o, String s, Throwable t) { logger.log(o, s, t, LogLevel.MINOR); } public synchronized static void minor(Class<?> class1, String string, Throwable t) { logger.log(class1, string, t, LogLevel.MINOR); } public synchronized static void normal(Object o, String s) { logger.log(o, s, LogLevel.NORMAL); } public synchronized static void normal(Object o, String s, Throwable t) { logger.log(o, s, t, LogLevel.NORMAL); } public synchronized static void normal(Class<?> c, String s) { logger.log(c, s, LogLevel.NORMAL); } public synchronized static void normal(Class<?> c, String s, Throwable t) { logger.log(c, s, t, LogLevel.NORMAL); } public synchronized static void warning(Class<?> c, String s) { logger.log(c, s, LogLevel.WARNING); } public synchronized static void warning(Class<?> c, String s, Throwable t) { logger.log(c, s, t, LogLevel.WARNING); } public synchronized static void warning(Object o, String s) { logger.log(o, s, LogLevel.WARNING); } public synchronized static void warning(Object o, String s, Throwable e) { logger.log(o, s, e, LogLevel.WARNING); } public synchronized static void logStatic(Object o, String s, LogLevel prio) { logger.log(o, s, prio); } public synchronized static void logStatic(Object o, String s, Throwable e, LogLevel prio) { logger.log(o, s, e, prio); } @Deprecated public synchronized static void logStatic(Object o, String s, int prio) { logStatic(o, s, LogLevel.fromOrdinal(prio)); } /** * Log a message * * @param o * The object where this message was generated. * @param source * The class where this message was generated. * @param message * A clear and verbose message describing the event * @param e * Logs this exception with the message. * @param priority * The priority of the mesage, one of LogLevel.ERROR, * LogLevel.NORMAL, LogLevel.MINOR, or LogLevel.DEBUG. */ public abstract void log( Object o, Class<?> source, String message, Throwable e, LogLevel priority); @Deprecated public void log( Object o, Class<?> source, String message, Throwable e, int priority) { log(o, source, message, e, LogLevel.fromOrdinal(priority)); } /** * Log a message. * @param source The source object where this message was generated * @param message A clear and verbose message describing the event * @param priority The priority of the mesage, one of LogLevel.ERROR, * LogLevel.NORMAL, LogLevel.MINOR, or LogLevel.DEBUG. **/ public abstract void log(Object source, String message, LogLevel priority); @Deprecated public void log(Object source, String message, int priority) { log(source, message, LogLevel.fromOrdinal(priority)); } /** * Log a message with an exception. * @param o The source object where this message was generated. * @param message A clear and verbose message describing the event. * @param e Logs this exception with the message. * @param priority The priority of the mesage, one of LogLevel.ERROR, * LogLevel.NORMAL, LogLevel.MINOR, or LogLevel.DEBUG. * @see #log(Object o, String message, int priority) */ public abstract void log(Object o, String message, Throwable e, LogLevel priority); @Deprecated public void log(Object o, String message, Throwable e, int priority) { log(o, message, e, LogLevel.fromOrdinal(priority)); } /** * Log a message from static code. * @param c The class where this message was generated. * @param message A clear and verbose message describing the event * @param priority The priority of the mesage, one of LogLevel.ERROR, * LogLevel.NORMAL, LogLevel.MINOR, or LogLevel.DEBUG. */ public abstract void log(Class<?> c, String message, LogLevel priority); @Deprecated public void log(Class<?> c, String message, int priority) { log(c, message, LogLevel.fromOrdinal(priority)); } /** * Log a message from static code. * @param c The class where this message was generated. * @param message A clear and verbose message describing the event * @param e Logs this exception with the message. * @param priority The priority of the mesage, one of LogLevel.ERROR, * LogLevel.NORMAL, LogLevel.MINOR, or LogLevel.DEBUG. */ public abstract void log(Class<?> c, String message, Throwable e, LogLevel priority); @Deprecated public void log(Class<?> c, String message, Throwable e, int priority) { log(c, message, e, LogLevel.fromOrdinal(priority)); } /** Should this specific Logger object log a message concerning the * given class with the given priority. */ public abstract boolean instanceShouldLog(LogLevel priority, Class<?> c); @Deprecated public boolean instanceShouldLog(int priority, Class<?> c) { return instanceShouldLog(LogLevel.fromOrdinal(priority), c); } /** Would a message concerning an object of the given class be logged * at the given priority by the global logger? */ public static boolean shouldLog(LogLevel priority, Class<?> c) { return logger.instanceShouldLog(priority, c); } @Deprecated public static boolean shouldLog(int priority, Class<?> c) { return shouldLog(LogLevel.fromOrdinal(priority), c); } /** Would a message concerning the given object be logged * at the given priority by the global logger? */ public static boolean shouldLog(LogLevel priority, Object o) { return shouldLog(priority, o.getClass()); } @Deprecated public static boolean shouldLog(int priority, Object o) { return shouldLog(LogLevel.fromOrdinal(priority), o); } /** Should this specific Logger object log a message concerning the * given object with the given priority. */ public abstract boolean instanceShouldLog(LogLevel prio, Object o); @Deprecated public boolean instanceShouldLog(int prio, Object o) { return instanceShouldLog(LogLevel.fromOrdinal(prio), o); } /** * Changes the priority threshold. * * @param thresh * The new threshhold */ public abstract void setThreshold(LogLevel thresh); @Deprecated public void setThreshold(int thresh) { setThreshold(LogLevel.fromOrdinal(thresh)); } /** * Changes the priority threshold. * * @param symbolicThreshold * The new threshhold, must be one of ERROR,NORMAL etc.. * @throws InvalidThresholdException */ public abstract void setThreshold(String symbolicThreshold) throws InvalidThresholdException; /** * @return The currently used logging threshold */ public abstract LogLevel getThresholdNew(); @Deprecated public int getThreshold() { return getThresholdNew().ordinal(); } /** Set the detailed list of thresholds. This allows to specify that * we are interested in debug level logging for one class but are only * interested in errors for another, which can be very useful for * debugging. Format is classname:threshold,classname:threshold... */ public abstract void setDetailedThresholds(String details) throws InvalidThresholdException; /** * Register a LogThresholdCallback; this callback will be called after registration, * and whether the overall threshold or the detailed thresholds change in a way that * would affect whether messages for the class registering will be logged. */ public static void registerLogThresholdCallback(LogThresholdCallback ltc) { logger.instanceRegisterLogThresholdCallback(ltc); } /** Register a log threshold callback with this specific logger, not with * the global logger. */ public abstract void instanceRegisterLogThresholdCallback(LogThresholdCallback ltc); /** * Register a LogThresholdCallback; this callback will be called after registration */ public static void unregisterLogThresholdCallback(LogThresholdCallback ltc) { logger.instanceUnregisterLogThresholdCallback(ltc); } /** Unregister a log threshold callback with this specific logger. */ public abstract void instanceUnregisterLogThresholdCallback(LogThresholdCallback ltc); /** Register a class so that its logMINOR and logDEBUG fields (the * latter is optional) are automatically updated whenever they should be * i.e. whenever shouldLog(classname, MINOR) or ,DEBUG would change. */ public static void registerClass(final Class<?> clazz) { LogThresholdCallback ltc = new LogThresholdCallback() { WeakReference<Class<?>> ref = new WeakReference<Class<?>> (clazz); @Override public void shouldUpdate() { Class<?> clazz = ref.get(); if (clazz == null) { // class unloaded unregisterLogThresholdCallback(this); return; } boolean done = false; try { Field logMINOR_Field = clazz.getDeclaredField("logMINOR"); if ((logMINOR_Field.getModifiers() & Modifier.STATIC) != 0) { logMINOR_Field.setAccessible(true); logMINOR_Field.set(null, shouldLog(LogLevel.MINOR, clazz)); } done = true; } catch (SecurityException e) { } catch (NoSuchFieldException e) { } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } try { Field logDEBUG_Field = clazz.getDeclaredField("logDEBUG"); if ((logDEBUG_Field.getModifiers() & Modifier.STATIC) != 0) { logDEBUG_Field.setAccessible(true); logDEBUG_Field.set(null, shouldLog(LogLevel.DEBUG, clazz)); } done = true; } catch (SecurityException e) { } catch (NoSuchFieldException e) { } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } if (!done) Logger.error(this, "No log level field for " + clazz); } }; registerLogThresholdCallback(ltc); } /** * Report a fatal error and exit. * @param cause the object or class involved * @param retcode the return code * @param message the reason why */ public static void fatal(Object cause, int retcode, String message) { error(cause, message); System.exit(retcode); } /** Add a logger hook to the global logger hook chain. Messages which * are not filtered out by the global logger hook chain's thresholds * will be passed to this logger. */ public synchronized static void globalAddHook(LoggerHook logger2) { if (logger instanceof VoidLogger) setupChain(); ((LoggerHookChain)logger).addHook(logger2); } /** Set the global threshold. The global logger will ignore messages * less significant than the given threshold. */ public synchronized static void globalSetThreshold(LogLevel i) { logger.setThreshold(i); } @Deprecated public synchronized static void globalSetThreshold(int i) { logger.setThreshold(LogLevel.fromOrdinal(i)); } /** What is the current global logging threshold? */ public synchronized static LogLevel globalGetThresholdNew() { return logger.getThresholdNew(); } @Deprecated public synchronized static int globalGetThreshold() { return globalGetThresholdNew().ordinal(); } /** Remove a logger hook from the global logger hook chain. */ public synchronized static void globalRemoveHook(LoggerHook hook) { if (logger instanceof LoggerHookChain) { ((LoggerHookChain)logger).removeHook(hook); } else { System.err.println("Cannot remove hook: "+hook+" global logger is "+logger); } } /** If no logger hooks are registered, destroy the global logger hook * chain by replacing it with a VoidLogger, which simply ignores * everything logged. */ public synchronized static void destroyChainIfEmpty() { if (logger instanceof VoidLogger) return; if ((logger instanceof LoggerHookChain) && (((LoggerHookChain)logger).getHooks().length == 0)) { logger = new VoidLogger(); } } /** Get the global logger hook chain, creating it if necessary. */ public synchronized static LoggerHookChain getChain() { if (logger instanceof LoggerHookChain) { return (LoggerHookChain) logger; } else { Logger oldLogger = logger; if (!(oldLogger instanceof VoidLogger)) { if (!(oldLogger instanceof LoggerHook)) { throw new IllegalStateException("The old logger is not a VoidLogger and is not a LoggerHook either!"); } } setupChain(); if (!(oldLogger instanceof VoidLogger)) { ((LoggerHookChain)logger).addHook((LoggerHook)oldLogger); } return (LoggerHookChain) logger; } } }