/* * Funambol is a mobile platform developed by Funambol, Inc. * Copyright (C) 2003 - 2007 Funambol, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * "Powered by Funambol" logo. If the display of the logo is not reasonably * feasible for technical reasons, the Appropriate Legal Notices must display * the words "Powered by Funambol". */ package com.funambol.util; import java.io.IOException; import java.util.Date; import java.util.Vector; /** * Generic Log class */ public class Log { //---------------------------------------------------------------- Constants /** * Log level DISABLED: used to speed up applications using logging features */ public static final int DISABLED = -1; /** * Log level ERROR: used to log error messages. */ public static final int ERROR = 0; /** * Log level INFO: used to log information messages. */ public static final int INFO = 1; /** * Log level DEBUG: used to log debug messages. */ public static final int DEBUG = 2; /** * Log level TRACE: used to trace the program execution. */ public static final int TRACE = 3; private static final int PROFILING = -2; //---------------------------------------------------------------- Variables /** * The default appender is the console */ private static Appender out; /** * The default log level is INFO */ private static int level = INFO; /** * Last time stamp used to dump profiling information */ private static long initialTimeStamp = -1; /** * Default log cache size */ private static final int CACHE_SIZE = 1024; /** * This is the log cache size (by default this is CACHE_SIZE) */ private static int cacheSize = CACHE_SIZE; /** * Log cache */ private static Vector cache; /** * Tail pointer in the log cache */ private static int next = 0; /** * Head pointer in the log cache */ private static int first = 0; /** * Controls the context logging feature */ private static boolean contextLogging = false; /** * The client max supported log level. This is only needed for more accurate context * logging behavior and the client filters log statements. */ private static int clientMaxLogLevel = TRACE; private static boolean lockedLogLevel; private static Log instance = null; //------------------------------------------------------------- Constructors /** * This class is static and cannot be intantiated */ private Log(){ } /** * The log can be used via its static methods or as a singleton in case * static access is not allowed (this is the case for example on the * BlackBerry listeners when invoked outside of the running process) */ public static Log getInstance() { if (instance == null) { instance = new Log(); } return instance; } //----------------------------------------------------------- Public methods /** * Initialize log file with a specific appender and log level. Contextual * errors handling is disabled after this call. * * @param object the appender object that write log file * @param level the log level */ public static void initLog(Appender object, int level){ out = object; out.initLogFile(); // Init the caching part cache = new Vector(cacheSize); first = 0; next = 0; contextLogging = false; lockedLogLevel = false; setLogLevel(level); if (level > Log.DISABLED) { writeLogMessage(level, "INITLOG","---------"); } } /** * Initialize log file with a specific appender and log level. * Contextual errors handling is enabled after this call. * * @param object the appender object that write log file * @param level the log level * @param cacheSize the max number of log messages cached before an error is * dumped */ public static void initLog(Appender object, int level, int cacheSize) { Log.cacheSize = cacheSize; initLog(object, level); contextLogging = true; } /** * Ititialize log file * @param object the appender object that write log file */ public static void initLog(Appender object){ initLog(object, INFO); } /** * Return a reference to the current appender */ public static Appender getAppender() { return out; } /** * Enabled/disable the context logging feature. When this feature is on, any * call to Log.error will trigger the dump of the error context. */ public static void enableContextLogging(boolean contextLogging) { Log.contextLogging = contextLogging; } /** * Allow clients to specify their maximum log level. By default this value * is set to TRACE. */ public static void setClientMaxLogLevel(int clientMaxLogLevel) { Log.clientMaxLogLevel = clientMaxLogLevel; } /** * Delete log file * */ public static void deleteLog() { out.deleteLogFile(); } /** * Accessor method to define log level * the method will be ignorated in Log level is locked * @param newlevel log level to be set */ public static void setLogLevel(int newlevel) { if(!lockedLogLevel){ level = newlevel; if (out != null) { out.setLogLevel(level); } } } /** * Accessor method to lock defined log level * @param level log level to be lock */ public static void lockLogLevel(int levelToLock) { level = levelToLock; lockedLogLevel = true; if (out != null) { out.setLogLevel(level); } } /** * Accessor method to lock defined log level * */ public static void unlockLogLevel() { lockedLogLevel = false; } /** * Accessor method to retrieve log level: * @return actual log level */ public static int getLogLevel() { return level; } /** * ERROR: Error message * @param msg the message to be logged */ public static void error(String msg) { writeLogMessage(ERROR, "ERROR", msg); } /** * ERROR: Error message * @param msg the message to be logged * @param obj the object that send error message */ public static void error(Object obj, String msg) { StringBuffer message = new StringBuffer(); message.append("[").append(obj.getClass().getName()) .append("] ").append(msg); writeLogMessage(ERROR, "ERROR", message.toString()); } /** * ERROR: Error message * @param msg the message to be logged * @param tag the tag characterizing the log message initiator */ public static void error(String tag, String msg) { StringBuffer message = new StringBuffer(); message.append("[").append(tag).append("] ").append(msg); writeLogMessage(ERROR, "ERROR", message.toString()); } /** * ERROR: Error message * @param msg the message to be logged * @param tag the tag characterizing the log message initiator * @param e the exception that caused the error */ public static void error(String tag, String msg, Throwable e) { StringBuffer message = new StringBuffer(); message.append("[").append(tag).append("] ").append(msg).append("(").append(e.toString()).append(")"); writeLogMessage(ERROR, "ERROR", message.toString()); writeLogMessage(ERROR, "ERROR", StackTracePrinter.getStackTrace(e)); } /** * INFO: Information message * @param msg the message to be logged */ public static void info(String msg) { writeLogMessage(INFO, "INFO", msg); } /** * INFO: Information message * @param msg the message to be logged * @param obj the object that send log message */ public static void info(Object obj, String msg) { StringBuffer message = new StringBuffer(); message.append("[").append(obj.getClass().getName()) .append("] ").append(msg); writeLogMessage(INFO, "INFO", message.toString()); } /** * INFO: Information message * @param msg the message to be logged * @param tag the tag characterizing the log message initiator */ public static void info(String tag, String msg) { StringBuffer message = new StringBuffer(); message.append("[").append(tag).append("] ").append(msg); writeLogMessage(INFO, "INFO", message.toString()); } /** * DEBUG: Debug message * @param msg the message to be logged */ public static void debug(String msg) { writeLogMessage(DEBUG, "DEBUG", msg); } /** * DEBUG: Information message * @param msg the message to be logged * @param tag the tag characterizing the log message initiator */ public static void debug(String tag, String msg) { StringBuffer message = new StringBuffer(); message.append("[").append(tag).append("] ").append(msg); writeLogMessage(DEBUG, "DEBUG", message.toString()); } /** * DEBUG: Information message * @param msg the message to be logged * @param obj the object that send log message */ public static void debug(Object obj, String msg) { StringBuffer message = new StringBuffer(); message.append("[").append(obj.getClass().getName()) .append("] ").append(msg); writeLogMessage(DEBUG, "DEBUG", message.toString()); } /** * TRACE: Debugger mode */ public static void trace(String msg) { writeLogMessage(TRACE, "TRACE", msg); } /** * TRACE: Information message * @param msg the message to be logged * @param obj the object that send log message */ public static void trace(Object obj, String msg) { StringBuffer message = new StringBuffer(); message.append("[").append(obj.getClass().getName()) .append("] ").append(msg); writeLogMessage(TRACE, "TRACE", message.toString()); } /** * TRACE: Information message * @param msg the message to be logged * @param tag the tag characterizing the log message initiator */ public static void trace(String tag, String msg) { StringBuffer message = new StringBuffer(); message.append("[").append(tag).append("] ").append(msg); writeLogMessage(TRACE, "TRACE", message.toString()); } /** * Dump memory statistics at this point. Dump if level >= DEBUG. * * @param msg message to be logged */ public static void memoryStats(String msg) { // Try to force a garbage collection, so we get the real amount of // available memory long available = Runtime.getRuntime().freeMemory(); Runtime.getRuntime().gc(); writeLogMessage(PROFILING, "PROFILING-MEMORY", msg + ":" + available + " [bytes]"); } /** * Dump memory statistics at this point. * * @param obj caller object * @param msg message to be logged */ public static void memoryStats(Object obj, String msg) { // Try to force a garbage collection, so we get the real amount of // available memory Runtime.getRuntime().gc(); long available = Runtime.getRuntime().freeMemory(); writeLogMessage(PROFILING, "PROFILING-MEMORY", obj.getClass().getName() + "::" + msg + ":" + available + " [bytes]"); } /** * Dump time statistics at this point. * * @param msg message to be logged */ public static void timeStats(String msg) { long time = System.currentTimeMillis(); if (initialTimeStamp == -1) { writeLogMessage(PROFILING, "PROFILING-TIME", msg + ": 0 [msec]"); initialTimeStamp = time; } else { long currentTime = time - initialTimeStamp; writeLogMessage(PROFILING, "PROFILING-TIME", msg + ": " + currentTime + "[msec]"); } } /** * Dump time statistics at this point. * * @param obj caller object * @param msg message to be logged */ public static void timeStats(Object obj, String msg) { // Try to force a garbage collection, so we get the real amount of // available memory long time = System.currentTimeMillis(); if (initialTimeStamp == -1) { writeLogMessage(PROFILING, "PROFILING-TIME", obj.getClass().getName() + "::" + msg + ": 0 [msec]"); initialTimeStamp = time; } else { long currentTime = time - initialTimeStamp; writeLogMessage(PROFILING, "PROFILING-TIME", obj.getClass().getName() + "::" + msg + ":" + currentTime + " [msec]"); } } /** * Dump time statistics at this point. * * @param msg message to be logged */ public static void stats(String msg) { memoryStats(msg); timeStats(msg); } /** * Dump time statistics at this point. * * @param obj caller object * @param msg message to be logged */ public static void stats(Object obj, String msg) { memoryStats(obj, msg); timeStats(obj, msg); } /** * Return the current log appender LogContent container object */ public static LogContent getCurrentLogContent() throws IOException { return out.getLogContent(); } /** * Return true if a message is currently loggable at the given log level * * @param msgLevel the log level the msg shall be logged at. */ public static boolean isLoggable(int msgLevel) { return msgLevel <= level; } private static synchronized void writeLogMessage(int msgLevel, String levelMsg, String msg) { if (contextLogging) { try { cacheMessage(msgLevel, levelMsg, msg); } catch (Exception e) { // Cannot cache log message, just ignore the error } } try { writeLogMessageNoCache(msgLevel, levelMsg, msg); } catch (Exception e) { // Cannot write log message, just ignore the error } } private static void writeLogMessageNoCache(int msgLevel, String levelMsg, String msg) { if (level >= msgLevel) { try { if (out != null) { out.writeLogMessage(levelMsg, msg); } else { Date now = new Date(); System.out.print(now.toString()); System.out.print(" [" + levelMsg + "] " ); System.out.println(msg); } } catch (Exception ex) { ex.printStackTrace(); } } } private static void cacheMessage(int msgLevel, String levelMsg, String msg) throws IOException { // If we are already dumping at DEBUG, then the context is already // available if (cache == null || level >= clientMaxLogLevel) { return; } if (msgLevel == ERROR) { dumpAndFlushCache(); } else { // Store at next if (next >= cache.size()) { cache.addElement(msg); } else { cache.setElementAt(msg, next); } // Move next next++; if (next == cacheSize) { next = 0; } if (next == first) { // Make room for the next entry first++; } if (first == cacheSize) { first = 0; } } } private static void dumpAndFlushCache() throws IOException { int i = first; if (first != next) { writeLogMessageNoCache(ERROR, "[Error Context]", "=================================================="); } while (i != next) { if (i == cacheSize) { i = 0; } writeLogMessageNoCache(ERROR, "[Error Context]", (String) cache.elementAt(i)); ++i; } if (first != next) { writeLogMessageNoCache(ERROR, "[Error Context]", "=================================================="); } first = 0; next = 0; } }