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    *      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