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.ZoneId;
23 import java.time.ZonedDateTime;
24 import java.time.format.DateTimeFormatter;
25 import java.util.ArrayList;
26 import java.util.List;
27
28 import org.apache.maven.api.Constants;
29 import org.apache.maven.api.MonotonicClock;
30 import org.slf4j.Logger;
31 import org.slf4j.Marker;
32 import org.slf4j.event.Level;
33 import org.slf4j.event.LoggingEvent;
34 import org.slf4j.helpers.LegacyAbstractLogger;
35 import org.slf4j.helpers.MessageFormatter;
36 import org.slf4j.helpers.NormalizedParameters;
37 import org.slf4j.spi.LocationAwareLogger;
38
39 /**
40 * <p>
41 * Simple implementation of {@link Logger} that sends all enabled log messages,
42 * for all defined loggers, to the console ({@code System.err}). The following
43 * system properties are supported to configure the behavior of this logger:
44 *
45 *
46 * <ul>
47 * <li><code>maven.logger.logFile</code> - The output target which can
48 * be the <em>path</em> to a file, or the special values "System.out" and
49 * "System.err". Default is "System.err".</li>
50 *
51 * <li><code>maven.logger.cacheOutputStream</code> - If the output
52 * target is set to "System.out" or "System.err" (see preceding entry), by
53 * default, logs will be output to the latest value referenced by
54 * <code>System.out/err</code> variables. By setting this parameter to true, the
55 * output stream will be cached, i.e. assigned once at initialization time and
56 * re-used independently of the current value referenced by
57 * <code>System.out/err</code>.</li>
58 *
59 * <li><code>maven.logger.defaultLogLevel</code> - Default log level
60 * for all instances of SimpleLogger. Must be one of ("trace", "debug", "info",
61 * "warn", "error" or "off"). If not specified, defaults to "info".</li>
62 *
63 * <li><code>maven.logger.log.<em>a.b.c</em></code> - Logging detail
64 * level for a SimpleLogger instance named "a.b.c". Right-side value must be one
65 * of "trace", "debug", "info", "warn", "error" or "off". When a SimpleLogger
66 * named "a.b.c" is initialized, its level is assigned from this property. If
67 * unspecified, the level of nearest parent logger will be used, and if none is
68 * set, then the value specified by
69 * <code>maven.logger.defaultLogLevel</code> will be used.</li>
70 *
71 * <li><code>maven.logger.showDateTime</code> - Set to
72 * <code>true</code> if you want the current date and time to be included in
73 * output messages. Default is <code>false</code></li>
74 *
75 * <li><code>maven.logger.dateTimeFormat</code> - The date and time
76 * format to be used in the output messages. The pattern describing the date and
77 * time format is defined by <a href=
78 * "http://docs.oracle.com/javase/1.5.0/docs/api/java/text/SimpleDateFormat.html">
79 * <code>SimpleDateFormat</code></a>. If the format is not specified or is
80 * invalid, the number of milliseconds since start up will be output.</li>
81 *
82 * <li><code>maven.logger.showThreadName</code> -Set to
83 * <code>true</code> if you want to output the current thread name. Defaults to
84 * <code>true</code>.</li>
85 *
86 * <li>(since version 1.7.33 and 2.0.0-alpha6) <code>maven.logger.showThreadId</code> -
87 * If you would like to output the current thread id, then set to
88 * <code>true</code>. Defaults to <code>false</code>.</li>
89 *
90 * <li><code>maven.logger.showLogName</code> - Set to
91 * <code>true</code> if you want the Logger instance name to be included in
92 * output messages. Defaults to <code>true</code>.</li>
93 *
94 * <li><code>maven.logger.showShortLogName</code> - Set to
95 * <code>true</code> if you want the last component of the name to be included
96 * in output messages. Defaults to <code>false</code>.</li>
97 *
98 * <li><code>maven.logger.levelInBrackets</code> - Should the level
99 * string be output in brackets? Defaults to <code>false</code>.</li>
100 *
101 * <li><code>maven.logger.warnLevelString</code> - The string value
102 * output for the warn level. Defaults to <code>WARN</code>.</li>
103 *
104 * </ul>
105 *
106 * <p>
107 * In addition to looking for system properties with the names specified above,
108 * this implementation also checks for a class loader resource named
109 * <code>"simplelogger.properties"</code>, and includes any matching definitions
110 * from this resource (if it exists).
111 *
112 *
113 * <p>
114 * With no configuration, the default output includes the relative time in
115 * milliseconds, thread name, the level, logger name, and the message followed
116 * by the line separator for the host. In log4j terms it amounts to the "%r [%t]
117 * %level %logger - %m%n" pattern.
118 *
119 * <p>
120 * Sample output follows.
121 *
122 *
123 * <pre>
124 * 176 [main] INFO examples.Sort - Populating an array of 2 elements in reverse order.
125 * 225 [main] INFO examples.SortAlgo - Entered the sort method.
126 * 304 [main] INFO examples.SortAlgo - Dump of integer array:
127 * 317 [main] INFO examples.SortAlgo - Element [0] = 0
128 * 331 [main] INFO examples.SortAlgo - Element [1] = 1
129 * 343 [main] INFO examples.Sort - The next log statement should be an error message.
130 * 346 [main] ERROR examples.SortAlgo - Tried to dump an uninitialized array.
131 * at org.log4j.examples.SortAlgo.dump(SortAlgo.java:58)
132 * at org.log4j.examples.Sort.main(Sort.java:64)
133 * 467 [main] INFO examples.Sort - Exiting main method.
134 * </pre>
135 *
136 * <p>
137 * This implementation is heavily inspired by
138 * <a href="http://commons.apache.org/logging/">Apache Commons Logging</a>'s
139 * SimpleLog.
140 *
141 *
142 * @author Ceki Gülcü
143 * @author Scott Sanders
144 * @author Rod Waldhoff
145 * @author Robert Burrell Donkin
146 * @author Cédrik LIME
147 */
148 public class MavenBaseLogger extends LegacyAbstractLogger {
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 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 * Legacy SLF4J prefix maintained for backwards compatibility
189 */
190 public static final String LEGACY_PREFIX = "org.slf4j.simpleLogger.";
191
192 /**
193 * Protected access allows only {@link MavenLoggerFactory} and also derived classes to instantiate
194 * MavenLoggerFactory instances.
195 */
196 protected MavenBaseLogger(String name) {
197 this.name = name;
198
199 String levelString = recursivelyComputeLevelString();
200 if (levelString != null) {
201 this.currentLogLevel = SimpleLoggerConfiguration.stringToLevel(levelString);
202 } else {
203 this.currentLogLevel = CONFIG_PARAMS.defaultLogLevel;
204 }
205 }
206
207 String recursivelyComputeLevelString() {
208 String tempName = name;
209 String levelString = null;
210 int indexOfLastDot = tempName.length();
211 while ((levelString == null) && (indexOfLastDot > -1)) {
212 tempName = tempName.substring(0, indexOfLastDot);
213 levelString = CONFIG_PARAMS.getStringProperty(Constants.MAVEN_LOGGER_LOG_PREFIX + tempName, null);
214 indexOfLastDot = tempName.lastIndexOf(".");
215 }
216 return levelString;
217 }
218
219 /**
220 * To avoid intermingling of log messages and associated stack traces, the two
221 * operations are done in a synchronized block.
222 *
223 * @param buf The StringBuilder containing the log message to be written
224 * @param t The Throwable object whose stack trace should be written, may be null
225 */
226 protected void write(StringBuilder buf, Throwable t) {
227 PrintStream targetStream = CONFIG_PARAMS.outputChoice.getTargetPrintStream();
228
229 synchronized (CONFIG_PARAMS) {
230 targetStream.println(buf.toString());
231 writeThrowable(t, targetStream);
232 targetStream.flush();
233 }
234 }
235
236 protected void writeThrowable(Throwable t, PrintStream targetStream) {
237 if (t != null) {
238 t.printStackTrace(targetStream);
239 }
240 }
241
242 protected String computeShortName() {
243 return name.substring(name.lastIndexOf(".") + 1);
244 }
245
246 // /**
247 // * For formatted messages, first substitute arguments and then log.
248 // *
249 // * @param level
250 // * @param format
251 // * @param arg1
252 // * @param arg2
253 // */
254 // private void formatAndLog(int level, String format, Object arg1, Object arg2) {
255 // if (!isLevelEnabled(level)) {
256 // return;
257 // }
258 // FormattingTuple tp = MessageFormatter.format(format, arg1, arg2);
259 // log(level, tp.getMessage(), tp.getThrowable());
260 // }
261
262 // /**
263 // * For formatted messages, first substitute arguments and then log.
264 // *
265 // * @param level
266 // * @param format
267 // * @param arguments
268 // * a list of 3 ore more arguments
269 // */
270 // private void formatAndLog(int level, String format, Object... arguments) {
271 // if (!isLevelEnabled(level)) {
272 // return;
273 // }
274 // FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments);
275 // log(level, tp.getMessage(), tp.getThrowable());
276 // }
277
278 /**
279 * Is the given log level currently enabled?
280 *
281 * @param logLevel is this level enabled?
282 * @return whether the logger is enabled for the given level
283 */
284 protected boolean isLevelEnabled(int logLevel) {
285 // log level are numerically ordered so can use simple numeric
286 // comparison
287 return (logLevel >= currentLogLevel);
288 }
289
290 /** Are {@code trace} messages currently enabled? */
291 public boolean isTraceEnabled() {
292 return isLevelEnabled(LOG_LEVEL_TRACE);
293 }
294
295 /** Are {@code debug} messages currently enabled? */
296 public boolean isDebugEnabled() {
297 return isLevelEnabled(LOG_LEVEL_DEBUG);
298 }
299
300 /** Are {@code info} messages currently enabled? */
301 public boolean isInfoEnabled() {
302 return isLevelEnabled(LOG_LEVEL_INFO);
303 }
304
305 /** Are {@code warn} messages currently enabled? */
306 public boolean isWarnEnabled() {
307 return isLevelEnabled(LOG_LEVEL_WARN);
308 }
309
310 /** Are {@code error} messages currently enabled? */
311 public boolean isErrorEnabled() {
312 return isLevelEnabled(LOG_LEVEL_ERROR);
313 }
314
315 /**
316 * SimpleLogger's implementation of
317 * {@link org.slf4j.helpers.AbstractLogger#handleNormalizedLoggingCall(Level, Marker, String, Object[], Throwable) AbstractLogger#handleNormalizedLoggingCall}
318 * }
319 *
320 * @param level the SLF4J level for this event
321 * @param marker The marker to be used for this event, may be null.
322 * @param messagePattern The message pattern which will be parsed and formatted
323 * @param arguments the array of arguments to be formatted, may be null
324 * @param throwable The exception whose stack trace should be logged, may be null
325 */
326 @Override
327 protected void handleNormalizedLoggingCall(
328 Level level, Marker marker, String messagePattern, Object[] arguments, Throwable throwable) {
329
330 List<Marker> markers = null;
331
332 if (marker != null) {
333 markers = new ArrayList<>();
334 markers.add(marker);
335 }
336
337 innerHandleNormalizedLoggingCall(level, markers, messagePattern, arguments, throwable);
338 }
339
340 private void innerHandleNormalizedLoggingCall(
341 Level level, List<Marker> markers, String messagePattern, Object[] arguments, Throwable t) {
342
343 StringBuilder buf = new StringBuilder(32);
344
345 // Append date-time if so configured
346 if (CONFIG_PARAMS.showDateTime) {
347 DateTimeFormatter formatter = CONFIG_PARAMS.dateFormatter;
348 if (formatter != null) {
349 ZonedDateTime zonedDateTime = MonotonicClock.now().atZone(ZoneId.systemDefault());
350 String dateText = formatter.format(zonedDateTime);
351 buf.append(dateText);
352 buf.append(SP);
353 } else {
354 buf.append(MonotonicClock.elapsed().toMillis());
355 buf.append(SP);
356 }
357 }
358
359 // Append current thread name if so configured
360 if (CONFIG_PARAMS.showThreadName) {
361 buf.append('[');
362 buf.append(Thread.currentThread().getName());
363 buf.append("] ");
364 }
365
366 if (CONFIG_PARAMS.showThreadId) {
367 buf.append(TID_PREFIX);
368 buf.append(Thread.currentThread().getId());
369 buf.append(SP);
370 }
371
372 if (CONFIG_PARAMS.levelInBrackets) {
373 buf.append('[');
374 }
375
376 // Append a readable representation of the log level
377 String levelStr = renderLevel(level.toInt());
378 buf.append(levelStr);
379 if (CONFIG_PARAMS.levelInBrackets) {
380 buf.append(']');
381 }
382 buf.append(SP);
383
384 // Append the name of the log instance if so configured
385 if (CONFIG_PARAMS.showShortLogName) {
386 if (shortLogName == null) {
387 shortLogName = computeShortName();
388 }
389 buf.append(shortLogName).append(" - ");
390 } else if (CONFIG_PARAMS.showLogName) {
391 buf.append(name).append(" - ");
392 }
393
394 if (markers != null) {
395 buf.append(SP);
396 for (Marker marker : markers) {
397 buf.append(marker.getName()).append(SP);
398 }
399 }
400
401 String formattedMessage = MessageFormatter.basicArrayFormat(messagePattern, arguments);
402
403 // Append the message
404 buf.append(formattedMessage);
405
406 write(buf, t);
407 }
408
409 protected String renderLevel(int levelInt) {
410 return switch (levelInt) {
411 case LOG_LEVEL_TRACE -> "TRACE";
412 case LOG_LEVEL_DEBUG -> ("DEBUG");
413 case LOG_LEVEL_INFO -> "INFO";
414 case LOG_LEVEL_WARN -> "WARN";
415 case LOG_LEVEL_ERROR -> "ERROR";
416 default -> throw new IllegalStateException("Unrecognized level [" + levelInt + "]");
417 };
418 }
419
420 public void log(LoggingEvent event) {
421 int levelInt = event.getLevel().toInt();
422
423 if (!isLevelEnabled(levelInt)) {
424 return;
425 }
426
427 NormalizedParameters np = NormalizedParameters.normalize(event);
428
429 innerHandleNormalizedLoggingCall(
430 event.getLevel(), event.getMarkers(), np.getMessage(), np.getArguments(), event.getThrowable());
431 }
432
433 @Override
434 protected String getFullyQualifiedCallerName() {
435 return null;
436 }
437 }