Home | History | Annotate | Download | only in logging
      1 /* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
      2  *
      3  * This program and the accompanying materials are made available under
      4  * the terms of the Common Public License v1.0 which accompanies this distribution,
      5  * and is available at http://www.eclipse.org/legal/cpl-v10.html
      6  *
      7  * $Id: Logger.java,v 1.1.1.1.2.2 2004/07/16 23:32:29 vlad_r Exp $
      8  */
      9 package com.vladium.logging;
     10 
     11 import java.io.PrintWriter;
     12 import java.io.StringWriter;
     13 import java.util.HashSet;
     14 import java.util.LinkedList;
     15 import java.util.NoSuchElementException;
     16 import java.util.Properties;
     17 import java.util.Set;
     18 import java.util.StringTokenizer;
     19 
     20 import com.vladium.emma.AppLoggers;
     21 import com.vladium.emma.IAppConstants;
     22 import com.vladium.util.ClassLoaderResolver;
     23 import com.vladium.util.Property;
     24 import com.vladium.util.Strings;
     25 
     26 // ----------------------------------------------------------------------------
     27 /**
     28  * A simple Java version-independent logging framework. Each Logger is also
     29  * an immutable context that encapsulates configuration elements like the
     30  * logging verbosity level etc. In general, a Logger is looked up as an
     31  * inheritable thread-local piece of data. This decouples classes and
     32  * logging configurations in a way that seems difficult with log4j.<P>
     33  *
     34  * Note that a given class is free to cache its context in an instance field
     35  * if the class is instantiated and used only on a single thread (or a set of
     36  * threads that are guaranteed to share the same logging context). [This is
     37  * different from the usual log4j pattern of caching a logger in a class static
     38  * field]. In other cases (e.g., the instrumentation runtime), it makes more
     39  * sense to scope a context to a single method invocation.<P>
     40  *
     41  * Every log message is structured as follows:
     42  * <OL>
     43  *  <LI> message is prefixed with the prefix string set in the Logger if that is
     44  * not null;
     45  *  <LI> if the calling class could be identified and it supplied the calling
     46  * method name, the calling method is identified with all name components that
     47  * are not null;
     48  *  <LI> caller-supplied message is logged, if not null;
     49  *  <LI> caller-supplied Throwable is dumped starting with a new line, if not null.
     50  * </OL>
     51  *
     52  * MT-safety: a given Logger instance will not get corrupted by concurrent
     53  * usage from multiple threads and guarantees that data written to the underlying
     54  * PrintWriter in a single log call will be done in one atomic print() step.
     55  *
     56  * @see ILogLevels
     57  *
     58  * @author (C) 2001, Vlad Roubtsov
     59  */
     60 public
     61 final class Logger implements ILogLevels
     62 {
     63     // public: ................................................................
     64 
     65     // TODO: update javadoc for 'logCaller'
     66     // TODO: need isLoggable (Class)
     67 
     68     public static Logger create (final int level, final PrintWriter out, final String prefix, final Set classMask)
     69     {
     70         if ((level < NONE) || (level > ALL))
     71             throw new IllegalArgumentException ("invalid log level: " + level);
     72 
     73         if ((out == null) || out.checkError ())
     74             throw new IllegalArgumentException ("null or corrupt input: out");
     75 
     76         return new Logger (level, out, prefix, classMask);
     77     }
     78 
     79     /**
     80      * This works as a cloning creator of sorts.
     81      *
     82      * @param level
     83      * @param out
     84      * @param prefix
     85      * @param classMask
     86      * @param base
     87      * @return
     88      */
     89     public static Logger create (final int level, final PrintWriter out, final String prefix, final Set classMask,
     90                                  final Logger base)
     91     {
     92         if (base == null)
     93         {
     94             return create (level, out, prefix, classMask);
     95         }
     96         else
     97         {
     98             final int _level = level >= NONE
     99                 ? level
    100                 : base.m_level;
    101 
    102             final PrintWriter _out = (out != null) && ! out.checkError ()
    103                 ? out
    104                 : base.m_out;
    105 
    106             // TODO: do a better job of logger cloning
    107             final String _prefix = prefix;
    108 //            final String _prefix = prefix != null
    109 //                ? prefix
    110 //                : base.m_prefix;
    111 
    112             final Set _classMask = classMask != null
    113                 ? classMask
    114                 : base.m_classMask;
    115 
    116 
    117             return new Logger (_level, _out, _prefix, _classMask);
    118         }
    119     }
    120 
    121 
    122     /**
    123      * A quick method to determine if logging is enabled at a given level.
    124      * This method acquires no monitors and should be used when calling one of
    125      * log() or convenience logging methods directly incurs significant
    126      * parameter construction overhead.
    127      *
    128      * @see ILogLevels
    129      */
    130     public final boolean isLoggable (final int level)
    131     {
    132         return (level <= m_level);
    133     }
    134 
    135     /**
    136      * A convenience method equivalent to isLoggable(INFO).
    137      */
    138     public final boolean atINFO ()
    139     {
    140         return (INFO <= m_level);
    141     }
    142 
    143     /**
    144      * A convenience method equivalent to isLoggable(VERBOSE).
    145      */
    146     public final boolean atVERBOSE ()
    147     {
    148         return (VERBOSE <= m_level);
    149     }
    150 
    151     /**
    152      * A convenience method equivalent to isLoggable(TRACE1).
    153      */
    154     public final boolean atTRACE1 ()
    155     {
    156         return (TRACE1 <= m_level);
    157     }
    158 
    159     /**
    160      * A convenience method equivalent to isLoggable(TRACE2).
    161      */
    162     public final boolean atTRACE2 ()
    163     {
    164         return (TRACE2 <= m_level);
    165     }
    166 
    167     /**
    168      * A convenience method equivalent to isLoggable(TRACE3).
    169      */
    170     public final boolean atTRACE3 ()
    171     {
    172         return (TRACE3 <= m_level);
    173     }
    174 
    175 
    176     /**
    177      * A convenience method to log 'msg' from an anonymous calling method
    178      * at WARNING level.
    179      *
    180      * @param msg log message [ignored if null]
    181      */
    182     public final void warning (final String msg)
    183     {
    184         _log (WARNING, null, msg, false);
    185     }
    186 
    187     /**
    188      * A convenience method to log 'msg' from an anonymous calling method
    189      * at INFO level.
    190      *
    191      * @param msg log message [ignored if null]
    192      */
    193     public final void info (final String msg)
    194     {
    195         _log (INFO, null, msg, false);
    196     }
    197 
    198     /**
    199      * A convenience method to log 'msg' from an anonymous calling method
    200      * at VERBOSE level.
    201      *
    202      * @param msg log message [ignored if null]
    203      */
    204     public final void verbose (final String msg)
    205     {
    206         _log (VERBOSE, null, msg, false);
    207     }
    208 
    209 
    210     /**
    211      * A convenience method equivalent to log(TRACE1, method, msg).
    212      *
    213      * @param method calling method name [ignored if null]
    214      * @param msg log message [ignored if null]
    215      */
    216     public final void trace1 (final String method, final String msg)
    217     {
    218         _log (TRACE1, method, msg, true);
    219     }
    220 
    221     /**
    222      * A convenience method equivalent to log(TRACE2, method, msg).
    223      *
    224      * @param method calling method name [ignored if null]
    225      * @param msg log message [ignored if null]
    226      */
    227     public final void trace2 (final String method, final String msg)
    228     {
    229         _log (TRACE2, method, msg, true);
    230     }
    231 
    232     /**
    233      * A convenience method equivalent to log(TRACE3, method, msg).
    234      *
    235      * @param method calling method name [ignored if null]
    236      * @param msg log message [ignored if null]
    237      */
    238     public final void trace3 (final String method, final String msg)
    239     {
    240         _log (TRACE3, method, msg, true);
    241     }
    242 
    243     /**
    244      * Logs 'msg' from an unnamed calling method.
    245      *
    246      * @param level level to log at [the method does nothing if this is less
    247      * than the set level].
    248      * @param msg log message [ignored if null]
    249      */
    250     public final void log (final int level, final String msg, final boolean logCaller)
    251     {
    252         _log (level, null, msg, logCaller);
    253     }
    254 
    255     /**
    256      * Logs 'msg' from a given calling method.
    257      *
    258      * @param level level to log at [the method does nothing if this is less
    259      * than the set level].
    260      * @param method calling method name [ignored if null]
    261      * @param msg log message [ignored if null]
    262      */
    263     public final void log (final int level, final String method, final String msg, final boolean logCaller)
    264     {
    265         _log (level, method, msg, logCaller);
    266     }
    267 
    268     /**
    269      * Logs 'msg' from an unnamed calling method followed by the 'throwable' stack
    270      * trace dump.
    271      *
    272      * @param level level to log at [the method does nothing if this is less
    273      * than the set level].
    274      * @param msg log message [ignored if null]
    275      * @param throwable to dump after message [ignored if null]
    276      */
    277     public final void log (final int level, final String msg, final Throwable throwable)
    278     {
    279         _log (level, null, msg, throwable);
    280     }
    281 
    282     /**
    283      * Logs 'msg' from a given calling method followed by the 'throwable' stack
    284      * trace dump.
    285      *
    286      * @param level level to log at [the method does nothing if this is less
    287      * than the set level].
    288      * @param method calling method name [ignored if null]
    289      * @param msg log message [ignored if null]
    290      * @param throwable to dump after message [ignored if null]
    291      */
    292     public final void log (final int level, final String method, final String msg, final Throwable throwable)
    293     {
    294         _log (level, method, msg, throwable);
    295     }
    296 
    297 
    298     /**
    299      * Provides direct access to the PrintWriter used by this Logger.
    300      *
    301      * @return print writer used by this logger [never null]
    302      */
    303     public PrintWriter getWriter ()
    304     {
    305         return m_out;
    306     }
    307 
    308 
    309     /**
    310      * Returns the current top of the thread-local logger stack or the static
    311      * Logger instance scoped to Logger.class if the stack is empty.
    312      *
    313      * @return current logger [never null]
    314      */
    315     public static Logger getLogger ()
    316     {
    317         final LinkedList stack = (LinkedList) THREAD_LOCAL_STACK.get ();
    318 
    319         // [assertion: stack != null]
    320 
    321         if (stack.isEmpty ())
    322         {
    323             return STATIC_LOGGER;
    324         }
    325         else
    326         {
    327             return (Logger) stack.getLast ();
    328         }
    329     }
    330 
    331     /**
    332      *
    333      * @param ctx [may not be null]
    334      */
    335     public static void push (final Logger ctx)
    336     {
    337         if (ctx == null)
    338             throw new IllegalArgumentException ("null input: ctx");
    339 
    340         final LinkedList stack = (LinkedList) THREAD_LOCAL_STACK.get ();
    341         stack.addLast (ctx);
    342     }
    343 
    344     /**
    345      * Requiring a context parameter here helps enforce correct push/pop
    346      * nesting in the caller code.
    347      *
    348      * @param ctx [may not be null]
    349      */
    350     public static void pop (final Logger ctx)
    351     {
    352         // TODO: add guards for making sure only the pushing thread is allowed to
    353         // execute this
    354 
    355         final LinkedList stack = (LinkedList) THREAD_LOCAL_STACK.get ();
    356 
    357         try
    358         {
    359             final Logger current = (Logger) stack.getLast ();
    360             if (current != ctx)
    361                 throw new IllegalStateException ("invalid context being popped: " + ctx);
    362 
    363             stack.removeLast ();
    364             current.cleanup ();
    365         }
    366         catch (NoSuchElementException nsee)
    367         {
    368             throw new IllegalStateException ("empty logger context stack on thread [" + Thread.currentThread () + "]: " + nsee);
    369         }
    370     }
    371 
    372 
    373     public static int stringToLevel (final String level)
    374     {
    375         if (ILogLevels.SEVERE_STRING.equalsIgnoreCase (level) || ILogLevels.SILENT_STRING.equalsIgnoreCase (level))
    376             return ILogLevels.SEVERE;
    377         else if (ILogLevels.WARNING_STRING.equalsIgnoreCase (level) || ILogLevels.QUIET_STRING.equalsIgnoreCase (level))
    378             return ILogLevels.WARNING;
    379         else if (ILogLevels.INFO_STRING.equalsIgnoreCase (level))
    380             return ILogLevels.INFO;
    381         else if (ILogLevels.VERBOSE_STRING.equalsIgnoreCase (level))
    382             return ILogLevels.VERBOSE;
    383         else if (ILogLevels.TRACE1_STRING.equalsIgnoreCase (level))
    384             return ILogLevels.TRACE1;
    385         else if (ILogLevels.TRACE2_STRING.equalsIgnoreCase (level))
    386             return ILogLevels.TRACE2;
    387         else if (ILogLevels.TRACE3_STRING.equalsIgnoreCase (level))
    388             return ILogLevels.TRACE3;
    389         else if (ILogLevels.NONE_STRING.equalsIgnoreCase (level))
    390             return ILogLevels.NONE;
    391         else if (ILogLevels.ALL_STRING.equalsIgnoreCase (level))
    392             return ILogLevels.ALL;
    393         else
    394         {
    395             int _level = Integer.MIN_VALUE;
    396             try
    397             {
    398                 _level = Integer.parseInt (level);
    399             }
    400             catch (Exception ignore) {}
    401 
    402             if ((_level >= ILogLevels.NONE) && (_level <= ILogLevels.ALL))
    403                 return _level;
    404             else
    405                 return ILogLevels.INFO; // default to something middle of the ground
    406         }
    407     }
    408 
    409     // protected: .............................................................
    410 
    411     // package: ...............................................................
    412 
    413     // private: ...............................................................
    414 
    415 
    416     private static final class ThreadLocalStack extends InheritableThreadLocal
    417     {
    418         protected Object initialValue ()
    419         {
    420             return new LinkedList ();
    421         }
    422 
    423     } // end of nested class
    424 
    425 
    426     private Logger (final int level, final PrintWriter out, final String prefix, final Set classMask)
    427     {
    428         m_level = level;
    429         m_out = out;
    430         m_prefix = prefix;
    431         m_classMask = classMask; // no defensive clone
    432     }
    433 
    434     private void cleanup ()
    435     {
    436         m_out.flush ();
    437     }
    438 
    439     private void _log (final int level, final String method,
    440                        final String msg, final boolean logCaller)
    441     {
    442         if ((level <= m_level) && (level >= SEVERE))
    443         {
    444             final Class caller = logCaller ? ClassLoaderResolver.getCallerClass (2) : null;
    445             final StringBuffer buf = new StringBuffer (m_prefix != null ? m_prefix + ": " : "");
    446 
    447             if ((caller != null) || (method != null))
    448             {
    449                 buf.append ("[");
    450 
    451                 if (caller != null) // if the caller could not be determined, s_classMask is ignored
    452                 {
    453                     String callerName = caller.getName ();
    454 
    455                     if (callerName.startsWith (PREFIX_TO_STRIP))
    456                         callerName = callerName.substring (PREFIX_TO_STRIP_LENGTH);
    457 
    458                     String parentName = callerName;
    459 
    460                     final int firstDollar = callerName.indexOf ('$');
    461                     if (firstDollar > 0) parentName = callerName.substring (0, firstDollar);
    462 
    463                     if ((m_classMask == null) || m_classMask.contains (parentName))
    464                         buf.append (callerName);
    465                     else
    466                         return;
    467                 }
    468 
    469                 if (method != null)
    470                 {
    471                     buf.append ("::");
    472                     buf.append (method);
    473                 }
    474 
    475                 buf.append ("] ");
    476             }
    477 
    478             final PrintWriter out = m_out;
    479 
    480             if (msg != null) buf.append (msg);
    481 
    482             out.println (buf);
    483             if (FLUSH_LOG) out.flush ();
    484         }
    485     }
    486 
    487     private void _log (final int level, final String method,
    488                        final String msg, final Throwable throwable)
    489     {
    490         if ((level <= m_level) && (level >= SEVERE))
    491         {
    492             final Class caller = ClassLoaderResolver.getCallerClass (2);
    493             final StringBuffer buf = new StringBuffer (m_prefix != null ? m_prefix + ": " : "");
    494 
    495             if ((caller != null) || (method != null))
    496             {
    497                 buf.append ("[");
    498 
    499                 if (caller != null) // if the caller could not be determined, s_classMask is ignored
    500                 {
    501                     String callerName = caller.getName ();
    502 
    503                     if (callerName.startsWith (PREFIX_TO_STRIP))
    504                         callerName = callerName.substring (PREFIX_TO_STRIP_LENGTH);
    505 
    506                     String parentName = callerName;
    507 
    508                     final int firstDollar = callerName.indexOf ('$');
    509                     if (firstDollar > 0) parentName = callerName.substring (0, firstDollar);
    510 
    511                     if ((m_classMask == null) || m_classMask.contains (parentName))
    512                         buf.append (callerName);
    513                     else
    514                         return;
    515                 }
    516 
    517                 if (method != null)
    518                 {
    519                     buf.append ("::");
    520                     buf.append (method);
    521                 }
    522 
    523                 buf.append ("] ");
    524             }
    525 
    526             final PrintWriter out = m_out;
    527 
    528             if (msg != null) buf.append (msg);
    529 
    530             if (throwable != null)
    531             {
    532                 final StringWriter sw = new StringWriter ();
    533                 final PrintWriter pw = new PrintWriter (sw);
    534 
    535                 throwable.printStackTrace (pw);
    536                 pw.flush ();
    537 
    538                 buf.append (sw.toString ());
    539             }
    540 
    541             out.println (buf);
    542             if (FLUSH_LOG) out.flush ();
    543         }
    544     }
    545 
    546 
    547 
    548     private final int m_level; // always in [NONE, ALL] range
    549     private final PrintWriter m_out; // never null
    550     private final String m_prefix; // null is equivalent to no prefix
    551     private final Set /* String */ m_classMask; // null is equivalent to no class filtering
    552 
    553     private static final String PREFIX_TO_STRIP = "com.vladium."; // TODO: can this be set programmatically ?
    554     private static final int PREFIX_TO_STRIP_LENGTH = PREFIX_TO_STRIP.length ();
    555     private static final boolean FLUSH_LOG = true;
    556     private static final String COMMA_DELIMITERS    = "," + Strings.WHITE_SPACE;
    557 
    558     private static final Logger STATIC_LOGGER; // set in <clinit>
    559     private static final ThreadLocalStack THREAD_LOCAL_STACK; // set in <clinit>
    560 
    561     static
    562     {
    563         THREAD_LOCAL_STACK = new ThreadLocalStack ();
    564 
    565         // TODO: unfortunately, this init code makes Logger coupled to the app classes
    566         // (via the app namespace string constants)
    567         // I don't quite see an elegant solution to this design problem yet
    568 
    569         final Properties properties = Property.getAppProperties (IAppConstants.APP_NAME_LC, Logger.class.getClassLoader ());
    570 
    571         // verbosity level:
    572 
    573         final int level;
    574         {
    575             final String _level = properties.getProperty (AppLoggers.PROPERTY_VERBOSITY_LEVEL,
    576                                                           AppLoggers.DEFAULT_VERBOSITY_LEVEL);
    577             level = stringToLevel (_level);
    578         }
    579 
    580         // verbosity filter:
    581 
    582         final Set filter;
    583         {
    584             final String _filter = properties.getProperty (AppLoggers.PROPERTY_VERBOSITY_FILTER);
    585             Set temp = null;
    586 
    587             if (_filter != null)
    588             {
    589                 final StringTokenizer tokenizer = new StringTokenizer (_filter, COMMA_DELIMITERS);
    590                 if (tokenizer.countTokens () > 0)
    591                 {
    592                     temp = new HashSet (tokenizer.countTokens ());
    593                     while (tokenizer.hasMoreTokens ())
    594                     {
    595                         temp.add (tokenizer.nextToken ());
    596                     }
    597                 }
    598             }
    599 
    600             filter = temp;
    601         }
    602 
    603 
    604         STATIC_LOGGER = create (level,
    605                                 new PrintWriter (System.out, false),
    606                                 IAppConstants.APP_NAME,
    607                                 filter);
    608     }
    609 
    610 } // end of class
    611 // ----------------------------------------------------------------------------