Threads Java

/*
 * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2006.
 *
 * Licensed under the Aduna BSD-style license.
 */
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
/**
 * Thread-based logging utility. The ThreadLog requires threads to register
 * themselves with the ThreadLog. In this registration procedure, a mapping is
 * made between the thread calling the registration method and an instance of
 * ThreadLog. This mapping is later used in all calls to logging-methods to
 * look-up whether these messages should be logged, and where they should be
 * logged.
 * 


 * Log messages are assigned a "level of importance". From high to low, these
 * levels are ERROR, WARNING, STATUS and TRACE. Messages can be suppressed based
 * on these levels. If a Thread registers itself with log level WARNING, only
 * errors and warning will be logged; status messages and traces will be
 * suppressed.
 */
public class ThreadLog {
  /*-----------*
   * Constants *
   *-----------*/
  public static final int NONE = 0;
  public static final int ERROR = 1;
  public static final int WARNING = 2;
  public static final int STATUS = 3;
  public static final int TRACE = 4;
  public static final int ALL = 5;
  private static final String LINE_SEPARATOR = System.getProperty("line.separator");
  /*------------------*
   * Static variables *
   *------------------*/
  static SimpleDateFormat _formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
  static String[] _levelNames = { "NONE   ", "ERROR  ", "WARNING", "STATUS ", "TRACE  ", "ALL    " };
  /** Keeps track of which Threads maps to which ThreadLogs. */
  static InheritableThreadLocal _threadContext = new InheritableThreadLocal();
  static ThreadLog _defaultThreadLog;
  /** Maps (absolute) file paths to PrintWriters. */
  static HashMap _writerTable = new HashMap();
  /**
   * set the ThreadLog to log warning and error messages to System.err by
   * default
   */
  static {
    setDefaultLog(null, WARNING);
  }
  /*----------------*
   * Static methods *
   *----------------*/
  /**
   * Sets a default log file for all threads that have not registered
   * themselves. Logging calls from any of these threads to ThreadLog will be
   * logged to the specified log file if their priority is equal to or higher
   * then the specified log level. If 'logFile' is equal to 'null', messages
   * will be printed to System.err.
   * 
   * @param logFile
   *        The file to log to, or null to log messages to
   *        System.err.
   * @param logLevel
   *        One of the constants ERROR, WARNING, STATUS, TRACE, or ALL.
   * @see #ERROR
   * @see #WARNING
   * @see #STATUS
   * @see #TRACE
   * @see #ALL
   */
  public static void setDefaultLog(String logFile, int logLevel) {
    if (_defaultThreadLog == null) {
      _defaultThreadLog = new ThreadLog();
    }
    Writer logWriter = null;
    try {
      logWriter = _getLogWriter(logFile);
    }
    catch (IOException e) {
      System.err.println(e.getMessage());
      e.printStackTrace();
      try {
        logWriter = _getLogWriter(null);
      }
      catch (IOException ignore) {
        // ignore
      }
    }
    _defaultThreadLog.setLogWriter(logWriter);
    _defaultThreadLog.setLogLev(logLevel);
  }
  /**
   * Unsets the default log file for all threads that have not registered
   * themselves.
   */
  public static void unsetDefaultLog() {
    _defaultThreadLog = null;
  }
  public static int getDefaultLogLevel() {
    if (_defaultThreadLog == null) {
      _defaultThreadLog = new ThreadLog();
    }
    return _defaultThreadLog.getLogLev();
  }
  /**
   * Registers the calling thread with the ThreadLog. Logging calls to
   * ThreadLog will be logged to the specified log file if their priority is
   * equal to or higher then the specified log level. If 'logFile' is equal to
   * 'null', messages will be printed to System.err.
   * 
   * @param logFile
   *        The file to log to, or null to log messages to
   *        System.err.
   * @param logLevel
   *        One of the constants ERROR, WARNING, STATUS, TRACE, or ALL.
   * @see #ERROR
   * @see #WARNING
   * @see #STATUS
   * @see #TRACE
   * @see #ALL
   */
  public static void registerThread(String logFile, int logLevel) {
    ThreadLog threadLog = _createThreadLog();
    Writer logWriter = null;
    try {
      logWriter = _getLogWriter(logFile);
    }
    catch (IOException e) {
      System.err.println(e.getMessage());
      e.printStackTrace();
      try {
        logWriter = _getLogWriter(null);
      }
      catch (IOException ignore) {
        // ignore
      }
    }
    threadLog.setLogWriter(logWriter);
    threadLog.setLogLev(logLevel);
    Thread currentThread = Thread.currentThread();
    threadLog.setThreadName(currentThread.getName());
  }
  /**
   * Changes the log level for the calling thread.
   * 
   * @param logLevel
   *        One of the constants ERROR, WARNING, STATUS, TRACE, or ALL.
   * @see #ERROR
   * @see #WARNING
   * @see #STATUS
   * @see #TRACE
   */
  public static void setLogLevel(int logLevel) {
    ThreadLog threadLog = null;
    synchronized (_threadContext) {
      threadLog = _threadContext.get();
    }
    if (threadLog != null) {
      threadLog.setLogLev(logLevel);
    }
  }
  
