KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > vladium > logging > Logger


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 JavaDoc;
12 import java.io.StringWriter JavaDoc;
13 import java.util.HashSet JavaDoc;
14 import java.util.LinkedList JavaDoc;
15 import java.util.NoSuchElementException JavaDoc;
16 import java.util.Properties JavaDoc;
17 import java.util.Set JavaDoc;
18 import java.util.StringTokenizer JavaDoc;
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 JavaDoc out, final String JavaDoc prefix, final Set JavaDoc classMask)
69     {
70         if ((level < NONE) || (level > ALL))
71             throw new IllegalArgumentException JavaDoc ("invalid log level: " + level);
72         
73         if ((out == null) || out.checkError ())
74             throw new IllegalArgumentException JavaDoc ("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 JavaDoc out, final String JavaDoc prefix, final Set JavaDoc 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 JavaDoc _out = (out != null) && ! out.checkError ()
103                 ? out
104                 : base.m_out;
105             
106             // TODO: do a better job of logger cloning
107
final String JavaDoc _prefix = prefix;
108 // final String _prefix = prefix != null
109
// ? prefix
110
// : base.m_prefix;
111

112             final Set JavaDoc _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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc method, final String JavaDoc 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 JavaDoc method, final String JavaDoc 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 JavaDoc method, final String JavaDoc 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 JavaDoc 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 JavaDoc method, final String JavaDoc 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 JavaDoc msg, final Throwable JavaDoc 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 JavaDoc method, final String JavaDoc msg, final Throwable JavaDoc 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 JavaDoc 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 JavaDoc stack = (LinkedList JavaDoc) 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 JavaDoc ("null input: ctx");
339         
340         final LinkedList JavaDoc stack = (LinkedList JavaDoc) 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 JavaDoc stack = (LinkedList JavaDoc) THREAD_LOCAL_STACK.get ();
356
357         try
358         {
359             final Logger current = (Logger) stack.getLast ();
360             if (current != ctx)
361                 throw new IllegalStateException JavaDoc ("invalid context being popped: " + ctx);
362             
363             stack.removeLast ();
364             current.cleanup ();
365         }
366         catch (NoSuchElementException JavaDoc nsee)
367         {
368             throw new IllegalStateException JavaDoc ("empty logger context stack on thread [" + Thread.currentThread () + "]: " + nsee);
369         }
370     }
371
372     
373     public static int stringToLevel (final String JavaDoc 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 JavaDoc 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 JavaDoc
417     {
418         protected Object JavaDoc initialValue ()
419         {
420             return new LinkedList JavaDoc ();
421         }
422         
423     } // end of nested class
424

425     
426     private Logger (final int level, final PrintWriter JavaDoc out, final String JavaDoc prefix, final Set JavaDoc 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 JavaDoc method,
440                        final String JavaDoc msg, final boolean logCaller)
441     {
442         if ((level <= m_level) && (level >= SEVERE))
443         {
444             final Class JavaDoc caller = logCaller ? ClassLoaderResolver.getCallerClass (2) : null;
445             final StringBuffer JavaDoc buf = new StringBuffer JavaDoc (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 JavaDoc callerName = caller.getName ();
454                     
455                     if (callerName.startsWith (PREFIX_TO_STRIP))
456                         callerName = callerName.substring (PREFIX_TO_STRIP_LENGTH);
457                         
458                     String JavaDoc 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 JavaDoc 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 JavaDoc method,
488                        final String JavaDoc msg, final Throwable JavaDoc throwable)
489     {
490         if ((level <= m_level) && (level >= SEVERE))
491         {
492             final Class JavaDoc caller = ClassLoaderResolver.getCallerClass (2);
493             final StringBuffer JavaDoc buf = new StringBuffer JavaDoc (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 JavaDoc callerName = caller.getName ();
502                     
503                     if (callerName.startsWith (PREFIX_TO_STRIP))
504                         callerName = callerName.substring (PREFIX_TO_STRIP_LENGTH);
505                         
506                     String JavaDoc 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 JavaDoc out = m_out;
527             
528             if (msg != null) buf.append (msg);
529             
530             if (throwable != null)
531             {
532                 final StringWriter JavaDoc sw = new StringWriter JavaDoc ();
533                 final PrintWriter JavaDoc pw = new PrintWriter JavaDoc (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 JavaDoc m_out; // never null
550
private final String JavaDoc m_prefix; // null is equivalent to no prefix
551
private final Set JavaDoc /* String */ m_classMask; // null is equivalent to no class filtering
552

553     private static final String JavaDoc 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 JavaDoc 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 JavaDoc properties = Property.getAppProperties (IAppConstants.APP_NAME_LC, Logger.class.getClassLoader ());
570         
571         // verbosity level:
572

573         final int level;
574         {
575             final String JavaDoc _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 JavaDoc filter;
583         {
584             final String JavaDoc _filter = properties.getProperty (AppLoggers.PROPERTY_VERBOSITY_FILTER);
585             Set JavaDoc temp = null;
586             
587             if (_filter != null)
588             {
589                 final StringTokenizer JavaDoc tokenizer = new StringTokenizer JavaDoc (_filter, COMMA_DELIMITERS);
590                 if (tokenizer.countTokens () > 0)
591                 {
592                     temp = new HashSet JavaDoc (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 JavaDoc (System.out, false),
606                                 IAppConstants.APP_NAME,
607                                 filter);
608     }
609     
610 } // end of class
611
// ----------------------------------------------------------------------------
Popular Tags