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