  public static int getLogLevel() {
    ThreadLog threadLog = _getThreadLog();
    
    if (threadLog != null) {
      return threadLog._logLevel;
    }
    else {
      return NONE;
    }
  }
  public static boolean logLevelEnabled(int logLevel) {
    return getLogLevel() >= logLevel;
  }
  
  /**
   * Creates a ThreadLog for the thread calling this method. If the thread has
   * already created a ThreadLog before, this existing ThreadLog will be
   * returned.
   */
  static ThreadLog _createThreadLog() {
    ThreadLog threadLog = null;
    synchronized (_threadContext) {
      threadLog = _threadContext.get();
      if (threadLog == null) {
        threadLog = new ThreadLog();
        _threadContext.set(threadLog);
      }
    }
    return threadLog;
  }
  /**
   * Gets a (possibly shared) Writer to the specified logFile.
   */
  static Writer _getLogWriter(String logFile)
    throws IOException
  {
    Writer logWriter = null;
    String absPath = null;
    File file = null;
    if (logFile != null) {
      file = new File(logFile);
      absPath = file.getAbsolutePath();
    }
    synchronized (_writerTable) {
      logWriter = _writerTable.get(absPath);
      if (logWriter == null) {
        // Create a new log writer
        if (absPath != null) {
          // Check if parent directory exists yet
          if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
          }
          logWriter = new FileWriter(absPath, true);
        }
        else {
          logWriter = new OutputStreamWriter(System.err);
        }
        _writerTable.put(absPath, logWriter);
      }
    }
    return logWriter;
  }
  /**
   * Deregisters the calling thread with the ThreadLog. Logging calls to
   * ThreadLog on the calling thread will no longer be logged.
   */
  public static void deregisterThread() {
    synchronized (_threadContext) {
      _threadContext.set(null);
    }
  }
  /**
   * Gets the ThreadLog for the thread calling this method. If no ThreadLog is
   * available, null will be returned.
   */
  static ThreadLog _getThreadLog() {
    ThreadLog threadLog = null;
    synchronized (_threadContext) {
      threadLog = _threadContext.get();
    }
    if (threadLog == null) {
      threadLog = _defaultThreadLog;
    }
    return threadLog;
  }
  /*-----------*
   * Variables *
   *-----------*/
  /** Writer for the log file. */
  Writer _logWriter;
  /** Log level */
  int _logLevel;
  /**
   * Name of the thread.
   */
  String _threadName;
  /*--------------*
   * Constructors *
   *--------------*/
  ThreadLog() {
    this(null, ALL);
  }
  ThreadLog(Writer logWriter) {
    this(logWriter, ALL);
  }
  ThreadLog(Writer logWriter, int logLevel) {
    setLogWriter(logWriter);
    setLogLev(logLevel);
    _threadName = null;
  }
  /*---------*
   * Methods *
   *---------*/
  void setLogWriter(Writer logWriter) {
    _logWriter = logWriter;
  }
  void setLogLev(int logLevel) {
    _logLevel = logLevel;
  }
  int getLogLev() {
    return _logLevel;
  }
  void setThreadName(String threadName) {
    _threadName = threadName;
  }
  String getThreadName() {
    return _threadName;
  }
  void doLog(String msg, Object arg, int level) {
    // First check log level
    if (_logLevel < level) {
      return;
    }
    // Create log message
    String logMsg = _createLogMessage(msg, arg, level, _threadName);
    // Write log message
    // Synchronize on _logWriter to prevent mixed lines
    try {
      synchronized (_logWriter) {
        _logWriter.write(logMsg);
        _logWriter.flush();
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
  static String _createLogMessage(String msg, Object arg, int level, String threadName) {
    StringBuilder logMsg = new StringBuilder(128);
    logMsg.append(_formatter.format(new Date()));
    if (threadName != null) {
      logMsg.append(" [");
      logMsg.append(threadName);
      logMsg.append("]");
    }
    logMsg.append(" [");
    logMsg.append(_levelNames[level]);
    logMsg.append("] ");
    logMsg.append(msg);
    if (arg != null) {
      logMsg.append(": ");
      if (arg instanceof Throwable) {
        Throwable throwable = (Throwable)arg;
        logMsg.append(throwable.getMessage());
        logMsg.append(LINE_SEPARATOR);
        java.io.StringWriter stackTrace = new java.io.StringWriter();
        java.io.PrintWriter pw = new java.io.PrintWriter(stackTrace);
        throwable.printStackTrace(pw);
        logMsg.append(stackTrace.toString());
      }
      else {
        String argString = arg.toString();
        if (argString.contains("\n") || argString.contains("\r")) {
          // argument formatted with newlines, do not append on same line
          logMsg.append(LINE_SEPARATOR);
        }
        logMsg.append(argString);
      }
    }
    logMsg.append(LINE_SEPARATOR);
    return logMsg.toString();
  }
  /*------------------------*
   * Static utility methods *
   *------------------------*/
  /**
   * Logs an error.
   * 
   * @param msg
   *        A indicative message for the error.
   */
  public static void error(String msg) {
    _log(msg, null, ERROR);
  }
  /**
   * Logs an error.
   * 
   * @param msg
   *        A indicative message for the error.
   * @param arg
   *        An argument related to the error. In case arg is an
   *        instance of java.lang.Throwable, the message and stack
   *        trace of the argument will be logged.
   */
  public static void error(String msg, Object arg) {
    _log(msg, arg, ERROR);
  }
  /**
   * Logs a warning.
   * 
   * @param msg
   *        A indicative message for the warning.
   */
  public static void warning(String msg) {
    _log(msg, null, WARNING);
  }
  /**
   * Logs a warning.
   * 
   * @param msg
   *        A indicative message for the warning.
   * @param arg
   *        An argument related to the warning. In case arg is an
   *        instance of java.lang.Throwable, the message and stack
   *        trace of the argument will be logged.
   */
  public static void warning(String msg, Object arg) {
    _log(msg, arg, WARNING);
  }
  /**
   * Logs a message.
   * 
   * @param msg
   *        A indicative message for the message.
   */
  public static void log(String msg) {
    _log(msg, null, STATUS);
  }
  /**
   * Logs a message.
   * 
   * @param msg
   *        A indicative message for the message.
   * @param arg
   *        An argument related to the message. In case arg is an
   *        instance of java.lang.Throwable, the message and stack
   *        trace of the argument will be logged.
   */
  public static void log(String msg, Object arg) {
    _log(msg, arg, STATUS);
  }
  /**
   * Logs a trace message.
   * 
   * @param msg
   *        A indicative message for the trace message.
   */
  public static void trace(String msg) {
    _log(msg, null, TRACE);
  }
  /**
   * Logs a trace message.
   * 
   * @param msg
   *        A indicative message for the trace message.
   * @param arg
   *        An argument related to the trace message. In case arg is
   *        an instance of java.lang.Throwable, the message and
   *        stack trace of the argument will be logged.
   */
  public static void trace(String msg, Object arg) {
    _log(msg, arg, TRACE);
  }
  /**
   * Logs a message on the specified level.
   * 
   * @param msg
   *        A indicative message for the trace message.
   * @param arg
   *        An argument related to the trace message. In case arg is
   *        an instance of java.lang.Throwable, the message and
   *        stack trace of the argument will be logged.
   * @param level
   *        One of the constants ERROR, WARNING, STATUS, TRACE, or ALL.
   * @see #ERROR
   * @see #WARNING
   * @see #STATUS
   * @see #TRACE
   * @see #ALL
   */
  protected static void _log(String msg, Object arg, int level) {
    ThreadLog threadLog = _getThreadLog();
    if (threadLog != null) {
      threadLog.doLog(msg, arg, level);
    }
  }
}