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