View Javadoc
1   /**
2    * Copyright (c) 2004-2012 QOS.ch
3    * All rights reserved.
4    *
5    * Permission is hereby granted, free of charge, to any person obtaining
6    * a copy of this software and associated documentation files (the
7    * "Software"), to  deal in  the Software without  restriction, including
8    * without limitation  the rights to  use, copy, modify,  merge, publish,
9    * distribute,  sublicense, and/or sell  copies of  the Software,  and to
10   * permit persons to whom the Software  is furnished to do so, subject to
11   * the following conditions:
12   *
13   * The  above  copyright  notice  and  this permission  notice  shall  be
14   * included in all copies or substantial portions of the Software.
15   *
16   * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
17   * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
18   * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
19   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20   * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21   * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
22   * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23   *
24   */
25  package org.slf4j.impl;
26  
27  import java.io.PrintStream;
28  import java.util.Date;
29  
30  import org.slf4j.Logger;
31  import org.slf4j.event.LoggingEvent;
32  import org.slf4j.helpers.FormattingTuple;
33  import org.slf4j.helpers.MarkerIgnoringBase;
34  import org.slf4j.helpers.MessageFormatter;
35  import org.slf4j.spi.LocationAwareLogger;
36  
37  /**
38   * <p>
39   * Simple implementation of {@link Logger} that sends all enabled log messages,
40   * for all defined loggers, to the console ({@code System.err}). The following
41   * system properties are supported to configure the behavior of this logger:
42   * </p>
43   *
44   * <ul>
45   * <li><code>org.slf4j.simpleLogger.logFile</code> - The output target which can
46   * be the <em>path</em> to a file, or the special values "System.out" and
47   * "System.err". Default is "System.err".</li>
48   * 
49   * <li><code>org.slf4j.simpleLogger.cacheOutputStream</code> - If the output
50   * target is set to "System.out" or "System.err" (see preceding entry), by
51   * default, logs will be output to the latest value referenced by
52   * <code>System.out/err</code> variables. By setting this
53   * parameter to true, the output stream will be cached, i.e. assigned once at
54   * initialization time and re-used independently of the current value referenced by
55   *  <code>System.out/err</code>.
56   * </li>
57   * 
58   * <li><code>org.slf4j.simpleLogger.defaultLogLevel</code> - Default log level
59   * for all instances of SimpleLogger. Must be one of ("trace", "debug", "info",
60   * "warn", "error" or "off"). If not specified, defaults to "info".</li>
61   *
62   * <li><code>org.slf4j.simpleLogger.log.<em>a.b.c</em></code> - Logging detail
63   * level for a SimpleLogger instance named "a.b.c". Right-side value must be one
64   * of "trace", "debug", "info", "warn", "error" or "off". When a SimpleLogger
65   * named "a.b.c" is initialized, its level is assigned from this property. If
66   * unspecified, the level of nearest parent logger will be used, and if none is
67   * set, then the value specified by
68   * <code>org.slf4j.simpleLogger.defaultLogLevel</code> will be used.</li>
69   *
70   * <li><code>org.slf4j.simpleLogger.showDateTime</code> - Set to
71   * <code>true</code> if you want the current date and time to be included in
72   * output messages. Default is <code>false</code></li>
73   *
74   * <li><code>org.slf4j.simpleLogger.dateTimeFormat</code> - The date and time
75   * format to be used in the output messages. The pattern describing the date and
76   * time format is defined by <a href=
77   * "http://docs.oracle.com/javase/1.5.0/docs/api/java/text/SimpleDateFormat.html">
78   * <code>SimpleDateFormat</code></a>. If the format is not specified or is
79   * invalid, the number of milliseconds since start up will be output.</li>
80   *
81   * <li><code>org.slf4j.simpleLogger.showThreadName</code> -Set to
82   * <code>true</code> if you want to output the current thread name. Defaults to
83   * <code>true</code>.</li>
84   *
85   * <li><code>org.slf4j.simpleLogger.showLogName</code> - Set to
86   * <code>true</code> if you want the Logger instance name to be included in
87   * output messages. Defaults to <code>true</code>.</li>
88   *
89   * <li><code>org.slf4j.simpleLogger.showShortLogName</code> - Set to
90   * <code>true</code> if you want the last component of the name to be included
91   * in output messages. Defaults to <code>false</code>.</li>
92   *
93   * <li><code>org.slf4j.simpleLogger.levelInBrackets</code> - Should the level
94   * string be output in brackets? Defaults to <code>false</code>.</li>
95   *
96   * <li><code>org.slf4j.simpleLogger.warnLevelString</code> - The string value
97   * output for the warn level. Defaults to <code>WARN</code>.</li>
98   * 
99   * </ul>
100  *
101  * <p>
102  * In addition to looking for system properties with the names specified above,
103  * this implementation also checks for a class loader resource named
104  * <code>"simplelogger.properties"</code>, and includes any matching definitions
105  * from this resource (if it exists).
106  * </p>
107  *
108  * <p>
109  * With no configuration, the default output includes the relative time in
110  * milliseconds, thread name, the level, logger name, and the message followed
111  * by the line separator for the host. In log4j terms it amounts to the "%r [%t]
112  * %level %logger - %m%n" pattern.
113  * </p>
114  * <p>
115  * Sample output follows.
116  * </p>
117  * 
118  * <pre>
119  * 176 [main] INFO examples.Sort - Populating an array of 2 elements in reverse order.
120  * 225 [main] INFO examples.SortAlgo - Entered the sort method.
121  * 304 [main] INFO examples.SortAlgo - Dump of integer array:
122  * 317 [main] INFO examples.SortAlgo - Element [0] = 0
123  * 331 [main] INFO examples.SortAlgo - Element [1] = 1
124  * 343 [main] INFO examples.Sort - The next log statement should be an error message.
125  * 346 [main] ERROR examples.SortAlgo - Tried to dump an uninitialized array.
126  *   at org.log4j.examples.SortAlgo.dump(SortAlgo.java:58)
127  *   at org.log4j.examples.Sort.main(Sort.java:64)
128  * 467 [main] INFO  examples.Sort - Exiting main method.
129  * </pre>
130  *
131  * <p>
132  * This implementation is heavily inspired by
133  * <a href="http://commons.apache.org/logging/">Apache Commons Logging</a>'s
134  * SimpleLog.
135  * </p>
136  *
137  * @author Ceki G&uuml;lc&uuml;
138  * @author Scott Sanders
139  * @author Rod Waldhoff
140  * @author Robert Burrell Donkin
141  * @author C&eacute;drik LIME
142  */
143 public class SimpleLogger extends MarkerIgnoringBase {
144 
145     private static final long serialVersionUID = -632788891211436180L;
146 
147     private static long START_TIME = System.currentTimeMillis();
148 
149     protected static final int LOG_LEVEL_TRACE = LocationAwareLogger.TRACE_INT;
150     protected static final int LOG_LEVEL_DEBUG = LocationAwareLogger.DEBUG_INT;
151     protected static final int LOG_LEVEL_INFO = LocationAwareLogger.INFO_INT;
152     protected static final int LOG_LEVEL_WARN = LocationAwareLogger.WARN_INT;
153     protected static final int LOG_LEVEL_ERROR = LocationAwareLogger.ERROR_INT;
154     // The OFF level can only be used in configuration files to disable logging.
155     // It has
156     // no printing method associated with it in o.s.Logger interface.
157     protected static final int LOG_LEVEL_OFF = LOG_LEVEL_ERROR + 10;
158 
159     private static boolean INITIALIZED = false;
160     static final private SimpleLoggerConfiguration CONFIG_PARAMS =  new SimpleLoggerConfiguration();
161 
162     static void lazyInit() {
163         if (INITIALIZED) {
164             return;
165         }
166         INITIALIZED = true;
167         init();
168     }
169 
170     // external software might be invoking this method directly. Do not rename
171     // or change its semantics.
172     static void init() {
173         CONFIG_PARAMS.init();
174     }
175 
176     /** The current log level */
177     protected int currentLogLevel = LOG_LEVEL_INFO;
178     /** The short name of this simple log instance */
179     private transient String shortLogName = null;
180 
181     /**
182      * All system properties used by <code>SimpleLogger</code> start with this
183      * prefix
184      */
185     public static final String SYSTEM_PREFIX = "org.slf4j.simpleLogger.";
186 
187     public static final String LOG_KEY_PREFIX = SimpleLogger.SYSTEM_PREFIX + "log.";
188 
189     public static final String CACHE_OUTPUT_STREAM_STRING_KEY = SimpleLogger.SYSTEM_PREFIX + "cacheOutputStream";
190 
191     public static final String WARN_LEVEL_STRING_KEY = SimpleLogger.SYSTEM_PREFIX + "warnLevelString";
192 
193     public static final String LEVEL_IN_BRACKETS_KEY = SimpleLogger.SYSTEM_PREFIX + "levelInBrackets";
194 
195     public static final String LOG_FILE_KEY = SimpleLogger.SYSTEM_PREFIX + "logFile";
196 
197     public static final String SHOW_SHORT_LOG_NAME_KEY = SimpleLogger.SYSTEM_PREFIX + "showShortLogName";
198 
199     public static final String SHOW_LOG_NAME_KEY = SimpleLogger.SYSTEM_PREFIX + "showLogName";
200 
201     public static final String SHOW_THREAD_NAME_KEY = SimpleLogger.SYSTEM_PREFIX + "showThreadName";
202 
203     public static final String DATE_TIME_FORMAT_KEY = SimpleLogger.SYSTEM_PREFIX + "dateTimeFormat";
204 
205     public static final String SHOW_DATE_TIME_KEY = SimpleLogger.SYSTEM_PREFIX + "showDateTime";
206 
207     public static final String DEFAULT_LOG_LEVEL_KEY = SimpleLogger.SYSTEM_PREFIX + "defaultLogLevel";
208 
209     /**
210      * Package access allows only {@link SimpleLoggerFactory} to instantiate
211      * SimpleLogger instances.
212      */
213     SimpleLogger(String name) {
214         this.name = name;
215 
216         String levelString = recursivelyComputeLevelString();
217         if (levelString != null) {
218             this.currentLogLevel = SimpleLoggerConfiguration.stringToLevel(levelString);
219         } else {
220             this.currentLogLevel = CONFIG_PARAMS.defaultLogLevel;
221         }
222     }
223 
224     String recursivelyComputeLevelString() {
225         String tempName = name;
226         String levelString = null;
227         int indexOfLastDot = tempName.length();
228         while ((levelString == null) && (indexOfLastDot > -1)) {
229             tempName = tempName.substring(0, indexOfLastDot);
230             levelString = CONFIG_PARAMS.getStringProperty(SimpleLogger.LOG_KEY_PREFIX + tempName, null);
231             indexOfLastDot = String.valueOf(tempName).lastIndexOf(".");
232         }
233         return levelString;
234     }
235 
236     /**
237      * This is our internal implementation for logging regular
238      * (non-parameterized) log messages.
239      *
240      * @param level
241      *            One of the LOG_LEVEL_XXX constants defining the log level
242      * @param message
243      *            The message itself
244      * @param t
245      *            The exception whose stack trace should be logged
246      */
247     private void log(int level, String message, Throwable t) {
248         if (!isLevelEnabled(level)) {
249             return;
250         }
251 
252         StringBuilder buf = new StringBuilder(32);
253 
254         // Append date-time if so configured
255         if (CONFIG_PARAMS.showDateTime) {
256             if (CONFIG_PARAMS.dateFormatter != null) {
257                 buf.append(getFormattedDate());
258                 buf.append(' ');
259             } else {
260                 buf.append(System.currentTimeMillis() - START_TIME);
261                 buf.append(' ');
262             }
263         }
264 
265         // Append current thread name if so configured
266         if (CONFIG_PARAMS.showThreadName) {
267             buf.append('[');
268             buf.append(Thread.currentThread().getName());
269             buf.append("] ");
270         }
271 
272         if (CONFIG_PARAMS.levelInBrackets)
273             buf.append('[');
274 
275         // Append a readable representation of the log level
276         String levelStr = renderLevel(level);
277         buf.append(levelStr);
278         if (CONFIG_PARAMS.levelInBrackets)
279             buf.append(']');
280         buf.append(' ');
281 
282         // Append the name of the log instance if so configured
283         if (CONFIG_PARAMS.showShortLogName) {
284             if (shortLogName == null)
285                 shortLogName = computeShortName();
286             buf.append(String.valueOf(shortLogName)).append(" - ");
287         } else if (CONFIG_PARAMS.showLogName) {
288             buf.append(String.valueOf(name)).append(" - ");
289         }
290 
291         // Append the message
292         buf.append(message);
293 
294         write(buf, t);
295 
296     }
297 
298     protected String renderLevel(int level) {
299         switch (level) {
300         case LOG_LEVEL_TRACE:
301             return "TRACE";
302         case LOG_LEVEL_DEBUG:
303             return ("DEBUG");
304         case LOG_LEVEL_INFO:
305             return "INFO";
306         case LOG_LEVEL_WARN:
307             return CONFIG_PARAMS.warnLevelString;
308         case LOG_LEVEL_ERROR:
309             return "ERROR";
310         }
311         throw new IllegalStateException("Unrecognized level [" + level + "]");
312     }
313 
314     /**
315      * To avoid intermingling of log messages and associated stack traces, the two
316      * operations are done in a synchronized block.
317      * 
318      * @param buf
319      * @param t
320      */
321     void write(StringBuilder buf, Throwable t) {
322         PrintStream targetStream = CONFIG_PARAMS.outputChoice.getTargetPrintStream();
323 
324         synchronized (CONFIG_PARAMS) {
325             targetStream.println(buf.toString());
326             writeThrowable(t, targetStream);
327             targetStream.flush();
328         }
329 
330     }
331 
332     protected void writeThrowable(Throwable t, PrintStream targetStream) {
333         if (t != null) {
334             t.printStackTrace(targetStream);
335         }
336     }
337 
338     private String getFormattedDate() {
339         Date now = new Date();
340         String dateText;
341         synchronized (CONFIG_PARAMS.dateFormatter) {
342             dateText = CONFIG_PARAMS.dateFormatter.format(now);
343         }
344         return dateText;
345     }
346 
347     private String computeShortName() {
348         return name.substring(name.lastIndexOf(".") + 1);
349     }
350 
351     /**
352      * For formatted messages, first substitute arguments and then log.
353      *
354      * @param level
355      * @param format
356      * @param arg1
357      * @param arg2
358      */
359     private void formatAndLog(int level, String format, Object arg1, Object arg2) {
360         if (!isLevelEnabled(level)) {
361             return;
362         }
363         FormattingTuple tp = MessageFormatter.format(format, arg1, arg2);
364         log(level, tp.getMessage(), tp.getThrowable());
365     }
366 
367     /**
368      * For formatted messages, first substitute arguments and then log.
369      *
370      * @param level
371      * @param format
372      * @param arguments
373      *            a list of 3 ore more arguments
374      */
375     private void formatAndLog(int level, String format, Object... arguments) {
376         if (!isLevelEnabled(level)) {
377             return;
378         }
379         FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments);
380         log(level, tp.getMessage(), tp.getThrowable());
381     }
382 
383     /**
384      * Is the given log level currently enabled?
385      *
386      * @param logLevel
387      *            is this level enabled?
388      */
389     protected boolean isLevelEnabled(int logLevel) {
390         // log level are numerically ordered so can use simple numeric
391         // comparison
392         return (logLevel >= currentLogLevel);
393     }
394 
395     /** Are {@code trace} messages currently enabled? */
396     public boolean isTraceEnabled() {
397         return isLevelEnabled(LOG_LEVEL_TRACE);
398     }
399 
400     /**
401      * A simple implementation which logs messages of level TRACE according to
402      * the format outlined above.
403      */
404     public void trace(String msg) {
405         log(LOG_LEVEL_TRACE, msg, null);
406     }
407 
408     /**
409      * Perform single parameter substitution before logging the message of level
410      * TRACE according to the format outlined above.
411      */
412     public void trace(String format, Object param1) {
413         formatAndLog(LOG_LEVEL_TRACE, format, param1, null);
414     }
415 
416     /**
417      * Perform double parameter substitution before logging the message of level
418      * TRACE according to the format outlined above.
419      */
420     public void trace(String format, Object param1, Object param2) {
421         formatAndLog(LOG_LEVEL_TRACE, format, param1, param2);
422     }
423 
424     /**
425      * Perform double parameter substitution before logging the message of level
426      * TRACE according to the format outlined above.
427      */
428     public void trace(String format, Object... argArray) {
429         formatAndLog(LOG_LEVEL_TRACE, format, argArray);
430     }
431 
432     /** Log a message of level TRACE, including an exception. */
433     public void trace(String msg, Throwable t) {
434         log(LOG_LEVEL_TRACE, msg, t);
435     }
436 
437     /** Are {@code debug} messages currently enabled? */
438     public boolean isDebugEnabled() {
439         return isLevelEnabled(LOG_LEVEL_DEBUG);
440     }
441 
442     /**
443      * A simple implementation which logs messages of level DEBUG according to
444      * the format outlined above.
445      */
446     public void debug(String msg) {
447         log(LOG_LEVEL_DEBUG, msg, null);
448     }
449 
450     /**
451      * Perform single parameter substitution before logging the message of level
452      * DEBUG according to the format outlined above.
453      */
454     public void debug(String format, Object param1) {
455         formatAndLog(LOG_LEVEL_DEBUG, format, param1, null);
456     }
457 
458     /**
459      * Perform double parameter substitution before logging the message of level
460      * DEBUG according to the format outlined above.
461      */
462     public void debug(String format, Object param1, Object param2) {
463         formatAndLog(LOG_LEVEL_DEBUG, format, param1, param2);
464     }
465 
466     /**
467      * Perform double parameter substitution before logging the message of level
468      * DEBUG according to the format outlined above.
469      */
470     public void debug(String format, Object... argArray) {
471         formatAndLog(LOG_LEVEL_DEBUG, format, argArray);
472     }
473 
474     /** Log a message of level DEBUG, including an exception. */
475     public void debug(String msg, Throwable t) {
476         log(LOG_LEVEL_DEBUG, msg, t);
477     }
478 
479     /** Are {@code info} messages currently enabled? */
480     public boolean isInfoEnabled() {
481         return isLevelEnabled(LOG_LEVEL_INFO);
482     }
483 
484     /**
485      * A simple implementation which logs messages of level INFO according to
486      * the format outlined above.
487      */
488     public void info(String msg) {
489         log(LOG_LEVEL_INFO, msg, null);
490     }
491 
492     /**
493      * Perform single parameter substitution before logging the message of level
494      * INFO according to the format outlined above.
495      */
496     public void info(String format, Object arg) {
497         formatAndLog(LOG_LEVEL_INFO, format, arg, null);
498     }
499 
500     /**
501      * Perform double parameter substitution before logging the message of level
502      * INFO according to the format outlined above.
503      */
504     public void info(String format, Object arg1, Object arg2) {
505         formatAndLog(LOG_LEVEL_INFO, format, arg1, arg2);
506     }
507 
508     /**
509      * Perform double parameter substitution before logging the message of level
510      * INFO according to the format outlined above.
511      */
512     public void info(String format, Object... argArray) {
513         formatAndLog(LOG_LEVEL_INFO, format, argArray);
514     }
515 
516     /** Log a message of level INFO, including an exception. */
517     public void info(String msg, Throwable t) {
518         log(LOG_LEVEL_INFO, msg, t);
519     }
520 
521     /** Are {@code warn} messages currently enabled? */
522     public boolean isWarnEnabled() {
523         return isLevelEnabled(LOG_LEVEL_WARN);
524     }
525 
526     /**
527      * A simple implementation which always logs messages of level WARN
528      * according to the format outlined above.
529      */
530     public void warn(String msg) {
531         log(LOG_LEVEL_WARN, msg, null);
532     }
533 
534     /**
535      * Perform single parameter substitution before logging the message of level
536      * WARN according to the format outlined above.
537      */
538     public void warn(String format, Object arg) {
539         formatAndLog(LOG_LEVEL_WARN, format, arg, null);
540     }
541 
542     /**
543      * Perform double parameter substitution before logging the message of level
544      * WARN according to the format outlined above.
545      */
546     public void warn(String format, Object arg1, Object arg2) {
547         formatAndLog(LOG_LEVEL_WARN, format, arg1, arg2);
548     }
549 
550     /**
551      * Perform double parameter substitution before logging the message of level
552      * WARN according to the format outlined above.
553      */
554     public void warn(String format, Object... argArray) {
555         formatAndLog(LOG_LEVEL_WARN, format, argArray);
556     }
557 
558     /** Log a message of level WARN, including an exception. */
559     public void warn(String msg, Throwable t) {
560         log(LOG_LEVEL_WARN, msg, t);
561     }
562 
563     /** Are {@code error} messages currently enabled? */
564     public boolean isErrorEnabled() {
565         return isLevelEnabled(LOG_LEVEL_ERROR);
566     }
567 
568     /**
569      * A simple implementation which always logs messages of level ERROR
570      * according to the format outlined above.
571      */
572     public void error(String msg) {
573         log(LOG_LEVEL_ERROR, msg, null);
574     }
575 
576     /**
577      * Perform single parameter substitution before logging the message of level
578      * ERROR according to the format outlined above.
579      */
580     public void error(String format, Object arg) {
581         formatAndLog(LOG_LEVEL_ERROR, format, arg, null);
582     }
583 
584     /**
585      * Perform double parameter substitution before logging the message of level
586      * ERROR according to the format outlined above.
587      */
588     public void error(String format, Object arg1, Object arg2) {
589         formatAndLog(LOG_LEVEL_ERROR, format, arg1, arg2);
590     }
591 
592     /**
593      * Perform double parameter substitution before logging the message of level
594      * ERROR according to the format outlined above.
595      */
596     public void error(String format, Object... argArray) {
597         formatAndLog(LOG_LEVEL_ERROR, format, argArray);
598     }
599 
600     /** Log a message of level ERROR, including an exception. */
601     public void error(String msg, Throwable t) {
602         log(LOG_LEVEL_ERROR, msg, t);
603     }
604 
605     public void log(LoggingEvent event) {
606         int levelInt = event.getLevel().toInt();
607 
608         if (!isLevelEnabled(levelInt)) {
609             return;
610         }
611         FormattingTuple tp = MessageFormatter.arrayFormat(event.getMessage(), event.getArgumentArray(), event.getThrowable());
612         log(levelInt, tp.getMessage(), event.getThrowable());
613     }
614 
615 }