View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.slf4j;
20  
21  import java.io.PrintStream;
22  import java.util.ArrayList;
23  import java.util.Date;
24  import java.util.List;
25  
26  import org.slf4j.Logger;
27  import org.slf4j.Marker;
28  import org.slf4j.event.Level;
29  import org.slf4j.event.LoggingEvent;
30  import org.slf4j.helpers.LegacyAbstractLogger;
31  import org.slf4j.helpers.MessageFormatter;
32  import org.slf4j.helpers.NormalizedParameters;
33  import org.slf4j.spi.LocationAwareLogger;
34  
35  /**
36   * <p>
37   * Simple implementation of {@link Logger} that sends all enabled log messages,
38   * for all defined loggers, to the console ({@code System.err}). The following
39   * system properties are supported to configure the behavior of this logger:
40   *
41   *
42   * <ul>
43   * <li><code>org.slf4j.simpleLogger.logFile</code> - The output target which can
44   * be the <em>path</em> to a file, or the special values "System.out" and
45   * "System.err". Default is "System.err".</li>
46   *
47   * <li><code>org.slf4j.simpleLogger.cacheOutputStream</code> - If the output
48   * target is set to "System.out" or "System.err" (see preceding entry), by
49   * default, logs will be output to the latest value referenced by
50   * <code>System.out/err</code> variables. By setting this parameter to true, the
51   * output stream will be cached, i.e. assigned once at initialization time and
52   * re-used independently of the current value referenced by
53   * <code>System.out/err</code>.</li>
54   *
55   * <li><code>org.slf4j.simpleLogger.defaultLogLevel</code> - Default log level
56   * for all instances of SimpleLogger. Must be one of ("trace", "debug", "info",
57   * "warn", "error" or "off"). If not specified, defaults to "info".</li>
58   *
59   * <li><code>org.slf4j.simpleLogger.log.<em>a.b.c</em></code> - Logging detail
60   * level for a SimpleLogger instance named "a.b.c". Right-side value must be one
61   * of "trace", "debug", "info", "warn", "error" or "off". When a SimpleLogger
62   * named "a.b.c" is initialized, its level is assigned from this property. If
63   * unspecified, the level of nearest parent logger will be used, and if none is
64   * set, then the value specified by
65   * <code>org.slf4j.simpleLogger.defaultLogLevel</code> will be used.</li>
66   *
67   * <li><code>org.slf4j.simpleLogger.showDateTime</code> - Set to
68   * <code>true</code> if you want the current date and time to be included in
69   * output messages. Default is <code>false</code></li>
70   *
71   * <li><code>org.slf4j.simpleLogger.dateTimeFormat</code> - The date and time
72   * format to be used in the output messages. The pattern describing the date and
73   * time format is defined by <a href=
74   * "http://docs.oracle.com/javase/1.5.0/docs/api/java/text/SimpleDateFormat.html">
75   * <code>SimpleDateFormat</code></a>. If the format is not specified or is
76   * invalid, the number of milliseconds since start up will be output.</li>
77   *
78   * <li><code>org.slf4j.simpleLogger.showThreadName</code> -Set to
79   * <code>true</code> if you want to output the current thread name. Defaults to
80   * <code>true</code>.</li>
81   *
82   * <li>(since version 1.7.33 and 2.0.0-alpha6) <code>org.slf4j.simpleLogger.showThreadId</code> -
83   * If you would like to output the current thread id, then set to
84   * <code>true</code>. Defaults to <code>false</code>.</li>
85   *
86   * <li><code>org.slf4j.simpleLogger.showLogName</code> - Set to
87   * <code>true</code> if you want the Logger instance name to be included in
88   * output messages. Defaults to <code>true</code>.</li>
89   *
90   * <li><code>org.slf4j.simpleLogger.showShortLogName</code> - Set to
91   * <code>true</code> if you want the last component of the name to be included
92   * in output messages. Defaults to <code>false</code>.</li>
93   *
94   * <li><code>org.slf4j.simpleLogger.levelInBrackets</code> - Should the level
95   * string be output in brackets? Defaults to <code>false</code>.</li>
96   *
97   * <li><code>org.slf4j.simpleLogger.warnLevelString</code> - The string value
98   * output for the warn level. Defaults to <code>WARN</code>.</li>
99   *
100  * </ul>
101  *
102  * <p>
103  * In addition to looking for system properties with the names specified above,
104  * this implementation also checks for a class loader resource named
105  * <code>"simplelogger.properties"</code>, and includes any matching definitions
106  * from this resource (if it exists).
107  *
108  *
109  * <p>
110  * With no configuration, the default output includes the relative time in
111  * milliseconds, thread name, the level, logger name, and the message followed
112  * by the line separator for the host. In log4j terms it amounts to the "%r [%t]
113  * %level %logger - %m%n" pattern.
114  *
115  * <p>
116  * Sample output follows.
117  *
118  *
119  * <pre>
120  * 176 [main] INFO examples.Sort - Populating an array of 2 elements in reverse order.
121  * 225 [main] INFO examples.SortAlgo - Entered the sort method.
122  * 304 [main] INFO examples.SortAlgo - Dump of integer array:
123  * 317 [main] INFO examples.SortAlgo - Element [0] = 0
124  * 331 [main] INFO examples.SortAlgo - Element [1] = 1
125  * 343 [main] INFO examples.Sort - The next log statement should be an error message.
126  * 346 [main] ERROR examples.SortAlgo - Tried to dump an uninitialized array.
127  *   at org.log4j.examples.SortAlgo.dump(SortAlgo.java:58)
128  *   at org.log4j.examples.Sort.main(Sort.java:64)
129  * 467 [main] INFO  examples.Sort - Exiting main method.
130  * </pre>
131  *
132  * <p>
133  * This implementation is heavily inspired by
134  * <a href="http://commons.apache.org/logging/">Apache Commons Logging</a>'s
135  * SimpleLog.
136  *
137  *
138  * @author Ceki G&uuml;lc&uuml;
139  * @author Scott Sanders
140  * @author Rod Waldhoff
141  * @author Robert Burrell Donkin
142  * @author C&eacute;drik LIME
143  */
144 public class MavenBaseLogger extends LegacyAbstractLogger {
145 
146     private static final long serialVersionUID = -632788891211436180L;
147 
148     private static final long START_TIME = System.currentTimeMillis();
149 
150     protected static final int LOG_LEVEL_TRACE = LocationAwareLogger.TRACE_INT;
151     protected static final int LOG_LEVEL_DEBUG = LocationAwareLogger.DEBUG_INT;
152     protected static final int LOG_LEVEL_INFO = LocationAwareLogger.INFO_INT;
153     protected static final int LOG_LEVEL_WARN = LocationAwareLogger.WARN_INT;
154     protected static final int LOG_LEVEL_ERROR = LocationAwareLogger.ERROR_INT;
155 
156     static final char SP = ' ';
157     static final String TID_PREFIX = "tid=";
158 
159     // The OFF level can only be used in configuration files to disable logging.
160     // It has
161     // no printing method associated with it in o.s.Logger interface.
162     protected static final int LOG_LEVEL_OFF = LOG_LEVEL_ERROR + 10;
163 
164     static final SimpleLoggerConfiguration CONFIG_PARAMS = new SimpleLoggerConfiguration();
165 
166     private static boolean initialized = false;
167 
168     static void lazyInit() {
169         if (initialized) {
170             return;
171         }
172         initialized = true;
173         init();
174     }
175 
176     // external software might be invoking this method directly. Do not rename
177     // or change its semantics.
178     static void init() {
179         CONFIG_PARAMS.init();
180     }
181 
182     /** The current log level */
183     protected int currentLogLevel = LOG_LEVEL_INFO;
184     /** The short name of this simple log instance */
185     private transient String shortLogName = null;
186 
187     /**
188      * All system properties used by <code>SimpleLogger</code> start with this
189      * prefix
190      */
191     public static final String SYSTEM_PREFIX = "org.slf4j.simpleLogger.";
192 
193     public static final String LOG_KEY_PREFIX = MavenBaseLogger.SYSTEM_PREFIX + "log.";
194 
195     public static final String CACHE_OUTPUT_STREAM_STRING_KEY = MavenBaseLogger.SYSTEM_PREFIX + "cacheOutputStream";
196 
197     public static final String WARN_LEVEL_STRING_KEY = MavenBaseLogger.SYSTEM_PREFIX + "warnLevelString";
198 
199     public static final String LEVEL_IN_BRACKETS_KEY = MavenBaseLogger.SYSTEM_PREFIX + "levelInBrackets";
200 
201     public static final String LOG_FILE_KEY = MavenBaseLogger.SYSTEM_PREFIX + "logFile";
202 
203     public static final String SHOW_SHORT_LOG_NAME_KEY = MavenBaseLogger.SYSTEM_PREFIX + "showShortLogName";
204 
205     public static final String SHOW_LOG_NAME_KEY = MavenBaseLogger.SYSTEM_PREFIX + "showLogName";
206 
207     public static final String SHOW_THREAD_NAME_KEY = MavenBaseLogger.SYSTEM_PREFIX + "showThreadName";
208 
209     public static final String SHOW_THREAD_ID_KEY = MavenBaseLogger.SYSTEM_PREFIX + "showThreadId";
210 
211     public static final String DATE_TIME_FORMAT_KEY = MavenBaseLogger.SYSTEM_PREFIX + "dateTimeFormat";
212 
213     public static final String SHOW_DATE_TIME_KEY = MavenBaseLogger.SYSTEM_PREFIX + "showDateTime";
214 
215     public static final String DEFAULT_LOG_LEVEL_KEY = MavenBaseLogger.SYSTEM_PREFIX + "defaultLogLevel";
216 
217     /**
218      * Protected access allows only {@link MavenLoggerFactory} and also derived classes to instantiate
219      * MavenLoggerFactory instances.
220      */
221     protected MavenBaseLogger(String name) {
222         this.name = name;
223 
224         String levelString = recursivelyComputeLevelString();
225         if (levelString != null) {
226             this.currentLogLevel = SimpleLoggerConfiguration.stringToLevel(levelString);
227         } else {
228             this.currentLogLevel = CONFIG_PARAMS.defaultLogLevel;
229         }
230     }
231 
232     String recursivelyComputeLevelString() {
233         String tempName = name;
234         String levelString = null;
235         int indexOfLastDot = tempName.length();
236         while ((levelString == null) && (indexOfLastDot > -1)) {
237             tempName = tempName.substring(0, indexOfLastDot);
238             levelString = CONFIG_PARAMS.getStringProperty(MavenBaseLogger.LOG_KEY_PREFIX + tempName, null);
239             indexOfLastDot = String.valueOf(tempName).lastIndexOf(".");
240         }
241         return levelString;
242     }
243 
244     /**
245      * To avoid intermingling of log messages and associated stack traces, the two
246      * operations are done in a synchronized block.
247      *
248      * @param buf
249      * @param t
250      */
251     protected void write(StringBuilder buf, Throwable t) {
252         PrintStream targetStream = CONFIG_PARAMS.outputChoice.getTargetPrintStream();
253 
254         synchronized (CONFIG_PARAMS) {
255             targetStream.println(buf.toString());
256             writeThrowable(t, targetStream);
257             targetStream.flush();
258         }
259     }
260 
261     protected void writeThrowable(Throwable t, PrintStream targetStream) {
262         if (t != null) {
263             t.printStackTrace(targetStream);
264         }
265     }
266 
267     protected String getFormattedDate() {
268         Date now = new Date();
269         String dateText;
270         synchronized (CONFIG_PARAMS.dateFormatter) {
271             dateText = CONFIG_PARAMS.dateFormatter.format(now);
272         }
273         return dateText;
274     }
275 
276     protected String computeShortName() {
277         return name.substring(name.lastIndexOf(".") + 1);
278     }
279 
280     // /**
281     // * For formatted messages, first substitute arguments and then log.
282     // *
283     // * @param level
284     // * @param format
285     // * @param arg1
286     // * @param arg2
287     // */
288     // private void formatAndLog(int level, String format, Object arg1, Object arg2) {
289     // if (!isLevelEnabled(level)) {
290     // return;
291     // }
292     // FormattingTuple tp = MessageFormatter.format(format, arg1, arg2);
293     // log(level, tp.getMessage(), tp.getThrowable());
294     // }
295 
296     // /**
297     // * For formatted messages, first substitute arguments and then log.
298     // *
299     // * @param level
300     // * @param format
301     // * @param arguments
302     // * a list of 3 ore more arguments
303     // */
304     // private void formatAndLog(int level, String format, Object... arguments) {
305     // if (!isLevelEnabled(level)) {
306     // return;
307     // }
308     // FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments);
309     // log(level, tp.getMessage(), tp.getThrowable());
310     // }
311 
312     /**
313      * Is the given log level currently enabled?
314      *
315      * @param logLevel is this level enabled?
316      * @return whether the logger is enabled for the given level
317      */
318     protected boolean isLevelEnabled(int logLevel) {
319         // log level are numerically ordered so can use simple numeric
320         // comparison
321         return (logLevel >= currentLogLevel);
322     }
323 
324     /** Are {@code trace} messages currently enabled? */
325     public boolean isTraceEnabled() {
326         return isLevelEnabled(LOG_LEVEL_TRACE);
327     }
328 
329     /** Are {@code debug} messages currently enabled? */
330     public boolean isDebugEnabled() {
331         return isLevelEnabled(LOG_LEVEL_DEBUG);
332     }
333 
334     /** Are {@code info} messages currently enabled? */
335     public boolean isInfoEnabled() {
336         return isLevelEnabled(LOG_LEVEL_INFO);
337     }
338 
339     /** Are {@code warn} messages currently enabled? */
340     public boolean isWarnEnabled() {
341         return isLevelEnabled(LOG_LEVEL_WARN);
342     }
343 
344     /** Are {@code error} messages currently enabled? */
345     public boolean isErrorEnabled() {
346         return isLevelEnabled(LOG_LEVEL_ERROR);
347     }
348 
349     /**
350      * SimpleLogger's implementation of
351      * {@link org.slf4j.helpers.AbstractLogger#handleNormalizedLoggingCall(Level, Marker, String, Object[], Throwable) AbstractLogger#handleNormalizedLoggingCall}
352      * }
353      *
354      * @param level the SLF4J level for this event
355      * @param marker  The marker to be used for this event, may be null.
356      * @param messagePattern The message pattern which will be parsed and formatted
357      * @param arguments  the array of arguments to be formatted, may be null
358      * @param throwable  The exception whose stack trace should be logged, may be null
359      */
360     @Override
361     protected void handleNormalizedLoggingCall(
362             Level level, Marker marker, String messagePattern, Object[] arguments, Throwable throwable) {
363 
364         List<Marker> markers = null;
365 
366         if (marker != null) {
367             markers = new ArrayList<>();
368             markers.add(marker);
369         }
370 
371         innerHandleNormalizedLoggingCall(level, markers, messagePattern, arguments, throwable);
372     }
373 
374     private void innerHandleNormalizedLoggingCall(
375             Level level, List<Marker> markers, String messagePattern, Object[] arguments, Throwable t) {
376 
377         StringBuilder buf = new StringBuilder(32);
378 
379         // Append date-time if so configured
380         if (CONFIG_PARAMS.showDateTime) {
381             if (CONFIG_PARAMS.dateFormatter != null) {
382                 buf.append(getFormattedDate());
383                 buf.append(SP);
384             } else {
385                 buf.append(System.currentTimeMillis() - START_TIME);
386                 buf.append(SP);
387             }
388         }
389 
390         // Append current thread name if so configured
391         if (CONFIG_PARAMS.showThreadName) {
392             buf.append('[');
393             buf.append(Thread.currentThread().getName());
394             buf.append("] ");
395         }
396 
397         if (CONFIG_PARAMS.showThreadId) {
398             buf.append(TID_PREFIX);
399             buf.append(Thread.currentThread().getId());
400             buf.append(SP);
401         }
402 
403         if (CONFIG_PARAMS.levelInBrackets) {
404             buf.append('[');
405         }
406 
407         // Append a readable representation of the log level
408         String levelStr = renderLevel(level.toInt());
409         buf.append(levelStr);
410         if (CONFIG_PARAMS.levelInBrackets) {
411             buf.append(']');
412         }
413         buf.append(SP);
414 
415         // Append the name of the log instance if so configured
416         if (CONFIG_PARAMS.showShortLogName) {
417             if (shortLogName == null) {
418                 shortLogName = computeShortName();
419             }
420             buf.append(shortLogName).append(" - ");
421         } else if (CONFIG_PARAMS.showLogName) {
422             buf.append(name).append(" - ");
423         }
424 
425         if (markers != null) {
426             buf.append(SP);
427             for (Marker marker : markers) {
428                 buf.append(marker.getName()).append(SP);
429             }
430         }
431 
432         String formattedMessage = MessageFormatter.basicArrayFormat(messagePattern, arguments);
433 
434         // Append the message
435         buf.append(formattedMessage);
436 
437         write(buf, t);
438     }
439 
440     protected String renderLevel(int levelInt) {
441         switch (levelInt) {
442             case LOG_LEVEL_TRACE:
443                 return "TRACE";
444             case LOG_LEVEL_DEBUG:
445                 return ("DEBUG");
446             case LOG_LEVEL_INFO:
447                 return "INFO";
448             case LOG_LEVEL_WARN:
449                 return "WARN";
450             case LOG_LEVEL_ERROR:
451                 return "ERROR";
452             default:
453                 throw new IllegalStateException("Unrecognized level [" + levelInt + "]");
454         }
455     }
456 
457     public void log(LoggingEvent event) {
458         int levelInt = event.getLevel().toInt();
459 
460         if (!isLevelEnabled(levelInt)) {
461             return;
462         }
463 
464         NormalizedParameters np = NormalizedParameters.normalize(event);
465 
466         innerHandleNormalizedLoggingCall(
467                 event.getLevel(), event.getMarkers(), np.getMessage(), np.getArguments(), event.getThrowable());
468     }
469 
470     @Override
471     protected String getFullyQualifiedCallerName() {
472         return null;
473     }
474 }