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 create(final int level, final PrintWriter out, final String prefix, final Set classMask)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 */ create(final int level, final PrintWriter out, final String prefix, final Set classMask, final Logger base)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 */ isLoggable(final int level)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 */ atINFO()138 public final boolean atINFO () 139 { 140 return (INFO <= m_level); 141 } 142 143 /** 144 * A convenience method equivalent to isLoggable(VERBOSE). 145 */ atVERBOSE()146 public final boolean atVERBOSE () 147 { 148 return (VERBOSE <= m_level); 149 } 150 151 /** 152 * A convenience method equivalent to isLoggable(TRACE1). 153 */ atTRACE1()154 public final boolean atTRACE1 () 155 { 156 return (TRACE1 <= m_level); 157 } 158 159 /** 160 * A convenience method equivalent to isLoggable(TRACE2). 161 */ atTRACE2()162 public final boolean atTRACE2 () 163 { 164 return (TRACE2 <= m_level); 165 } 166 167 /** 168 * A convenience method equivalent to isLoggable(TRACE3). 169 */ atTRACE3()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 */ warning(final String msg)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 */ info(final String msg)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 */ verbose(final String msg)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 */ trace1(final String method, final String msg)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 */ trace2(final String method, final String msg)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 */ trace3(final String method, final String msg)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 */ log(final int level, final String msg, final boolean logCaller)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 */ log(final int level, final String method, final String msg, final boolean logCaller)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 */ log(final int level, final String msg, final Throwable throwable)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 */ log(final int level, final String method, final String msg, final Throwable throwable)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 */ getWriter()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 */ getLogger()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 */ push(final Logger ctx)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 */ pop(final Logger ctx)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 stringToLevel(final String level)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 { initialValue()418 protected Object initialValue () 419 { 420 return new LinkedList (); 421 } 422 423 } // end of nested class 424 425 Logger(final int level, final PrintWriter out, final String prefix, final Set classMask)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 cleanup()434 private void cleanup () 435 { 436 m_out.flush (); 437 } 438 _log(final int level, final String method, final String msg, final boolean logCaller)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 _log(final int level, final String method, final String msg, final Throwable throwable)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 { tokenizer.nextToken()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 // ----------------------------------------------------------------------------