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ülcü
138 * @author Scott Sanders
139 * @author Rod Waldhoff
140 * @author Robert Burrell Donkin
141 * @author Cé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 }