1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.logging.impl;
19
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.PrintWriter;
23 import java.io.Serializable;
24 import java.io.StringWriter;
25 import java.security.AccessController;
26 import java.security.PrivilegedAction;
27 import java.text.DateFormat;
28 import java.text.SimpleDateFormat;
29 import java.util.Date;
30 import java.util.Locale;
31 import java.util.Objects;
32 import java.util.Properties;
33
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogConfigurationException;
36
37 /**
38 * Simple implementation of Log that sends all enabled log messages,
39 * for all defined loggers, to System.err. The following system properties
40 * are supported to configure the behavior of this logger:
41 * <ul>
42 * <li>{@code org.apache.commons.logging.simplelog.defaultlog} -
43 * Default logging detail level for all instances of SimpleLog.
44 * Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").
45 * If not specified, defaults to "info". </li>
46 * <li>{@code org.apache.commons.logging.simplelog.log.xxxxx} -
47 * Logging detail level for a SimpleLog instance named "xxxxx".
48 * Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").
49 * If not specified, the default logging detail level is used.</li>
50 * <li>{@code org.apache.commons.logging.simplelog.showlogname} -
51 * Set to {@code true} if you want the Log instance name to be
52 * included in output messages. Defaults to {@code false}.</li>
53 * <li>{@code org.apache.commons.logging.simplelog.showShortLogname} -
54 * Set to {@code true} if you want the last component of the name to be
55 * included in output messages. Defaults to {@code true}.</li>
56 * <li>{@code org.apache.commons.logging.simplelog.showdatetime} -
57 * Set to {@code true} if you want the current date and time
58 * to be included in output messages. Default is {@code false}.</li>
59 * <li>{@code org.apache.commons.logging.simplelog.dateTimeFormat} -
60 * The date and time format to be used in the output messages.
61 * The pattern describing the date and time format is the same that is
62 * used in {@link java.text.SimpleDateFormat}. If the format is not
63 * specified or is invalid, the default format is used.
64 * The default format is {@code yyyy/MM/dd HH:mm:ss:SSS zzz}.</li>
65 * </ul>
66 * <p>
67 * In addition to looking for system properties with the names specified
68 * above, this implementation also checks for a class loader resource named
69 * {@code "simplelog.properties"}, and includes any matching definitions
70 * from this resource (if it exists).
71 * </p>
72 */
73 public class SimpleLog implements Log, Serializable {
74
75 /** Serializable version identifier. */
76 private static final long serialVersionUID = 136942970684951178L;
77
78 /** All system properties used by {@code SimpleLog} start with this */
79 static protected final String systemPrefix = "org.apache.commons.logging.simplelog.";
80
81 /** Properties loaded from simplelog.properties */
82 static protected final Properties simpleLogProps = new Properties();
83
84 /** The default format to use when formating dates */
85 static protected final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz";
86
87 /** Include the instance name in the log message? */
88 static volatile protected boolean showLogName;
89
90 /**
91 * Include the short name (last component) of the logger in the log
92 * message. Defaults to true - otherwise we'll be lost in a flood of
93 * messages without knowing who sends them.
94 */
95 static volatile protected boolean showShortName = true;
96
97 /** Include the current time in the log message */
98 static volatile protected boolean showDateTime;
99
100 /** The date and time format to use in the log message */
101 static volatile protected String dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
102
103 /**
104 * Used to format times.
105 * <p>
106 * Any code that accesses this object should first obtain a lock on it,
107 * that is, use synchronized(dateFormatter); this requirement was introduced
108 * in 1.1.1 to fix an existing thread safety bug (SimpleDateFormat.format
109 * is not thread-safe).
110 * </p>
111 * <p>
112 * Statically initialized to a {@link SimpleDateFormat}.
113 * </p>
114 */
115 static protected DateFormat dateFormatter;
116
117 /** "Trace" level logging. */
118 public static final int LOG_LEVEL_TRACE = 1;
119
120 /** "Debug" level logging. */
121 public static final int LOG_LEVEL_DEBUG = 2;
122
123 /** "Info" level logging. */
124 public static final int LOG_LEVEL_INFO = 3;
125
126 /** "Warn" level logging. */
127 public static final int LOG_LEVEL_WARN = 4;
128
129 /** "Error" level logging. */
130 public static final int LOG_LEVEL_ERROR = 5;
131
132 /** "Fatal" level logging. */
133 public static final int LOG_LEVEL_FATAL = 6;
134
135 /** Enable all logging levels */
136 public static final int LOG_LEVEL_ALL = LOG_LEVEL_TRACE - 1;
137
138 /** Enable no logging levels */
139 public static final int LOG_LEVEL_OFF = LOG_LEVEL_FATAL + 1;
140
141 // Initialize class attributes.
142 // Load properties file, if found.
143 // Override with system properties.
144 static {
145 // Add props from the resource simplelog.properties
146 try (InputStream in = getResourceAsStream("simplelog.properties")) {
147 if (null != in) {
148 simpleLogProps.load(in);
149 }
150 } catch (final IOException ignore) {
151 // Ignore
152 }
153 showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName);
154 showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName);
155 showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime);
156 if (showDateTime) {
157 final SimpleDateFormat simpleDateFormatter = getSimpleDateFormat();
158 dateFormatter = simpleDateFormatter;
159 dateTimeFormat = simpleDateFormatter.toPattern();
160 }
161 }
162
163 private static boolean getBooleanProperty(final String name, final boolean defaultValue) {
164 final String prop = getStringProperty(name);
165 return prop == null ? defaultValue : Boolean.parseBoolean(prop);
166 }
167
168 /**
169 * Gets the thread context class loader if available. Otherwise return null.
170 *
171 * The thread context class loader is available if certain security conditions are met.
172 *
173 * @throws LogConfigurationException if a suitable class loader cannot be identified.
174 */
175 private static ClassLoader getContextClassLoader() {
176 ClassLoader classLoader = null;
177
178 // Get the thread context class loader (if there is one)
179 try {
180 classLoader = Thread.currentThread().getContextClassLoader();
181 } catch (final RuntimeException e) {
182
183 /**
184 * getContextClassLoader() throws SecurityException when the context class loader isn't an ancestor of the calling class's class loader, or if
185 * security permissions are restricted.
186 *
187 * In the first case (not related), we want to ignore and keep going. We cannot help but also ignore the second with the logic below, but other
188 * calls elsewhere (to obtain a class loader) will trigger this exception where we can make a distinction.
189 */
190 // Capture 'e.getTargetException()' exception for details
191 // alternate: log 'e.getTargetException()', and pass back 'e'.
192 if (!(e instanceof SecurityException)) {
193 throw new LogConfigurationException("Unexpected SecurityException", e);
194 }
195 }
196
197 if (classLoader == null) {
198 classLoader = SimpleLog.class.getClassLoader();
199 }
200
201 // Return the selected class loader
202 return classLoader;
203 }
204
205 private static InputStream getResourceAsStream(final String name) {
206 return AccessController.doPrivileged((PrivilegedAction<InputStream>) () -> {
207 final ClassLoader threadCL = getContextClassLoader();
208 if (threadCL != null) {
209 return threadCL.getResourceAsStream(name);
210 }
211 return ClassLoader.getSystemResourceAsStream(name);
212 });
213 }
214
215 private static SimpleDateFormat getSimpleDateFormat() {
216 try {
217 return new SimpleDateFormat(getStringProperty(systemPrefix + "dateTimeFormat", dateTimeFormat));
218 } catch (final IllegalArgumentException e) {
219 // If the format pattern is invalid - use the default format
220 return new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
221 }
222 }
223
224 private static String getStringProperty(final String name) {
225 String prop = null;
226 try {
227 prop = System.getProperty(name);
228 } catch (final SecurityException e) {
229 // Ignore
230 }
231 return prop == null ? simpleLogProps.getProperty(name) : prop;
232 }
233 private static String getStringProperty(final String name, final String defaultValue) {
234 final String prop = getStringProperty(name);
235 return prop == null ? defaultValue : prop;
236 }
237
238 /** The name of this simple log instance */
239 protected volatile String logName;
240
241 /** The current log level */
242 protected volatile int currentLogLevel;
243
244 /** The short name of this simple log instance */
245 private volatile String shortLogName;
246
247 /**
248 * Constructs a simple log with given name.
249 *
250 * @param name log name
251 */
252 public SimpleLog(String name) {
253 logName = name;
254
255 // Set initial log level
256 // Used to be: set default log level to ERROR
257 // IMHO it should be lower, but at least info (costin).
258 setLevel(LOG_LEVEL_INFO);
259
260 // Set log level from properties
261 String level = getStringProperty(systemPrefix + "log." + logName);
262 int i = String.valueOf(name).lastIndexOf(".");
263 while (level == null && i > -1) {
264 name = name.substring(0, i);
265 level = getStringProperty(systemPrefix + "log." + name);
266 i = String.valueOf(name).lastIndexOf(".");
267 }
268
269 if (level == null) {
270 level = getStringProperty(systemPrefix + "defaultlog");
271 }
272 if (level != null) {
273 level = level.toLowerCase(Locale.ROOT);
274 }
275 if (level != null) {
276 switch (level) {
277 case "all":
278 setLevel(LOG_LEVEL_ALL);
279 break;
280 case "trace":
281 setLevel(LOG_LEVEL_TRACE);
282 break;
283 case "debug":
284 setLevel(LOG_LEVEL_DEBUG);
285 break;
286 case "info":
287 setLevel(LOG_LEVEL_INFO);
288 break;
289 case "warn":
290 setLevel(LOG_LEVEL_WARN);
291 break;
292 case "error":
293 setLevel(LOG_LEVEL_ERROR);
294 break;
295 case "fatal":
296 setLevel(LOG_LEVEL_FATAL);
297 break;
298 case "off":
299 setLevel(LOG_LEVEL_OFF);
300 break;
301 default:
302 // do nothing
303 }
304 }
305 }
306
307 /**
308 * Logs a message with
309 * {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG}.
310 *
311 * @param message to log
312 * @see org.apache.commons.logging.Log#debug(Object)
313 */
314 @Override
315 public final void debug(final Object message) {
316 if (isLevelEnabled(LOG_LEVEL_DEBUG)) {
317 log(LOG_LEVEL_DEBUG, message, null);
318 }
319 }
320
321 /**
322 * Logs a message with
323 * {@code org.apache.commons.logging.impl.LOG_LEVEL_DEBUG}.
324 *
325 * @param message to log
326 * @param t log this cause
327 * @see org.apache.commons.logging.Log#debug(Object, Throwable)
328 */
329 @Override
330 public final void debug(final Object message, final Throwable t) {
331 if (isLevelEnabled(LOG_LEVEL_DEBUG)) {
332 log(LOG_LEVEL_DEBUG, message, t);
333 }
334 }
335
336 /**
337 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR}.
338 *
339 * @param message to log
340 * @see org.apache.commons.logging.Log#error(Object)
341 */
342 @Override
343 public final void error(final Object message) {
344 if (isLevelEnabled(LOG_LEVEL_ERROR)) {
345 log(LOG_LEVEL_ERROR, message, null);
346 }
347 }
348
349 /**
350 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR}.
351 *
352 * @param message to log
353 * @param t log this cause
354 * @see org.apache.commons.logging.Log#error(Object, Throwable)
355 */
356 @Override
357 public final void error(final Object message, final Throwable t) {
358 if (isLevelEnabled(LOG_LEVEL_ERROR)) {
359 log(LOG_LEVEL_ERROR, message, t);
360 }
361 }
362
363 /**
364 * Log a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL}.
365 *
366 * @param message to log
367 * @see org.apache.commons.logging.Log#fatal(Object)
368 */
369 @Override
370 public final void fatal(final Object message) {
371 if (isLevelEnabled(LOG_LEVEL_FATAL)) {
372 log(LOG_LEVEL_FATAL, message, null);
373 }
374 }
375
376 /**
377 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL}.
378 *
379 * @param message to log
380 * @param t log this cause
381 * @see org.apache.commons.logging.Log#fatal(Object, Throwable)
382 */
383 @Override
384 public final void fatal(final Object message, final Throwable t) {
385 if (isLevelEnabled(LOG_LEVEL_FATAL)) {
386 log(LOG_LEVEL_FATAL, message, t);
387 }
388 }
389
390 /**
391 * Gets logging level.
392 *
393 * @return logging level.
394 */
395 public int getLevel() {
396 return currentLogLevel;
397 }
398
399 /**
400 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO}.
401 *
402 * @param message to log
403 * @see org.apache.commons.logging.Log#info(Object)
404 */
405 @Override
406 public final void info(final Object message) {
407 if (isLevelEnabled(LOG_LEVEL_INFO)) {
408 log(LOG_LEVEL_INFO,message,null);
409 }
410 }
411
412 /**
413 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO}.
414 *
415 * @param message to log
416 * @param t log this cause
417 * @see org.apache.commons.logging.Log#info(Object, Throwable)
418 */
419 @Override
420 public final void info(final Object message, final Throwable t) {
421 if (isLevelEnabled(LOG_LEVEL_INFO)) {
422 log(LOG_LEVEL_INFO, message, t);
423 }
424 }
425
426 /**
427 * Tests whether debug messages are enabled.
428 * <p>
429 * This allows expensive operations such as {@code String}
430 * concatenation to be avoided when the message will be ignored by the
431 * logger.
432 * </p>
433 */
434 @Override
435 public final boolean isDebugEnabled() {
436 return isLevelEnabled(LOG_LEVEL_DEBUG);
437 }
438
439 /**
440 * Tests whether error messages are enabled.
441 * <p>
442 * This allows expensive operations such as {@code String}
443 * concatenation to be avoided when the message will be ignored by the
444 * logger.
445 * </p>
446 */
447 @Override
448 public final boolean isErrorEnabled() {
449 return isLevelEnabled(LOG_LEVEL_ERROR);
450 }
451
452 /**
453 * Tests whether fatal messages are enabled.
454 * <p>
455 * This allows expensive operations such as {@code String}
456 * concatenation to be avoided when the message will be ignored by the
457 * logger.
458 * </p>
459 */
460 @Override
461 public final boolean isFatalEnabled() {
462 return isLevelEnabled(LOG_LEVEL_FATAL);
463 }
464
465 /**
466 * Tests whether info messages are enabled.
467 * <p>
468 * This allows expensive operations such as {@code String}
469 * concatenation to be avoided when the message will be ignored by the
470 * logger.
471 * </p>
472 */
473 @Override
474 public final boolean isInfoEnabled() {
475 return isLevelEnabled(LOG_LEVEL_INFO);
476 }
477
478 /**
479 * Tests whether the given level is enabled.
480 *
481 * @param logLevel is this level enabled?
482 * @return whether the given log level currently enabled.
483 */
484 protected boolean isLevelEnabled(final int logLevel) {
485 // log level are numerically ordered so can use simple numeric
486 // comparison
487 return logLevel >= currentLogLevel;
488 }
489
490 /**
491 * Tests whether trace messages are enabled.
492 * <p>
493 * This allows expensive operations such as {@code String}
494 * concatenation to be avoided when the message will be ignored by the
495 * logger.
496 * </p>
497 */
498 @Override
499 public final boolean isTraceEnabled() {
500 return isLevelEnabled(LOG_LEVEL_TRACE);
501 }
502
503 /**
504 * Tests whether warn messages are enabled.
505 * <p>
506 * This allows expensive operations such as {@code String}
507 * concatenation to be avoided when the message will be ignored by the
508 * logger.
509 * </p>
510 */
511 @Override
512 public final boolean isWarnEnabled() {
513 return isLevelEnabled(LOG_LEVEL_WARN);
514 }
515
516 /**
517 * Do the actual logging.
518 * <p>
519 * This method assembles the message and then calls {@code write()}
520 * to cause it to be written.
521 * </p>
522 *
523 * @param type One of the LOG_LEVEL_XXX constants defining the log level
524 * @param message The message itself (typically a String)
525 * @param t The exception whose stack trace should be logged
526 */
527 protected void log(final int type, final Object message, final Throwable t) {
528 // Use a string buffer for better performance
529 final StringBuilder buf = new StringBuilder();
530
531 // Append date-time if so configured
532 if (showDateTime) {
533 final Date now = new Date();
534 String dateText;
535 synchronized (dateFormatter) {
536 dateText = dateFormatter.format(now);
537 }
538 buf.append(dateText);
539 buf.append(" ");
540 }
541
542 // Append a readable representation of the log level
543 switch (type) {
544 case LOG_LEVEL_TRACE:
545 buf.append("[TRACE] ");
546 break;
547 case LOG_LEVEL_DEBUG:
548 buf.append("[DEBUG] ");
549 break;
550 case LOG_LEVEL_INFO:
551 buf.append("[INFO] ");
552 break;
553 case LOG_LEVEL_WARN:
554 buf.append("[WARN] ");
555 break;
556 case LOG_LEVEL_ERROR:
557 buf.append("[ERROR] ");
558 break;
559 case LOG_LEVEL_FATAL:
560 buf.append("[FATAL] ");
561 break;
562 default:
563 // Or throw?
564 buf.append("[UNDEFINED] ");
565 break;
566 }
567
568 // Append the name of the log instance if so configured
569 if (showShortName) {
570 if (shortLogName == null) {
571 // Cut all but the last component of the name for both styles
572 final String slName = logName.substring(logName.lastIndexOf(".") + 1);
573 shortLogName = slName.substring(slName.lastIndexOf("/") + 1);
574 }
575 buf.append(String.valueOf(shortLogName)).append(" - ");
576 } else if (showLogName) {
577 buf.append(String.valueOf(logName)).append(" - ");
578 }
579
580 // Append the message
581 buf.append(String.valueOf(message));
582
583 // Append stack trace if not null
584 if (t != null) {
585 buf.append(" <");
586 buf.append(t.toString());
587 buf.append(">");
588
589 final StringWriter sw = new StringWriter(1024);
590 try (PrintWriter pw = new PrintWriter(sw)) {
591 t.printStackTrace(pw);
592 }
593 buf.append(sw.toString());
594 }
595
596 // Print to the appropriate destination
597 write(buf);
598 }
599
600 /**
601 * Sets logging level.
602 *
603 * @param currentLogLevel new logging level
604 */
605 public void setLevel(final int currentLogLevel) {
606 this.currentLogLevel = currentLogLevel;
607 }
608
609 /**
610 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE}.
611 *
612 * @param message to log
613 * @see org.apache.commons.logging.Log#trace(Object)
614 */
615 @Override
616 public final void trace(final Object message) {
617 if (isLevelEnabled(LOG_LEVEL_TRACE)) {
618 log(LOG_LEVEL_TRACE, message, null);
619 }
620 }
621
622 /**
623 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE}.
624 *
625 * @param message to log
626 * @param t log this cause
627 * @see org.apache.commons.logging.Log#trace(Object, Throwable)
628 */
629 @Override
630 public final void trace(final Object message, final Throwable t) {
631 if (isLevelEnabled(LOG_LEVEL_TRACE)) {
632 log(LOG_LEVEL_TRACE, message, t);
633 }
634 }
635
636 /**
637 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN}.
638 *
639 * @param message to log
640 * @see org.apache.commons.logging.Log#warn(Object)
641 */
642 @Override
643 public final void warn(final Object message) {
644 if (isLevelEnabled(LOG_LEVEL_WARN)) {
645 log(LOG_LEVEL_WARN, message, null);
646 }
647 }
648
649 /**
650 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN}.
651 *
652 * @param message to log
653 * @param t log this cause
654 * @see org.apache.commons.logging.Log#warn(Object, Throwable)
655 */
656 @Override
657 public final void warn(final Object message, final Throwable t) {
658 if (isLevelEnabled(LOG_LEVEL_WARN)) {
659 log(LOG_LEVEL_WARN, message, t);
660 }
661 }
662
663 /**
664 * Writes the content of the message accumulated in the specified
665 * {@code StringBuffer} to the appropriate output destination. The
666 * default implementation writes to {@code System.err}.
667 *
668 * @param buffer A {@code StringBuffer} containing the accumulated
669 * text to be logged
670 */
671 private void write(final Object buffer) {
672 System.err.println(Objects.toString(buffer));
673 }
674
675 /**
676 * Writes the content of the message accumulated in the specified
677 * {@code StringBuffer} to the appropriate output destination. The
678 * default implementation writes to {@code System.err}.
679 *
680 * @param buffer A {@code StringBuffer} containing the accumulated
681 * text to be logged
682 */
683 protected void write(final StringBuffer buffer) {
684 System.err.println(Objects.toString(buffer));
685 }
686 }
687