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