1 /*
2 * Copyright 2001-2004 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.commons.logging.impl;
18
19 import java.io.InputStream;
20 import java.io.Serializable;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.security.AccessController;
24 import java.security.PrivilegedAction;
25 import java.text.DateFormat;
26 import java.text.SimpleDateFormat;
27 import java.util.Date;
28 import java.util.Properties;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogConfigurationException;
32
33 /**
34 * <p>
35 * Simple implementation of Log that sends all enabled log messages, for all
36 * defined loggers, to System.err. The following system properties are supported
37 * to configure the behavior of this logger:
38 * </p>
39 * <ul>
40 * <li><code>org.apache.commons.logging.simplelog.defaultlog</code> - Default
41 * logging detail level for all instances of SimpleLog. Must be one of ("trace",
42 * "debug", "info", "warn", "error", or "fatal"). If not specified, defaults to
43 * "info".</li>
44 * <li><code>org.apache.commons.logging.simplelog.log.xxxxx</code> - Logging
45 * detail level for a SimpleLog instance named "xxxxx". Must be one of ("trace",
46 * "debug", "info", "warn", "error", or "fatal"). If not specified, the default
47 * logging detail level is used.</li>
48 * <li><code>org.apache.commons.logging.simplelog.showlogname</code> - Set to
49 * <code>true</code> if you want the Log instance name to be included in output
50 * messages. Defaults to <code>false</code>.</li>
51 * <li><code>org.apache.commons.logging.simplelog.showShortLogname</code> - Set
52 * to <code>true</code> if you want the last component of the name to be
53 * included in output messages. Defaults to <code>true</code>.</li>
54 * <li><code>org.apache.commons.logging.simplelog.showdatetime</code> - Set to
55 * <code>true</code> if you want the current date and time to be included in
56 * output messages. Default is <code>false</code>.</li>
57 * <li><code>org.apache.commons.logging.simplelog.dateTimeFormat</code> - The
58 * date and time format to be used in the output messages. The pattern
59 * describing the date and time format is the same that is used in
60 * <code>java.text.SimpleDateFormat</code>. If the format is not specified or is
61 * invalid, the default format is used. The default format is
62 * <code>yyyy/MM/dd HH:mm:ss:SSS zzz</code>.</li>
63 * </ul>
64 *
65 * <p>
66 * In addition to looking for system properties with the names specified above,
67 * this implementation also checks for a class loader resource named
68 * <code>"simplelog.properties"</code>, and includes any matching definitions
69 * from this resource (if it exists).
70 * </p>
71 *
72 * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
73 * @author Rod Waldhoff
74 * @author Robert Burrell Donkin
75 *
76 * @version $Id: SimpleLog.java,v 1.21 2004/06/06 20:47:56 rdonkin Exp $
77 */
78 public class SimpleLog implements Log, Serializable {
79
80 private static final long serialVersionUID = 136942970684951178L;
81
82 // ------------------------------------------------------- Class Attributes
83
84 /** All system properties used by <code>SimpleLog</code> start with this */
85 static protected final String systemPrefix = "org.apache.commons.logging.simplelog.";
86
87 /** Properties loaded from simplelog.properties */
88 static protected final Properties simpleLogProps = new Properties();
89
90 /** The default format to use when formatting dates */
91 static protected final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz";
92
93 /** Include the instance name in the log message? */
94 static protected boolean showLogName = false;
95 /**
96 * Include the short name ( last component ) of the logger in the log message.
97 * Defaults to true - otherwise we'll be lost in a flood of messages without
98 * knowing who sends them.
99 */
100 static protected boolean showShortName = true;
101 /** Include the current time in the log message */
102 static protected boolean showDateTime = false;
103 /** The date and time format to use in the log message */
104 static protected String dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
105 /** Used to format times */
106 static protected DateFormat dateFormatter = null;
107
108 // ---------------------------------------------------- Log Level Constants
109
110 /** "Trace" level logging. */
111 public static final int LOG_LEVEL_TRACE = 1;
112 /** "Debug" level logging. */
113 public static final int LOG_LEVEL_DEBUG = 2;
114 /** "Info" level logging. */
115 public static final int LOG_LEVEL_INFO = 3;
116 /** "Warn" level logging. */
117 public static final int LOG_LEVEL_WARN = 4;
118 /** "Error" level logging. */
119 public static final int LOG_LEVEL_ERROR = 5;
120 /** "Fatal" level logging. */
121 public static final int LOG_LEVEL_FATAL = 6;
122
123 /** Enable all logging levels */
124 public static final int LOG_LEVEL_ALL = (LOG_LEVEL_TRACE - 1);
125
126 /** Enable no logging levels */
127 public static final int LOG_LEVEL_OFF = (LOG_LEVEL_FATAL + 1);
128
129 // ------------------------------------------------------------ Initializer
130
131 private static String getStringProperty(String name) {
132 String prop = null;
133 try {
134 prop = System.getProperty(name);
135 } catch (SecurityException e) {
136 ; // Ignore
137 }
138 return (prop == null) ? simpleLogProps.getProperty(name) : prop;
139 }
140
141 private static String getStringProperty(String name, String dephault) {
142 String prop = getStringProperty(name);
143 return (prop == null) ? dephault : prop;
144 }
145
146 private static boolean getBooleanProperty(String name, boolean dephault) {
147 String prop = getStringProperty(name);
148 return (prop == null) ? dephault : "true".equalsIgnoreCase(prop);
149 }
150
151 // Initialize class attributes.
152 // Load properties file, if found.
153 // Override with system properties.
154 static {
155 // Add props from the resource simplelog.properties
156 InputStream in = getResourceAsStream("simplelog.properties");
157 if (null != in) {
158 try {
159 simpleLogProps.load(in);
160 } catch (java.io.IOException e) {
161 // ignored
162 } finally {
163 try {
164 in.close();
165 } catch (java.io.IOException e) {
166 // ignored
167 }
168 }
169 }
170
171 showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName);
172 showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName);
173 showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime);
174
175 if (showDateTime) {
176 dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat", dateTimeFormat);
177 try {
178 dateFormatter = new SimpleDateFormat(dateTimeFormat);
179 } catch (IllegalArgumentException e) {
180 // If the format pattern is invalid - use the default format
181 dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
182 dateFormatter = new SimpleDateFormat(dateTimeFormat);
183 }
184 }
185 }
186
187 // ------------------------------------------------------------- Attributes
188
189 /** The name of this simple log instance */
190 protected String logName = null;
191 /** The current log level */
192 protected int currentLogLevel;
193 /** The short name of this simple log instance */
194 private String shortLogName = null;
195
196 // ------------------------------------------------------------ Constructor
197
198 /**
199 * Construct a simple log with given name.
200 *
201 * @param name
202 * log name
203 */
204 public SimpleLog(String name) {
205
206 logName = name;
207
208 // Set initial log level
209 // Used to be: set default log level to ERROR
210 // IMHO it should be lower, but at least info ( costin ).
211 setLevel(SimpleLog.LOG_LEVEL_INFO);
212
213 // Set log level from properties
214 String lvl = getStringProperty(systemPrefix + "log." + logName);
215 int i = String.valueOf(name).lastIndexOf(".");
216 while (null == lvl && i > -1) {
217 name = name.substring(0, i);
218 lvl = getStringProperty(systemPrefix + "log." + name);
219 i = String.valueOf(name).lastIndexOf(".");
220 }
221
222 if (null == lvl) {
223 lvl = getStringProperty(systemPrefix + "defaultlog");
224 }
225
226 if ("all".equalsIgnoreCase(lvl)) {
227 setLevel(SimpleLog.LOG_LEVEL_ALL);
228 } else if ("trace".equalsIgnoreCase(lvl)) {
229 setLevel(SimpleLog.LOG_LEVEL_TRACE);
230 } else if ("debug".equalsIgnoreCase(lvl)) {
231 setLevel(SimpleLog.LOG_LEVEL_DEBUG);
232 } else if ("info".equalsIgnoreCase(lvl)) {
233 setLevel(SimpleLog.LOG_LEVEL_INFO);
234 } else if ("warn".equalsIgnoreCase(lvl)) {
235 setLevel(SimpleLog.LOG_LEVEL_WARN);
236 } else if ("error".equalsIgnoreCase(lvl)) {
237 setLevel(SimpleLog.LOG_LEVEL_ERROR);
238 } else if ("fatal".equalsIgnoreCase(lvl)) {
239 setLevel(SimpleLog.LOG_LEVEL_FATAL);
240 } else if ("off".equalsIgnoreCase(lvl)) {
241 setLevel(SimpleLog.LOG_LEVEL_OFF);
242 }
243
244 }
245
246 // -------------------------------------------------------- Properties
247
248 /**
249 * <p>
250 * Set logging level.
251 * </p>
252 *
253 * @param currentLogLevel
254 * new logging level
255 */
256 public void setLevel(int currentLogLevel) {
257
258 this.currentLogLevel = currentLogLevel;
259
260 }
261
262 /**
263 * <p>
264 * Get logging level.
265 * </p>
266 */
267 public int getLevel() {
268
269 return currentLogLevel;
270 }
271
272 // -------------------------------------------------------- Logging Methods
273
274 /**
275 * <p>
276 * Do the actual logging. This method assembles the message and then calls
277 * <code>write()</code> to cause it to be written.
278 * </p>
279 *
280 * @param type
281 * One of the LOG_LEVEL_XXX constants defining the log level
282 * @param message
283 * The message itself (typically a String)
284 * @param t
285 * The exception whose stack trace should be logged
286 */
287 protected void log(int type, Object message, Throwable t) {
288 // Use a string buffer for better performance
289 StringBuffer buf = new StringBuffer();
290
291 // Append date-time if so configured
292 if (showDateTime) {
293 buf.append(dateFormatter.format(new Date()));
294 buf.append(" ");
295 }
296
297 // Append a readable representation of the log level
298 switch (type) {
299 case SimpleLog.LOG_LEVEL_TRACE:
300 buf.append("[TRACE] ");
301 break;
302 case SimpleLog.LOG_LEVEL_DEBUG:
303 buf.append("[DEBUG] ");
304 break;
305 case SimpleLog.LOG_LEVEL_INFO:
306 buf.append("[INFO] ");
307 break;
308 case SimpleLog.LOG_LEVEL_WARN:
309 buf.append("[WARN] ");
310 break;
311 case SimpleLog.LOG_LEVEL_ERROR:
312 buf.append("[ERROR] ");
313 break;
314 case SimpleLog.LOG_LEVEL_FATAL:
315 buf.append("[FATAL] ");
316 break;
317 }
318
319 // Append the name of the log instance if so configured
320 if (showShortName) {
321 if (shortLogName == null) {
322 // Cut all but the last component of the name for both styles
323 shortLogName = logName.substring(logName.lastIndexOf(".") + 1);
324 shortLogName = shortLogName.substring(shortLogName.lastIndexOf("/") + 1);
325 }
326 buf.append(String.valueOf(shortLogName)).append(" - ");
327 } else if (showLogName) {
328 buf.append(String.valueOf(logName)).append(" - ");
329 }
330
331 // Append the message
332 buf.append(String.valueOf(message));
333
334 // Append stack trace if not null
335 if (t != null) {
336 buf.append(" <");
337 buf.append(t.toString());
338 buf.append(">");
339
340 java.io.StringWriter sw = new java.io.StringWriter(1024);
341 java.io.PrintWriter pw = new java.io.PrintWriter(sw);
342 t.printStackTrace(pw);
343 pw.close();
344 buf.append(sw.toString());
345 }
346
347 // Print to the appropriate destination
348 write(buf);
349
350 }
351
352 /**
353 * <p>
354 * Write the content of the message accumulated in the specified
355 * <code>StringBuffer</code> to the appropriate output destination. The
356 * default implementation writes to <code>System.err</code>.
357 * </p>
358 *
359 * @param buffer
360 * A <code>StringBuffer</code> containing the accumulated text to be
361 * logged
362 */
363 protected void write(StringBuffer buffer) {
364
365 System.err.println(buffer.toString());
366
367 }
368
369 /**
370 * Is the given log level currently enabled?
371 *
372 * @param logLevel
373 * is this level enabled?
374 */
375 protected boolean isLevelEnabled(int logLevel) {
376 // log level are numerically ordered so can use simple numeric
377 // comparison
378 return (logLevel >= currentLogLevel);
379 }
380
381 // -------------------------------------------------------- Log Implementation
382
383 /**
384 * <p>
385 * Log a message with debug log level.
386 * </p>
387 */
388 public final void debug(Object message) {
389
390 if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) {
391 log(SimpleLog.LOG_LEVEL_DEBUG, message, null);
392 }
393 }
394
395 /**
396 * <p>
397 * Log an error with debug log level.
398 * </p>
399 */
400 public final void debug(Object message, Throwable t) {
401
402 if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) {
403 log(SimpleLog.LOG_LEVEL_DEBUG, message, t);
404 }
405 }
406
407 /**
408 * <p>
409 * Log a message with trace log level.
410 * </p>
411 */
412 public final void trace(Object message) {
413
414 if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) {
415 log(SimpleLog.LOG_LEVEL_TRACE, message, null);
416 }
417 }
418
419 /**
420 * <p>
421 * Log an error with trace log level.
422 * </p>
423 */
424 public final void trace(Object message, Throwable t) {
425
426 if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) {
427 log(SimpleLog.LOG_LEVEL_TRACE, message, t);
428 }
429 }
430
431 /**
432 * <p>
433 * Log a message with info log level.
434 * </p>
435 */
436 public final void info(Object message) {
437
438 if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) {
439 log(SimpleLog.LOG_LEVEL_INFO, message, null);
440 }
441 }
442
443 /**
444 * <p>
445 * Log an error with info log level.
446 * </p>
447 */
448 public final void info(Object message, Throwable t) {
449
450 if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) {
451 log(SimpleLog.LOG_LEVEL_INFO, message, t);
452 }
453 }
454
455 /**
456 * <p>
457 * Log a message with warn log level.
458 * </p>
459 */
460 public final void warn(Object message) {
461
462 if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) {
463 log(SimpleLog.LOG_LEVEL_WARN, message, null);
464 }
465 }
466
467 /**
468 * <p>
469 * Log an error with warn log level.
470 * </p>
471 */
472 public final void warn(Object message, Throwable t) {
473
474 if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) {
475 log(SimpleLog.LOG_LEVEL_WARN, message, t);
476 }
477 }
478
479 /**
480 * <p>
481 * Log a message with error log level.
482 * </p>
483 */
484 public final void error(Object message) {
485
486 if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) {
487 log(SimpleLog.LOG_LEVEL_ERROR, message, null);
488 }
489 }
490
491 /**
492 * <p>
493 * Log an error with error log level.
494 * </p>
495 */
496 public final void error(Object message, Throwable t) {
497
498 if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) {
499 log(SimpleLog.LOG_LEVEL_ERROR, message, t);
500 }
501 }
502
503 /**
504 * <p>
505 * Log a message with fatal log level.
506 * </p>
507 */
508 public final void fatal(Object message) {
509
510 if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) {
511 log(SimpleLog.LOG_LEVEL_FATAL, message, null);
512 }
513 }
514
515 /**
516 * <p>
517 * Log an error with fatal log level.
518 * </p>
519 */
520 public final void fatal(Object message, Throwable t) {
521
522 if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) {
523 log(SimpleLog.LOG_LEVEL_FATAL, message, t);
524 }
525 }
526
527 /**
528 * <p>
529 * Are debug messages currently enabled?
530 * </p>
531 *
532 * <p>
533 * This allows expensive operations such as <code>String</code> concatenation
534 * to be avoided when the message will be ignored by the logger.
535 * </p>
536 */
537 public final boolean isDebugEnabled() {
538
539 return isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG);
540 }
541
542 /**
543 * <p>
544 * Are error messages currently enabled?
545 * </p>
546 *
547 * <p>
548 * This allows expensive operations such as <code>String</code> concatenation
549 * to be avoided when the message will be ignored by the logger.
550 * </p>
551 */
552 public final boolean isErrorEnabled() {
553
554 return isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR);
555 }
556
557 /**
558 * <p>
559 * Are fatal messages currently enabled?
560 * </p>
561 *
562 * <p>
563 * This allows expensive operations such as <code>String</code> concatenation
564 * to be avoided when the message will be ignored by the logger.
565 * </p>
566 */
567 public final boolean isFatalEnabled() {
568
569 return isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL);
570 }
571
572 /**
573 * <p>
574 * Are info messages currently enabled?
575 * </p>
576 *
577 * <p>
578 * This allows expensive operations such as <code>String</code> concatenation
579 * to be avoided when the message will be ignored by the logger.
580 * </p>
581 */
582 public final boolean isInfoEnabled() {
583
584 return isLevelEnabled(SimpleLog.LOG_LEVEL_INFO);
585 }
586
587 /**
588 * <p>
589 * Are trace messages currently enabled?
590 * </p>
591 *
592 * <p>
593 * This allows expensive operations such as <code>String</code> concatenation
594 * to be avoided when the message will be ignored by the logger.
595 * </p>
596 */
597 public final boolean isTraceEnabled() {
598
599 return isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE);
600 }
601
602 /**
603 * <p>
604 * Are warn messages currently enabled?
605 * </p>
606 *
607 * <p>
608 * This allows expensive operations such as <code>String</code> concatenation
609 * to be avoided when the message will be ignored by the logger.
610 * </p>
611 */
612 public final boolean isWarnEnabled() {
613
614 return isLevelEnabled(SimpleLog.LOG_LEVEL_WARN);
615 }
616
617 /**
618 * Return the thread context class loader if available. Otherwise return null.
619 *
620 * The thread context class loader is available for JDK 1.2 or later, if
621 * certain security conditions are met.
622 *
623 * @exception LogConfigurationException
624 * if a suitable class loader cannot be identified.
625 */
626 private static ClassLoader getContextClassLoader() {
627 ClassLoader classLoader = null;
628
629 if (classLoader == null) {
630 try {
631 // Are we running on a JDK 1.2 or later system?
632 Method method = Thread.class.getMethod("getContextClassLoader");
633
634 // Get the thread context class loader (if there is one)
635 try {
636 classLoader = (ClassLoader) method.invoke(Thread.currentThread());
637 } catch (IllegalAccessException e) {
638 ; // ignore
639 } catch (InvocationTargetException e) {
640 /**
641 * InvocationTargetException is thrown by 'invoke' when the method
642 * being invoked (getContextClassLoader) throws an exception.
643 *
644 * getContextClassLoader() throws SecurityException when the context
645 * class loader isn't an ancestor of the calling class's class loader,
646 * or if security permissions are restricted.
647 *
648 * In the first case (not related), we want to ignore and keep going.
649 * We cannot help but also ignore the second with the logic below, but
650 * other calls elsewhere (to obtain a class loader) will trigger this
651 * exception where we can make a distinction.
652 */
653 if (e.getTargetException() instanceof SecurityException) {
654 ; // ignore
655 } else {
656 // Capture 'e.getTargetException()' exception for details
657 // alternate: log 'e.getTargetException()', and pass back 'e'.
658 throw new LogConfigurationException("Unexpected InvocationTargetException", e.getTargetException());
659 }
660 }
661 } catch (NoSuchMethodException e) {
662 // Assume we are running on JDK 1.1
663 ; // ignore
664 }
665 }
666
667 if (classLoader == null) {
668 classLoader = SimpleLog.class.getClassLoader();
669 }
670
671 // Return the selected class loader
672 return classLoader;
673 }
674
675 private static InputStream getResourceAsStream(final String name) {
676 return AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
677 public InputStream run() {
678 ClassLoader threadCL = getContextClassLoader();
679
680 if (threadCL != null) {
681 return threadCL.getResourceAsStream(name);
682 } else {
683 return ClassLoader.getSystemResourceAsStream(name);
684 }
685 }
686 });
687 }
688 }