View Javadoc
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