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 * http://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 */
112 static protected DateFormat dateFormatter;
113
114 /** "Trace" level logging. */
115 public static final int LOG_LEVEL_TRACE = 1;
116 /** "Debug" level logging. */
117 public static final int LOG_LEVEL_DEBUG = 2;
118 /** "Info" level logging. */
119 public static final int LOG_LEVEL_INFO = 3;
120 /** "Warn" level logging. */
121 public static final int LOG_LEVEL_WARN = 4;
122 /** "Error" level logging. */
123 public static final int LOG_LEVEL_ERROR = 5;
124 /** "Fatal" level logging. */
125 public static final int LOG_LEVEL_FATAL = 6;
126
127 /** Enable all logging levels */
128 public static final int LOG_LEVEL_ALL = LOG_LEVEL_TRACE - 1;
129
130 /** Enable no logging levels */
131 public static final int LOG_LEVEL_OFF = LOG_LEVEL_FATAL + 1;
132
133 // Initialize class attributes.
134 // Load properties file, if found.
135 // Override with system properties.
136 static {
137 // Add props from the resource simplelog.properties
138 try (InputStream in = getResourceAsStream("simplelog.properties")) {
139 if (null != in) {
140 simpleLogProps.load(in);
141 }
142 } catch (final IOException ignore) {
143 // Ignore
144 }
145
146 showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName);
147 showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName);
148 showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime);
149
150 if (showDateTime) {
151 dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat", dateTimeFormat);
152 try {
153 dateFormatter = new SimpleDateFormat(dateTimeFormat);
154 } catch (final IllegalArgumentException e) {
155 // If the format pattern is invalid - use the default format
156 dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
157 dateFormatter = new SimpleDateFormat(dateTimeFormat);
158 }
159 }
160 }
161
162 private static boolean getBooleanProperty(final String name, final boolean defaultValue) {
163 final String prop = getStringProperty(name);
164 return prop == null ? defaultValue : Boolean.parseBoolean(prop);
165 }
166
167 /**
168 * Gets the thread context class loader if available. Otherwise return null.
169 *
170 * The thread context class loader is available if certain security conditions are met.
171 *
172 * @throws LogConfigurationException if a suitable class loader cannot be identified.
173 */
174 private static ClassLoader getContextClassLoader() {
175 ClassLoader classLoader = null;
176
177 // Get the thread context class loader (if there is one)
178 try {
179 classLoader = Thread.currentThread().getContextClassLoader();
180 } catch (final RuntimeException e) {
181 /**
182 * getContextClassLoader() throws SecurityException when the context class loader isn't an ancestor of the calling class's class loader, or if
183 * security permissions are restricted.
184 *
185 * 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
186 * calls elsewhere (to obtain a class loader) will trigger this exception where we can make a distinction.
187 */
188 // Capture 'e.getTargetException()' exception for details
189 // alternate: log 'e.getTargetException()', and pass back 'e'.
190 if (!(e instanceof SecurityException)) {
191 throw new LogConfigurationException("Unexpected SecurityException", e);
192 }
193 }
194
195 if (classLoader == null) {
196 classLoader = SimpleLog.class.getClassLoader();
197 }
198
199 // Return the selected class loader
200 return classLoader;
201 }
202
203 private static InputStream getResourceAsStream(final String name) {
204 return AccessController.doPrivileged((PrivilegedAction<InputStream>) () -> {
205 final ClassLoader threadCL = getContextClassLoader();
206 if (threadCL != null) {
207 return threadCL.getResourceAsStream(name);
208 }
209 return ClassLoader.getSystemResourceAsStream(name);
210 });
211 }
212
213 private static String getStringProperty(final String name) {
214 String prop = null;
215 try {
216 prop = System.getProperty(name);
217 } catch (final SecurityException e) {
218 // Ignore
219 }
220 return prop == null ? simpleLogProps.getProperty(name) : prop;
221 }
222 private static String getStringProperty(final String name, final String defaultValue) {
223 final String prop = getStringProperty(name);
224 return prop == null ? defaultValue : prop;
225 }
226 /** The name of this simple log instance */
227 protected volatile String logName;
228
229 /** The current log level */
230 protected volatile int currentLogLevel;
231
232 /** The short name of this simple log instance */
233 private volatile String shortLogName;
234
235 /**
236 * Constructs a simple log with given name.
237 *
238 * @param name log name
239 */
240 public SimpleLog(String name) {
241 logName = name;
242
243 // Set initial log level
244 // Used to be: set default log level to ERROR
245 // IMHO it should be lower, but at least info (costin).
246 setLevel(LOG_LEVEL_INFO);
247
248 // Set log level from properties
249 String level = getStringProperty(systemPrefix + "log." + logName);
250 int i = String.valueOf(name).lastIndexOf(".");
251 while (level == null && i > -1) {
252 name = name.substring(0, i);
253 level = getStringProperty(systemPrefix + "log." + name);
254 i = String.valueOf(name).lastIndexOf(".");
255 }
256
257 if (level == null) {
258 level = getStringProperty(systemPrefix + "defaultlog");
259 }
260 if (level != null) {
261 level = level.toLowerCase(Locale.ROOT);
262 }
263 if (level != null) {
264 switch (level) {
265 case "all":
266 setLevel(LOG_LEVEL_ALL);
267 break;
268 case "trace":
269 setLevel(LOG_LEVEL_TRACE);
270 break;
271 case "debug":
272 setLevel(LOG_LEVEL_DEBUG);
273 break;
274 case "info":
275 setLevel(LOG_LEVEL_INFO);
276 break;
277 case "warn":
278 setLevel(LOG_LEVEL_WARN);
279 break;
280 case "error":
281 setLevel(LOG_LEVEL_ERROR);
282 break;
283 case "fatal":
284 setLevel(LOG_LEVEL_FATAL);
285 break;
286 case "off":
287 setLevel(LOG_LEVEL_OFF);
288 break;
289 default:
290 // do nothing
291 }
292 }
293 }
294
295 /**
296 * Logs a message with
297 * {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG}.
298 *
299 * @param message to log
300 * @see org.apache.commons.logging.Log#debug(Object)
301 */
302 @Override
303 public final void debug(final Object message) {
304 if (isLevelEnabled(LOG_LEVEL_DEBUG)) {
305 log(LOG_LEVEL_DEBUG, message, null);
306 }
307 }
308
309 /**
310 * Logs a message with
311 * {@code org.apache.commons.logging.impl.LOG_LEVEL_DEBUG}.
312 *
313 * @param message to log
314 * @param t log this cause
315 * @see org.apache.commons.logging.Log#debug(Object, Throwable)
316 */
317 @Override
318 public final void debug(final Object message, final Throwable t) {
319 if (isLevelEnabled(LOG_LEVEL_DEBUG)) {
320 log(LOG_LEVEL_DEBUG, message, t);
321 }
322 }
323
324 /**
325 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR}.
326 *
327 * @param message to log
328 * @see org.apache.commons.logging.Log#error(Object)
329 */
330 @Override
331 public final void error(final Object message) {
332 if (isLevelEnabled(LOG_LEVEL_ERROR)) {
333 log(LOG_LEVEL_ERROR, message, null);
334 }
335 }
336
337 /**
338 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR}.
339 *
340 * @param message to log
341 * @param t log this cause
342 * @see org.apache.commons.logging.Log#error(Object, Throwable)
343 */
344 @Override
345 public final void error(final Object message, final Throwable t) {
346 if (isLevelEnabled(LOG_LEVEL_ERROR)) {
347 log(LOG_LEVEL_ERROR, message, t);
348 }
349 }
350
351 /**
352 * Log a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL}.
353 *
354 * @param message to log
355 * @see org.apache.commons.logging.Log#fatal(Object)
356 */
357 @Override
358 public final void fatal(final Object message) {
359 if (isLevelEnabled(LOG_LEVEL_FATAL)) {
360 log(LOG_LEVEL_FATAL, message, null);
361 }
362 }
363
364 /**
365 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL}.
366 *
367 * @param message to log
368 * @param t log this cause
369 * @see org.apache.commons.logging.Log#fatal(Object, Throwable)
370 */
371 @Override
372 public final void fatal(final Object message, final Throwable t) {
373 if (isLevelEnabled(LOG_LEVEL_FATAL)) {
374 log(LOG_LEVEL_FATAL, message, t);
375 }
376 }
377
378 /**
379 * Gets logging level.
380 *
381 * @return logging level.
382 */
383 public int getLevel() {
384 return currentLogLevel;
385 }
386
387 /**
388 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO}.
389 *
390 * @param message to log
391 * @see org.apache.commons.logging.Log#info(Object)
392 */
393 @Override
394 public final void info(final Object message) {
395 if (isLevelEnabled(LOG_LEVEL_INFO)) {
396 log(LOG_LEVEL_INFO,message,null);
397 }
398 }
399
400 /**
401 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO}.
402 *
403 * @param message to log
404 * @param t log this cause
405 * @see org.apache.commons.logging.Log#info(Object, Throwable)
406 */
407 @Override
408 public final void info(final Object message, final Throwable t) {
409 if (isLevelEnabled(LOG_LEVEL_INFO)) {
410 log(LOG_LEVEL_INFO, message, t);
411 }
412 }
413
414 /**
415 * Tests whether debug messages are enabled.
416 * <p>
417 * This allows expensive operations such as {@code String}
418 * concatenation to be avoided when the message will be ignored by the
419 * logger.
420 * </p>
421 */
422 @Override
423 public final boolean isDebugEnabled() {
424 return isLevelEnabled(LOG_LEVEL_DEBUG);
425 }
426
427 /**
428 * Tests whether error messages are enabled.
429 * <p>
430 * This allows expensive operations such as {@code String}
431 * concatenation to be avoided when the message will be ignored by the
432 * logger.
433 * </p>
434 */
435 @Override
436 public final boolean isErrorEnabled() {
437 return isLevelEnabled(LOG_LEVEL_ERROR);
438 }
439
440 /**
441 * Tests whether fatal messages are enabled.
442 * <p>
443 * This allows expensive operations such as {@code String}
444 * concatenation to be avoided when the message will be ignored by the
445 * logger.
446 * </p>
447 */
448 @Override
449 public final boolean isFatalEnabled() {
450 return isLevelEnabled(LOG_LEVEL_FATAL);
451 }
452
453 /**
454 * Tests whether info messages are enabled.
455 * <p>
456 * This allows expensive operations such as {@code String}
457 * concatenation to be avoided when the message will be ignored by the
458 * logger.
459 * </p>
460 */
461 @Override
462 public final boolean isInfoEnabled() {
463 return isLevelEnabled(LOG_LEVEL_INFO);
464 }
465
466 /**
467 * Tests whether the given level is enabled.
468 *
469 * @param logLevel is this level enabled?
470 * @return whether the given log level currently enabled.
471 */
472 protected boolean isLevelEnabled(final int logLevel) {
473 // log level are numerically ordered so can use simple numeric
474 // comparison
475 return logLevel >= currentLogLevel;
476 }
477
478 /**
479 * Tests whether trace messages are enabled.
480 * <p>
481 * This allows expensive operations such as {@code String}
482 * concatenation to be avoided when the message will be ignored by the
483 * logger.
484 * </p>
485 */
486 @Override
487 public final boolean isTraceEnabled() {
488 return isLevelEnabled(LOG_LEVEL_TRACE);
489 }
490
491 /**
492 * Tests whether warn messages are enabled.
493 * <p>
494 * This allows expensive operations such as {@code String}
495 * concatenation to be avoided when the message will be ignored by the
496 * logger.
497 * </p>
498 */
499 @Override
500 public final boolean isWarnEnabled() {
501 return isLevelEnabled(LOG_LEVEL_WARN);
502 }
503
504 /**
505 * Do the actual logging.
506 * <p>
507 * This method assembles the message and then calls {@code write()}
508 * to cause it to be written.
509 * </p>
510 *
511 * @param type One of the LOG_LEVEL_XXX constants defining the log level
512 * @param message The message itself (typically a String)
513 * @param t The exception whose stack trace should be logged
514 */
515 protected void log(final int type, final Object message, final Throwable t) {
516 // Use a string buffer for better performance
517 final StringBuilder buf = new StringBuilder();
518
519 // Append date-time if so configured
520 if (showDateTime) {
521 final Date now = new Date();
522 String dateText;
523 synchronized (dateFormatter) {
524 dateText = dateFormatter.format(now);
525 }
526 buf.append(dateText);
527 buf.append(" ");
528 }
529
530 // Append a readable representation of the log level
531 switch (type) {
532 case LOG_LEVEL_TRACE:
533 buf.append("[TRACE] ");
534 break;
535 case LOG_LEVEL_DEBUG:
536 buf.append("[DEBUG] ");
537 break;
538 case LOG_LEVEL_INFO:
539 buf.append("[INFO] ");
540 break;
541 case LOG_LEVEL_WARN:
542 buf.append("[WARN] ");
543 break;
544 case LOG_LEVEL_ERROR:
545 buf.append("[ERROR] ");
546 break;
547 case LOG_LEVEL_FATAL:
548 buf.append("[FATAL] ");
549 break;
550 default:
551 // Or throw?
552 buf.append("[UNDEFINED] ");
553 break;
554 }
555
556 // Append the name of the log instance if so configured
557 if (showShortName) {
558 if (shortLogName == null) {
559 // Cut all but the last component of the name for both styles
560 final String slName = logName.substring(logName.lastIndexOf(".") + 1);
561 shortLogName = slName.substring(slName.lastIndexOf("/") + 1);
562 }
563 buf.append(String.valueOf(shortLogName)).append(" - ");
564 } else if (showLogName) {
565 buf.append(String.valueOf(logName)).append(" - ");
566 }
567
568 // Append the message
569 buf.append(String.valueOf(message));
570
571 // Append stack trace if not null
572 if (t != null) {
573 buf.append(" <");
574 buf.append(t.toString());
575 buf.append(">");
576
577 final StringWriter sw = new StringWriter(1024);
578 try (PrintWriter pw = new PrintWriter(sw)) {
579 t.printStackTrace(pw);
580 }
581 buf.append(sw.toString());
582 }
583
584 // Print to the appropriate destination
585 write(buf);
586 }
587
588 /**
589 * Sets logging level.
590 *
591 * @param currentLogLevel new logging level
592 */
593 public void setLevel(final int currentLogLevel) {
594 this.currentLogLevel = currentLogLevel;
595 }
596
597 /**
598 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE}.
599 *
600 * @param message to log
601 * @see org.apache.commons.logging.Log#trace(Object)
602 */
603 @Override
604 public final void trace(final Object message) {
605 if (isLevelEnabled(LOG_LEVEL_TRACE)) {
606 log(LOG_LEVEL_TRACE, message, null);
607 }
608 }
609
610 /**
611 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE}.
612 *
613 * @param message to log
614 * @param t log this cause
615 * @see org.apache.commons.logging.Log#trace(Object, Throwable)
616 */
617 @Override
618 public final void trace(final Object message, final Throwable t) {
619 if (isLevelEnabled(LOG_LEVEL_TRACE)) {
620 log(LOG_LEVEL_TRACE, message, t);
621 }
622 }
623
624 /**
625 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN}.
626 *
627 * @param message to log
628 * @see org.apache.commons.logging.Log#warn(Object)
629 */
630 @Override
631 public final void warn(final Object message) {
632 if (isLevelEnabled(LOG_LEVEL_WARN)) {
633 log(LOG_LEVEL_WARN, message, null);
634 }
635 }
636
637 /**
638 * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN}.
639 *
640 * @param message to log
641 * @param t log this cause
642 * @see org.apache.commons.logging.Log#warn(Object, Throwable)
643 */
644 @Override
645 public final void warn(final Object message, final Throwable t) {
646 if (isLevelEnabled(LOG_LEVEL_WARN)) {
647 log(LOG_LEVEL_WARN, message, t);
648 }
649 }
650
651 /**
652 * Writes the content of the message accumulated in the specified
653 * {@code StringBuffer} to the appropriate output destination. The
654 * default implementation writes to {@code System.err}.
655 *
656 * @param buffer A {@code StringBuffer} containing the accumulated
657 * text to be logged
658 */
659 private void write(final Object buffer) {
660 System.err.println(Objects.toString(buffer));
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 protected void write(final StringBuffer buffer) {
672 System.err.println(Objects.toString(buffer));
673 }
674 }
675