1 /**
2 * Copyright (c) 2004-2011 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
26 package org.slf4j.impl;
27
28 import java.io.InputStream;
29 import java.security.AccessController;
30 import java.security.PrivilegedAction;
31 import java.text.DateFormat;
32 import java.text.SimpleDateFormat;
33 import java.util.Date;
34 import java.util.Properties;
35
36 import org.slf4j.Logger;
37 import org.slf4j.helpers.FormattingTuple;
38 import org.slf4j.helpers.MarkerIgnoringBase;
39 import org.slf4j.helpers.MessageFormatter;
40 import org.slf4j.helpers.Util;
41 import org.slf4j.spi.LocationAwareLogger;
42
43 /**
44 * <p>Simple implementation of {@link Logger} that sends all enabled log messages,
45 * for all defined loggers, to the console ({@code System.err}).
46 * The following system properties are supported to configure the behavior of this logger:</p>
47 * <ul>
48 * <li><code>org.slf4j.simplelogger.defaultlog</code> -
49 * Default logging detail level for all instances of SimpleLogger.
50 * Must be one of ("trace", "debug", "info", "warn", or "error").
51 * If not specified, defaults to "info". </li>
52 * <li><code>org.slf4j.simplelogger.log.xxxxx</code> -
53 * Logging detail level for a SimpleLogger instance named "xxxxx".
54 * Must be one of ("trace", "debug", "info", "warn", or "error").
55 * If not specified, the default logging detail level is used.</li>
56 * <li><code>org.slf4j.simplelogger.showdatetime</code> -
57 * Set to <code>true</code> if you want the current date and time
58 * to be included in output messages. Default is <code>false</code>,
59 * and will output the number of milliseconds elapsed since startup.</li>
60 * <li><code>org.slf4j.simplelogger.dateTimeFormat</code> -
61 * The date and time format to be used in the output messages.
62 * The pattern describing the date and time format is the same that is
63 * used in <code>java.text.SimpleDateFormat</code>. If the format is not
64 * specified or is invalid, the default format is used.
65 * The default format is <code>yyyy-MM-dd HH:mm:ss:SSS Z</code>.</li>
66 * <li><code>org.slf4j.simplelogger.showthreadname</code> -
67 * Set to <code>true</code> if you want to output the current thread name.
68 * Defaults to <code>true</code>.</li>
69 * <li><code>org.slf4j.simplelogger.showlogname</code> -
70 * Set to <code>true</code> if you want the Logger instance name to be
71 * included in output messages. Defaults to <code>true</code>.</li>
72 * <li><code>org.slf4j.simplelogger.showShortLogname</code> -
73 * Set to <code>true</code> if you want the last component of the name to be
74 * included in output messages. Defaults to <code>false</code>.</li>
75 * </ul>
76 *
77 * <p>In addition to looking for system properties with the names specified
78 * above, this implementation also checks for a class loader resource named
79 * <code>"simplelogger.properties"</code>, and includes any matching definitions
80 * from this resource (if it exists).</p>
81 *
82 *
83 * <p>With no configurationn, the default output includes the relative time in milliseconds,
84 * thread name, the level, logger name, and the message followed by the line
85 * separator for the host. In log4j terms it amounts to the "%r [%t]
86 * %level %logger - %m%n" pattern. </p>
87 *
88 * <p>Sample output follows.</p>
89 <pre>
90 176 [main] INFO examples.Sort - Populating an array of 2 elements in reverse order.
91 225 [main] INFO examples.SortAlgo - Entered the sort method.
92 304 [main] INFO examples.SortAlgo - Dump of integer array:
93 317 [main] INFO examples.SortAlgo - Element [0] = 0
94 331 [main] INFO examples.SortAlgo - Element [1] = 1
95 343 [main] INFO examples.Sort - The next log statement should be an error message.
96 346 [main] ERROR examples.SortAlgo - Tried to dump an uninitialized array.
97 at org.log4j.examples.SortAlgo.dump(SortAlgo.java:58)
98 at org.log4j.examples.Sort.main(Sort.java:64)
99 467 [main] INFO examples.Sort - Exiting main method.
100 </pre>
101 *
102 * <p>This implementation is heavily inspired by
103 * <a href="http://commons.apache.org/logging/">Apache Commons Logging</a>'s SimpleLog.
104 *
105 * @author Ceki Gülcü
106 * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
107 * @author Rod Waldhoff
108 * @author Robert Burrell Donkin
109 * @author Cédrik LIME
110 */
111 public class SimpleLogger extends MarkerIgnoringBase {
112
113 private static final long serialVersionUID = -632788891211436180L;
114
115 /**
116 * Mark the time when this class gets loaded into memory.
117 */
118 private static long startTime = System.currentTimeMillis();
119
120 private static final String CONFIGURATION_FILE = "simplelogger.properties";
121
122 /** All system properties used by <code>SimpleLogger</code> start with this */
123 private static final String systemPrefix = "org.slf4j.simplelogger.";
124
125 /** Properties loaded from simplelogger.properties */
126 private static final Properties simpleLoggerProps = new Properties();
127
128 /** The default format to use when formating dates */
129 private static final String DEFAULT_DATE_TIME_FORMAT =
130 "yyyy-MM-dd HH:mm:ss:SSS Z";
131
132 /** Include the instance name in the log message? */
133 private static boolean showLogName = true;
134 /** Include the short name ( last component ) of the logger in the log
135 * message. Defaults to true - otherwise we'll be lost in a flood of
136 * messages without knowing who sends them.
137 */
138 private static boolean showShortName = false;
139 /** Include the current time in the log message */
140 private static boolean showDateTime = false;
141 /** The date and time format to use in the log message */
142 private static String dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
143
144 /** Include the current thread name in the log message */
145 private static boolean showThreadName = true;
146
147 /**
148 * Used to format times.
149 * <p>
150 * Any code that accesses this object should first obtain a lock on it,
151 * ie use synchronized(dateFormatter); this requirement is
152 * to fix an existing thread safety bug (SimpleDateFormat.format
153 * is not thread-safe).
154 */
155 private static DateFormat dateFormatter = null;
156
157 /** "Trace" level logging. */
158 public static final int LOG_LEVEL_TRACE = LocationAwareLogger.TRACE_INT;
159 /** "Debug" level logging. */
160 public static final int LOG_LEVEL_DEBUG = LocationAwareLogger.DEBUG_INT;
161 /** "Info" level logging. */
162 public static final int LOG_LEVEL_INFO = LocationAwareLogger.INFO_INT;
163 /** "Warn" level logging. */
164 public static final int LOG_LEVEL_WARN = LocationAwareLogger.WARN_INT;
165 /** "Error" level logging. */
166 public static final int LOG_LEVEL_ERROR = LocationAwareLogger.ERROR_INT;
167 /** "Fatal" level logging. */
168 // public static final int LOG_LEVEL_FATAL = 6;
169
170 /** Enable all logging levels */
171 public static final int LOG_LEVEL_ALL = (LOG_LEVEL_TRACE - 10);
172
173 /** Enable no logging levels */
174 public static final int LOG_LEVEL_OFF = (LOG_LEVEL_ERROR + 10);
175
176
177 private static String getStringProperty(String name) {
178 String prop = null;
179 try {
180 prop = System.getProperty(name);
181 } catch (SecurityException e) {
182 ; // Ignore
183 }
184 return (prop == null) ? simpleLoggerProps.getProperty(name) : prop;
185 }
186
187 private static String getStringProperty(String name, String defaultValue) {
188 String prop = getStringProperty(name);
189 return (prop == null) ? defaultValue : prop;
190 }
191
192 private static boolean getBooleanProperty(String name, boolean defaultValue) {
193 String prop = getStringProperty(name);
194 return (prop == null) ? defaultValue : "true".equalsIgnoreCase(prop);
195 }
196
197 // Initialize class attributes.
198 // Load properties file, if found.
199 // Override with system properties.
200 static {
201 // Add props from the resource simplelogger.properties
202 InputStream in = (InputStream)AccessController.doPrivileged(
203 new PrivilegedAction() {
204 public Object run() {
205 ClassLoader threadCL = Thread.currentThread().getContextClassLoader();
206 if (threadCL != null) {
207 return threadCL.getResourceAsStream(CONFIGURATION_FILE);
208 } else {
209 return ClassLoader.getSystemResourceAsStream(CONFIGURATION_FILE);
210 }
211 }
212 });
213 if(null != in) {
214 try {
215 simpleLoggerProps.load(in);
216 in.close();
217 } catch(java.io.IOException e) {
218 // ignored
219 }
220 }
221
222 showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName);
223 showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName);
224 showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime);
225 showThreadName = getBooleanProperty(systemPrefix + "showthreadname", showThreadName);
226 dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat", dateTimeFormat);
227
228 if(showDateTime) {
229 try {
230 dateFormatter = new SimpleDateFormat(dateTimeFormat);
231 } catch(IllegalArgumentException e) {
232 Util.report("Bad date format in " + CONFIGURATION_FILE + "; reverting to default", e);
233 // If the format pattern is invalid - use the default format
234 dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
235 dateFormatter = new SimpleDateFormat(dateTimeFormat);
236 }
237 }
238 }
239
240
241 /** The name of this simple log instance */
242 //protected String logName = null;// == name
243 /** The current log level */
244 protected int currentLogLevel = LOG_LEVEL_INFO;
245 /** The short name of this simple log instance */
246 private transient String shortLogName = null;
247
248 /**
249 * Package access allows only {@link SimpleLoggerFactory} to instantiate
250 * SimpleLogger instances.
251 */
252 SimpleLogger(String name) {
253 this.name = name;
254
255 // Set initial log level
256 this.currentLogLevel = LOG_LEVEL_INFO;
257
258 // Set log level from properties
259 String lvl = getStringProperty(systemPrefix + "log." + name);
260 int i = String.valueOf(name).lastIndexOf(".");
261 while(null == lvl && i > -1) {
262 name = name.substring(0,i);
263 lvl = getStringProperty(systemPrefix + "log." + name);
264 i = String.valueOf(name).lastIndexOf(".");
265 }
266
267 if(null == lvl) {
268 lvl = getStringProperty(systemPrefix + "defaultlog");
269 }
270
271 if("all".equalsIgnoreCase(lvl)) {
272 this.currentLogLevel = LOG_LEVEL_ALL;
273 } else if("trace".equalsIgnoreCase(lvl)) {
274 this.currentLogLevel = LOG_LEVEL_TRACE;
275 } else if("debug".equalsIgnoreCase(lvl)) {
276 this.currentLogLevel = LOG_LEVEL_DEBUG;
277 } else if("info".equalsIgnoreCase(lvl)) {
278 this.currentLogLevel = LOG_LEVEL_INFO;
279 } else if("warn".equalsIgnoreCase(lvl)) {
280 this.currentLogLevel = LOG_LEVEL_WARN;
281 } else if("error".equalsIgnoreCase(lvl)) {
282 this.currentLogLevel = LOG_LEVEL_ERROR;
283 // } else if("fatal".equalsIgnoreCase(lvl)) {
284 // setLevel(LOG_LEVEL_FATAL);
285 } else if("off".equalsIgnoreCase(lvl)) {
286 this.currentLogLevel = LOG_LEVEL_OFF;
287 }
288 }
289
290
291 /**
292 * This is our internal implementation for logging regular (non-parameterized)
293 * log messages.
294 *
295 * @param level One of the LOG_LEVEL_XXX constants defining the log level
296 * @param message The message itself
297 * @param t The exception whose stack trace should be logged
298 */
299 private void log(int level, String message, Throwable t) {
300 if (! isLevelEnabled(level)) {
301 return;
302 }
303
304 StringBuffer buf = new StringBuffer(32);
305
306 // Append date-time if so configured
307 if(showDateTime) {
308 Date now = new Date();
309 String dateText;
310 synchronized(dateFormatter) {
311 dateText = dateFormatter.format(now);
312 }
313 buf.append(dateText);
314 buf.append(' ');
315 } else {
316 buf.append(System.currentTimeMillis() - startTime);
317 buf.append(' ');
318 }
319
320 // Append current thread name if so configured
321 if (showThreadName) {
322 buf.append('[');
323 buf.append(Thread.currentThread().getName());
324 buf.append("] ");
325 }
326
327 // Append a readable representation of the log level
328 switch(level) {
329 case LOG_LEVEL_TRACE: buf.append("TRACE"); break;
330 case LOG_LEVEL_DEBUG: buf.append("DEBUG"); break;
331 case LOG_LEVEL_INFO: buf.append("INFO"); break;
332 case LOG_LEVEL_WARN: buf.append("WARN"); break;
333 case LOG_LEVEL_ERROR: buf.append("ERROR"); break;
334 // case LOG_LEVEL_FATAL: buf.append("[FATAL] "); break;
335 }
336 buf.append(' ');
337
338 // Append the name of the log instance if so configured
339 if(showShortName) {
340 if(shortLogName==null) {
341 // Cut all but the last component of the name for both styles
342 shortLogName = name.substring(name.lastIndexOf(".") + 1);
343 shortLogName =
344 shortLogName.substring(shortLogName.lastIndexOf("/") + 1);
345 }
346 buf.append(String.valueOf(shortLogName)).append(" - ");
347 } else if(showLogName) {
348 buf.append(String.valueOf(name)).append(" - ");
349 }
350
351 // Append the message
352 buf.append(message);
353
354 System.err.println(buf.toString());
355 // Append stack trace if not null
356 if (t != null) {
357 t.printStackTrace(System.err);
358 }
359 System.err.flush();
360 }
361
362 /**
363 * For formatted messages, first substitute arguments and then log.
364 *
365 * @param level
366 * @param format
367 * @param param1
368 * @param param2
369 */
370 private void formatAndLog(int level, String format, Object arg1,
371 Object arg2) {
372 if (! isLevelEnabled(level)) {
373 return;
374 }
375 FormattingTuple tp = MessageFormatter.format(format, arg1, arg2);
376 log(level, tp.getMessage(), tp.getThrowable());
377 }
378
379 /**
380 * For formatted messages, first substitute arguments and then log.
381 *
382 * @param level
383 * @param format
384 * @param argArray
385 */
386 private void formatAndLog(int level, String format, Object[] argArray) {
387 if (! isLevelEnabled(level)) {
388 return;
389 }
390 FormattingTuple tp = MessageFormatter.arrayFormat(format, argArray);
391 log(level, tp.getMessage(), tp.getThrowable());
392 }
393
394 /**
395 * Is the given log level currently enabled?
396 *
397 * @param logLevel is this level enabled?
398 */
399 protected boolean isLevelEnabled(int logLevel) {
400 // log level are numerically ordered so can use simple numeric
401 // comparison
402 return (logLevel >= currentLogLevel);
403 }
404
405
406 /**
407 * Are {@code trace} messages currently enabled?
408 */
409 public boolean isTraceEnabled() {
410 return isLevelEnabled(LOG_LEVEL_TRACE);
411 }
412
413 /**
414 * A simple implementation which logs messages of level TRACE according
415 * to the format outlined above.
416 */
417 public void trace(String msg) {
418 log(LOG_LEVEL_TRACE, msg, null);
419 }
420
421 /**
422 * Perform single parameter substitution before logging the message of level
423 * TRACE according to the format outlined above.
424 */
425 public void trace(String format, Object param1) {
426 formatAndLog(LOG_LEVEL_TRACE, format, param1, null);
427 }
428
429 /**
430 * Perform double parameter substitution before logging the message of level
431 * TRACE according to the format outlined above.
432 */
433 public void trace(String format, Object param1, Object param2) {
434 formatAndLog(LOG_LEVEL_TRACE, format, param1, param2);
435 }
436
437 /**
438 * Perform double parameter substitution before logging the message of level
439 * TRACE according to the format outlined above.
440 */
441 public void trace(String format, Object[] argArray) {
442 formatAndLog(LOG_LEVEL_TRACE, format, argArray);
443 }
444
445 /**
446 * Log a message of level TRACE, including an exception.
447 */
448 public void trace(String msg, Throwable t) {
449 log(LOG_LEVEL_TRACE, msg, t);
450 }
451
452 /**
453 * Are {@code debug} messages currently enabled?
454 */
455 public boolean isDebugEnabled() {
456 return isLevelEnabled(LOG_LEVEL_DEBUG);
457 }
458
459 /**
460 * A simple implementation which logs messages of level DEBUG according
461 * to the format outlined above.
462 */
463 public void debug(String msg) {
464 log(LOG_LEVEL_DEBUG, msg, null);
465 }
466
467 /**
468 * Perform single parameter substitution before logging the message of level
469 * DEBUG according to the format outlined above.
470 */
471 public void debug(String format, Object param1) {
472 formatAndLog(LOG_LEVEL_DEBUG, format, param1, null);
473 }
474
475 /**
476 * Perform double parameter substitution before logging the message of level
477 * DEBUG according to the format outlined above.
478 */
479 public void debug(String format, Object param1, Object param2) {
480 formatAndLog(LOG_LEVEL_DEBUG, format, param1, param2);
481 }
482
483 /**
484 * Perform double parameter substitution before logging the message of level
485 * DEBUG according to the format outlined above.
486 */
487 public void debug(String format, Object[] argArray) {
488 formatAndLog(LOG_LEVEL_DEBUG, format, argArray);
489 }
490
491 /**
492 * Log a message of level DEBUG, including an exception.
493 */
494 public void debug(String msg, Throwable t) {
495 log(LOG_LEVEL_DEBUG, msg, t);
496 }
497
498 /**
499 * Are {@code info} messages currently enabled?
500 */
501 public boolean isInfoEnabled() {
502 return isLevelEnabled(LOG_LEVEL_INFO);
503 }
504
505 /**
506 * A simple implementation which logs messages of level INFO according
507 * to the format outlined above.
508 */
509 public void info(String msg) {
510 log(LOG_LEVEL_INFO, msg, null);
511 }
512
513 /**
514 * Perform single parameter substitution before logging the message of level
515 * INFO according to the format outlined above.
516 */
517 public void info(String format, Object arg) {
518 formatAndLog(LOG_LEVEL_INFO, format, arg, null);
519 }
520
521 /**
522 * Perform double parameter substitution before logging the message of level
523 * INFO according to the format outlined above.
524 */
525 public void info(String format, Object arg1, Object arg2) {
526 formatAndLog(LOG_LEVEL_INFO, format, arg1, arg2);
527 }
528
529 /**
530 * Perform double parameter substitution before logging the message of level
531 * INFO according to the format outlined above.
532 */
533 public void info(String format, Object[] argArray) {
534 formatAndLog(LOG_LEVEL_INFO, format, argArray);
535 }
536
537 /**
538 * Log a message of level INFO, including an exception.
539 */
540 public void info(String msg, Throwable t) {
541 log(LOG_LEVEL_INFO, msg, t);
542 }
543
544 /**
545 * Are {@code warn} messages currently enabled?
546 */
547 public boolean isWarnEnabled() {
548 return isLevelEnabled(LOG_LEVEL_WARN);
549 }
550
551 /**
552 * A simple implementation which always logs messages of level WARN according
553 * to the format outlined above.
554 */
555 public void warn(String msg) {
556 log(LOG_LEVEL_WARN, msg, null);
557 }
558
559 /**
560 * Perform single parameter substitution before logging the message of level
561 * WARN according to the format outlined above.
562 */
563 public void warn(String format, Object arg) {
564 formatAndLog(LOG_LEVEL_WARN, format, arg, null);
565 }
566
567 /**
568 * Perform double parameter substitution before logging the message of level
569 * WARN according to the format outlined above.
570 */
571 public void warn(String format, Object arg1, Object arg2) {
572 formatAndLog(LOG_LEVEL_WARN, format, arg1, arg2);
573 }
574
575 /**
576 * Perform double parameter substitution before logging the message of level
577 * WARN according to the format outlined above.
578 */
579 public void warn(String format, Object[] argArray) {
580 formatAndLog(LOG_LEVEL_WARN, format, argArray);
581 }
582
583 /**
584 * Log a message of level WARN, including an exception.
585 */
586 public void warn(String msg, Throwable t) {
587 log(LOG_LEVEL_WARN, msg, t);
588 }
589
590 /**
591 * Are {@code error} messages currently enabled?
592 */
593 public boolean isErrorEnabled() {
594 return isLevelEnabled(LOG_LEVEL_ERROR);
595 }
596
597 /**
598 * A simple implementation which always logs messages of level ERROR according
599 * to the format outlined above.
600 */
601 public void error(String msg) {
602 log(LOG_LEVEL_ERROR, msg, null);
603 }
604
605 /**
606 * Perform single parameter substitution before logging the message of level
607 * ERROR according to the format outlined above.
608 */
609 public void error(String format, Object arg) {
610 formatAndLog(LOG_LEVEL_ERROR, format, arg, null);
611 }
612
613 /**
614 * Perform double parameter substitution before logging the message of level
615 * ERROR according to the format outlined above.
616 */
617 public void error(String format, Object arg1, Object arg2) {
618 formatAndLog(LOG_LEVEL_ERROR, format, arg1, arg2);
619 }
620
621 /**
622 * Perform double parameter substitution before logging the message of level
623 * ERROR according to the format outlined above.
624 */
625 public void error(String format, Object[] argArray) {
626 formatAndLog(LOG_LEVEL_ERROR, format, argArray);
627 }
628
629 /**
630 * Log a message of level ERROR, including an exception.
631 */
632 public void error(String msg, Throwable t) {
633 log(LOG_LEVEL_ERROR, msg, t);
634 }
635 }