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 * Is the given log level currently enabled?
248 *
249 * @param logLevel is this level enabled?
250 * @return whether the logger is enabled for the given level
251 */
252 protected boolean isLevelEnabled(int logLevel) {
253 // log level are numerically ordered so can use simple numeric
254 // comparison
255 return (logLevel >= currentLogLevel);
256 }
257
258 /** Are {@code trace} messages currently enabled? */
259 @Override
260 public boolean isTraceEnabled() {
261 return isLevelEnabled(LOG_LEVEL_TRACE);
262 }
263
264 /** Are {@code debug} messages currently enabled? */
265 @Override
266 public boolean isDebugEnabled() {
267 return isLevelEnabled(LOG_LEVEL_DEBUG);
268 }
269
270 /** Are {@code info} messages currently enabled? */
271 @Override
272 public boolean isInfoEnabled() {
273 return isLevelEnabled(LOG_LEVEL_INFO);
274 }
275
276 /** Are {@code warn} messages currently enabled? */
277 @Override
278 public boolean isWarnEnabled() {
279 return isLevelEnabled(LOG_LEVEL_WARN);
280 }
281
282 /** Are {@code error} messages currently enabled? */
283 @Override
284 public boolean isErrorEnabled() {
285 return isLevelEnabled(LOG_LEVEL_ERROR);
286 }
287
288 /**
289 * SimpleLogger's implementation of
290 * {@link org.slf4j.helpers.AbstractLogger#handleNormalizedLoggingCall(Level, Marker, String, Object[], Throwable) AbstractLogger#handleNormalizedLoggingCall}
291 * }
292 *
293 * @param level the SLF4J level for this event
294 * @param marker The marker to be used for this event, may be null.
295 * @param messagePattern The message pattern which will be parsed and formatted
296 * @param arguments the array of arguments to be formatted, may be null
297 * @param throwable The exception whose stack trace should be logged, may be null
298 */
299 @Override
300 protected void handleNormalizedLoggingCall(
301 Level level, Marker marker, String messagePattern, Object[] arguments, Throwable throwable) {
302
303 List<Marker> markers = null;
304
305 if (marker != null) {
306 markers = new ArrayList<>();
307 markers.add(marker);
308 }
309
310 innerHandleNormalizedLoggingCall(level, markers, messagePattern, arguments, throwable);
311 }
312
313 private void innerHandleNormalizedLoggingCall(
314 Level level, List<Marker> markers, String messagePattern, Object[] arguments, Throwable t) {
315
316 StringBuilder buf = new StringBuilder(32);
317
318 // Append date-time if so configured
319 if (CONFIG_PARAMS.showDateTime) {
320 DateTimeFormatter formatter = CONFIG_PARAMS.dateFormatter;
321 if (formatter != null) {
322 ZonedDateTime zonedDateTime = MonotonicClock.now().atZone(ZoneId.systemDefault());
323 String dateText = formatter.format(zonedDateTime);
324 buf.append(dateText);
325 buf.append(SP);
326 } else {
327 buf.append(MonotonicClock.elapsed().toMillis());
328 buf.append(SP);
329 }
330 }
331
332 // Append current thread name if so configured
333 if (CONFIG_PARAMS.showThreadName) {
334 buf.append('[');
335 buf.append(Thread.currentThread().getName());
336 buf.append("] ");
337 }
338
339 if (CONFIG_PARAMS.showThreadId) {
340 buf.append(TID_PREFIX);
341 buf.append(Thread.currentThread().getId());
342 buf.append(SP);
343 }
344
345 if (CONFIG_PARAMS.levelInBrackets) {
346 buf.append('[');
347 }
348
349 // Append a readable representation of the log level
350 String levelStr = renderLevel(level.toInt());
351 buf.append(levelStr);
352 if (CONFIG_PARAMS.levelInBrackets) {
353 buf.append(']');
354 }
355 buf.append(SP);
356
357 // Append the name of the log instance if so configured
358 if (CONFIG_PARAMS.showShortLogName) {
359 if (shortLogName == null) {
360 shortLogName = computeShortName();
361 }
362 buf.append(shortLogName).append(" - ");
363 } else if (CONFIG_PARAMS.showLogName) {
364 buf.append(name).append(" - ");
365 }
366
367 if (markers != null) {
368 buf.append(SP);
369 for (Marker marker : markers) {
370 buf.append(marker.getName()).append(SP);
371 }
372 }
373
374 String formattedMessage = MessageFormatter.basicArrayFormat(messagePattern, arguments);
375
376 // Append the message
377 buf.append(formattedMessage);
378
379 write(buf, t);
380 }
381
382 protected String renderLevel(int levelInt) {
383 return switch (levelInt) {
384 case LOG_LEVEL_TRACE -> "TRACE";
385 case LOG_LEVEL_DEBUG -> ("DEBUG");
386 case LOG_LEVEL_INFO -> "INFO";
387 case LOG_LEVEL_WARN -> "WARN";
388 case LOG_LEVEL_ERROR -> "ERROR";
389 default -> throw new IllegalStateException("Unrecognized level [" + levelInt + "]");
390 };
391 }
392
393 public void log(LoggingEvent event) {
394 int levelInt = event.getLevel().toInt();
395
396 if (!isLevelEnabled(levelInt)) {
397 return;
398 }
399
400 NormalizedParameters np = NormalizedParameters.normalize(event);
401
402 innerHandleNormalizedLoggingCall(
403 event.getLevel(), event.getMarkers(), np.getMessage(), np.getArguments(), event.getThrowable());
404 }
405
406 @Override
407 protected String getFullyQualifiedCallerName() {
408 return null;
409 }
410 }