001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.logging.impl;
019
020import java.io.InputStream;
021import java.io.Serializable;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.security.AccessController;
025import java.security.PrivilegedAction;
026import java.text.DateFormat;
027import java.text.SimpleDateFormat;
028import java.util.Date;
029import java.util.Properties;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogConfigurationException;
033
034/**
035 * Simple implementation of Log that sends all enabled log messages,
036 * for all defined loggers, to System.err.  The following system properties
037 * are supported to configure the behavior of this logger:
038 * <ul>
039 * <li><code>org.apache.commons.logging.simplelog.defaultlog</code> -
040 *     Default logging detail level for all instances of SimpleLog.
041 *     Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").
042 *     If not specified, defaults to "info". </li>
043 * <li><code>org.apache.commons.logging.simplelog.log.xxxxx</code> -
044 *     Logging detail level for a SimpleLog instance named "xxxxx".
045 *     Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").
046 *     If not specified, the default logging detail level is used.</li>
047 * <li><code>org.apache.commons.logging.simplelog.showlogname</code> -
048 *     Set to <code>true</code> if you want the Log instance name to be
049 *     included in output messages. Defaults to <code>false</code>.</li>
050 * <li><code>org.apache.commons.logging.simplelog.showShortLogname</code> -
051 *     Set to <code>true</code> if you want the last component of the name to be
052 *     included in output messages. Defaults to <code>true</code>.</li>
053 * <li><code>org.apache.commons.logging.simplelog.showdatetime</code> -
054 *     Set to <code>true</code> if you want the current date and time
055 *     to be included in output messages. Default is <code>false</code>.</li>
056 * <li><code>org.apache.commons.logging.simplelog.dateTimeFormat</code> -
057 *     The date and time format to be used in the output messages.
058 *     The pattern describing the date and time format is the same that is
059 *     used in <code>java.text.SimpleDateFormat</code>. If the format is not
060 *     specified or is invalid, the default format is used.
061 *     The default format is <code>yyyy/MM/dd HH:mm:ss:SSS zzz</code>.</li>
062 * </ul>
063 * <p>
064 * In addition to looking for system properties with the names specified
065 * above, this implementation also checks for a class loader resource named
066 * <code>"simplelog.properties"</code>, and includes any matching definitions
067 * from this resource (if it exists).
068 *
069 * @version $Id: SimpleLog.html 915605 2014-07-09 20:22:43Z tn $
070 */
071public class SimpleLog implements Log, Serializable {
072
073    /** Serializable version identifier. */
074    private static final long serialVersionUID = 136942970684951178L;
075
076    // ------------------------------------------------------- Class Attributes
077
078    /** All system properties used by <code>SimpleLog</code> start with this */
079    static protected final String systemPrefix = "org.apache.commons.logging.simplelog.";
080
081    /** Properties loaded from simplelog.properties */
082    static protected final Properties simpleLogProps = new Properties();
083
084    /** The default format to use when formating dates */
085    static protected final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz";
086
087    /** Include the instance name in the log message? */
088    static volatile protected boolean showLogName = false;
089
090    /** Include the short name ( last component ) of the logger in the log
091     *  message. Defaults to true - otherwise we'll be lost in a flood of
092     *  messages without knowing who sends them.
093     */
094    static volatile protected boolean showShortName = true;
095
096    /** Include the current time in the log message */
097    static volatile protected boolean showDateTime = false;
098
099    /** The date and time format to use in the log message */
100    static volatile protected String dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
101
102    /**
103     * Used to format times.
104     * <p>
105     * Any code that accesses this object should first obtain a lock on it,
106     * ie use synchronized(dateFormatter); this requirement was introduced
107     * in 1.1.1 to fix an existing thread safety bug (SimpleDateFormat.format
108     * is not thread-safe).
109     */
110    static protected DateFormat dateFormatter = null;
111
112    // ---------------------------------------------------- Log Level Constants
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    // ------------------------------------------------------------ Initializer
134
135    private static String getStringProperty(String name) {
136        String prop = null;
137        try {
138            prop = System.getProperty(name);
139        } catch (SecurityException e) {
140            // Ignore
141        }
142        return prop == null ? simpleLogProps.getProperty(name) : prop;
143    }
144
145    private static String getStringProperty(String name, String dephault) {
146        String prop = getStringProperty(name);
147        return prop == null ? dephault : prop;
148    }
149
150    private static boolean getBooleanProperty(String name, boolean dephault) {
151        String prop = getStringProperty(name);
152        return prop == null ? dephault : "true".equalsIgnoreCase(prop);
153    }
154
155    // Initialize class attributes.
156    // Load properties file, if found.
157    // Override with system properties.
158    static {
159        // Add props from the resource simplelog.properties
160        InputStream in = getResourceAsStream("simplelog.properties");
161        if(null != in) {
162            try {
163                simpleLogProps.load(in);
164                in.close();
165            } catch(java.io.IOException e) {
166                // ignored
167            }
168        }
169
170        showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName);
171        showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName);
172        showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime);
173
174        if(showDateTime) {
175            dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat",
176                                               dateTimeFormat);
177            try {
178                dateFormatter = new SimpleDateFormat(dateTimeFormat);
179            } catch(IllegalArgumentException e) {
180                // If the format pattern is invalid - use the default format
181                dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
182                dateFormatter = new SimpleDateFormat(dateTimeFormat);
183            }
184        }
185    }
186
187    // ------------------------------------------------------------- Attributes
188
189    /** The name of this simple log instance */
190    protected volatile String logName = null;
191    /** The current log level */
192    protected volatile int currentLogLevel;
193    /** The short name of this simple log instance */
194    private volatile String shortLogName = null;
195
196    // ------------------------------------------------------------ Constructor
197
198    /**
199     * Construct a simple log with given name.
200     *
201     * @param name log name
202     */
203    public SimpleLog(String name) {
204        logName = name;
205
206        // Set initial log level
207        // Used to be: set default log level to ERROR
208        // IMHO it should be lower, but at least info ( costin ).
209        setLevel(SimpleLog.LOG_LEVEL_INFO);
210
211        // Set log level from properties
212        String lvl = getStringProperty(systemPrefix + "log." + logName);
213        int i = String.valueOf(name).lastIndexOf(".");
214        while(null == lvl && i > -1) {
215            name = name.substring(0,i);
216            lvl = getStringProperty(systemPrefix + "log." + name);
217            i = String.valueOf(name).lastIndexOf(".");
218        }
219
220        if(null == lvl) {
221            lvl =  getStringProperty(systemPrefix + "defaultlog");
222        }
223
224        if("all".equalsIgnoreCase(lvl)) {
225            setLevel(SimpleLog.LOG_LEVEL_ALL);
226        } else if("trace".equalsIgnoreCase(lvl)) {
227            setLevel(SimpleLog.LOG_LEVEL_TRACE);
228        } else if("debug".equalsIgnoreCase(lvl)) {
229            setLevel(SimpleLog.LOG_LEVEL_DEBUG);
230        } else if("info".equalsIgnoreCase(lvl)) {
231            setLevel(SimpleLog.LOG_LEVEL_INFO);
232        } else if("warn".equalsIgnoreCase(lvl)) {
233            setLevel(SimpleLog.LOG_LEVEL_WARN);
234        } else if("error".equalsIgnoreCase(lvl)) {
235            setLevel(SimpleLog.LOG_LEVEL_ERROR);
236        } else if("fatal".equalsIgnoreCase(lvl)) {
237            setLevel(SimpleLog.LOG_LEVEL_FATAL);
238        } else if("off".equalsIgnoreCase(lvl)) {
239            setLevel(SimpleLog.LOG_LEVEL_OFF);
240        }
241    }
242
243    // -------------------------------------------------------- Properties
244
245    /**
246     * Set logging level.
247     *
248     * @param currentLogLevel new logging level
249     */
250    public void setLevel(int currentLogLevel) {
251        this.currentLogLevel = currentLogLevel;
252    }
253
254    /**
255     * Get logging level.
256     */
257    public int getLevel() {
258        return currentLogLevel;
259    }
260
261    // -------------------------------------------------------- Logging Methods
262
263    /**
264     * Do the actual logging.
265     * <p>
266     * This method assembles the message and then calls <code>write()</code>
267     * to cause it to be written.
268     *
269     * @param type One of the LOG_LEVEL_XXX constants defining the log level
270     * @param message The message itself (typically a String)
271     * @param t The exception whose stack trace should be logged
272     */
273    protected void log(int type, Object message, Throwable t) {
274        // Use a string buffer for better performance
275        final StringBuffer buf = new StringBuffer();
276
277        // Append date-time if so configured
278        if(showDateTime) {
279            final Date now = new Date();
280            String dateText;
281            synchronized(dateFormatter) {
282                dateText = dateFormatter.format(now);
283            }
284            buf.append(dateText);
285            buf.append(" ");
286        }
287
288        // Append a readable representation of the log level
289        switch(type) {
290            case SimpleLog.LOG_LEVEL_TRACE: buf.append("[TRACE] "); break;
291            case SimpleLog.LOG_LEVEL_DEBUG: buf.append("[DEBUG] "); break;
292            case SimpleLog.LOG_LEVEL_INFO:  buf.append("[INFO] ");  break;
293            case SimpleLog.LOG_LEVEL_WARN:  buf.append("[WARN] ");  break;
294            case SimpleLog.LOG_LEVEL_ERROR: buf.append("[ERROR] "); break;
295            case SimpleLog.LOG_LEVEL_FATAL: buf.append("[FATAL] "); break;
296        }
297
298        // Append the name of the log instance if so configured
299        if(showShortName) {
300            if(shortLogName == null) {
301                // Cut all but the last component of the name for both styles
302                final String slName = logName.substring(logName.lastIndexOf(".") + 1);
303                shortLogName = slName.substring(slName.lastIndexOf("/") + 1);
304            }
305            buf.append(String.valueOf(shortLogName)).append(" - ");
306        } else if(showLogName) {
307            buf.append(String.valueOf(logName)).append(" - ");
308        }
309
310        // Append the message
311        buf.append(String.valueOf(message));
312
313        // Append stack trace if not null
314        if(t != null) {
315            buf.append(" <");
316            buf.append(t.toString());
317            buf.append(">");
318
319            final java.io.StringWriter sw = new java.io.StringWriter(1024);
320            final java.io.PrintWriter pw = new java.io.PrintWriter(sw);
321            t.printStackTrace(pw);
322            pw.close();
323            buf.append(sw.toString());
324        }
325
326        // Print to the appropriate destination
327        write(buf);
328    }
329
330    /**
331     * Write the content of the message accumulated in the specified
332     * <code>StringBuffer</code> to the appropriate output destination.  The
333     * default implementation writes to <code>System.err</code>.
334     *
335     * @param buffer A <code>StringBuffer</code> containing the accumulated
336     *  text to be logged
337     */
338    protected void write(StringBuffer buffer) {
339        System.err.println(buffer.toString());
340    }
341
342    /**
343     * Is the given log level currently enabled?
344     *
345     * @param logLevel is this level enabled?
346     */
347    protected boolean isLevelEnabled(int logLevel) {
348        // log level are numerically ordered so can use simple numeric
349        // comparison
350        return logLevel >= currentLogLevel;
351    }
352
353    // -------------------------------------------------------- Log Implementation
354
355    /**
356     * Logs a message with
357     * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG</code>.
358     *
359     * @param message to log
360     * @see org.apache.commons.logging.Log#debug(Object)
361     */
362    public final void debug(Object message) {
363        if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) {
364            log(SimpleLog.LOG_LEVEL_DEBUG, message, null);
365        }
366    }
367
368    /**
369     * Logs a message with
370     * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG</code>.
371     *
372     * @param message to log
373     * @param t log this cause
374     * @see org.apache.commons.logging.Log#debug(Object, Throwable)
375     */
376    public final void debug(Object message, Throwable t) {
377        if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) {
378            log(SimpleLog.LOG_LEVEL_DEBUG, message, t);
379        }
380    }
381
382    /**
383     * Logs a message with <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE</code>.
384     *
385     * @param message to log
386     * @see org.apache.commons.logging.Log#trace(Object)
387     */
388    public final void trace(Object message) {
389        if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) {
390            log(SimpleLog.LOG_LEVEL_TRACE, message, null);
391        }
392    }
393
394    /**
395     * Logs a message with <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE</code>.
396     *
397     * @param message to log
398     * @param t log this cause
399     * @see org.apache.commons.logging.Log#trace(Object, Throwable)
400     */
401    public final void trace(Object message, Throwable t) {
402        if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) {
403            log(SimpleLog.LOG_LEVEL_TRACE, message, t);
404        }
405    }
406
407    /**
408     * Logs a message with <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO</code>.
409     *
410     * @param message to log
411     * @see org.apache.commons.logging.Log#info(Object)
412     */
413    public final void info(Object message) {
414        if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) {
415            log(SimpleLog.LOG_LEVEL_INFO,message,null);
416        }
417    }
418
419    /**
420     * Logs a message with <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO</code>.
421     *
422     * @param message to log
423     * @param t log this cause
424     * @see org.apache.commons.logging.Log#info(Object, Throwable)
425     */
426    public final void info(Object message, Throwable t) {
427        if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) {
428            log(SimpleLog.LOG_LEVEL_INFO, message, t);
429        }
430    }
431
432    /**
433     * Logs a message with <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN</code>.
434     *
435     * @param message to log
436     * @see org.apache.commons.logging.Log#warn(Object)
437     */
438    public final void warn(Object message) {
439        if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) {
440            log(SimpleLog.LOG_LEVEL_WARN, message, null);
441        }
442    }
443
444    /**
445     * Logs a message with <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN</code>.
446     *
447     * @param message to log
448     * @param t log this cause
449     * @see org.apache.commons.logging.Log#warn(Object, Throwable)
450     */
451    public final void warn(Object message, Throwable t) {
452        if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) {
453            log(SimpleLog.LOG_LEVEL_WARN, message, t);
454        }
455    }
456
457    /**
458     * Logs a message with <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR</code>.
459     *
460     * @param message to log
461     * @see org.apache.commons.logging.Log#error(Object)
462     */
463    public final void error(Object message) {
464        if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) {
465            log(SimpleLog.LOG_LEVEL_ERROR, message, null);
466        }
467    }
468
469    /**
470     * Logs a message with <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR</code>.
471     *
472     * @param message to log
473     * @param t log this cause
474     * @see org.apache.commons.logging.Log#error(Object, Throwable)
475     */
476    public final void error(Object message, Throwable t) {
477        if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) {
478            log(SimpleLog.LOG_LEVEL_ERROR, message, t);
479        }
480    }
481
482    /**
483     * Log a message with <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL</code>.
484     *
485     * @param message to log
486     * @see org.apache.commons.logging.Log#fatal(Object)
487     */
488    public final void fatal(Object message) {
489        if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) {
490            log(SimpleLog.LOG_LEVEL_FATAL, message, null);
491        }
492    }
493
494    /**
495     * Logs a message with <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL</code>.
496     *
497     * @param message to log
498     * @param t log this cause
499     * @see org.apache.commons.logging.Log#fatal(Object, Throwable)
500     */
501    public final void fatal(Object message, Throwable t) {
502        if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) {
503            log(SimpleLog.LOG_LEVEL_FATAL, message, t);
504        }
505    }
506
507    /**
508     * Are debug messages currently enabled?
509     * <p>
510     * This allows expensive operations such as <code>String</code>
511     * concatenation to be avoided when the message will be ignored by the
512     * logger.
513     */
514    public final boolean isDebugEnabled() {
515        return isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG);
516    }
517
518    /**
519     * Are error messages currently enabled?
520     * <p>
521     * This allows expensive operations such as <code>String</code>
522     * concatenation to be avoided when the message will be ignored by the
523     * logger.
524     */
525    public final boolean isErrorEnabled() {
526        return isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR);
527    }
528
529    /**
530     * Are fatal messages currently enabled?
531     * <p>
532     * This allows expensive operations such as <code>String</code>
533     * concatenation to be avoided when the message will be ignored by the
534     * logger.
535     */
536    public final boolean isFatalEnabled() {
537        return isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL);
538    }
539
540    /**
541     * Are info messages currently enabled?
542     * <p>
543     * This allows expensive operations such as <code>String</code>
544     * concatenation to be avoided when the message will be ignored by the
545     * logger.
546     */
547    public final boolean isInfoEnabled() {
548        return isLevelEnabled(SimpleLog.LOG_LEVEL_INFO);
549    }
550
551    /**
552     * Are trace messages currently enabled?
553     * <p>
554     * This allows expensive operations such as <code>String</code>
555     * concatenation to be avoided when the message will be ignored by the
556     * logger.
557     */
558    public final boolean isTraceEnabled() {
559        return isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE);
560    }
561
562    /**
563     * Are warn messages currently enabled?
564     * <p>
565     * This allows expensive operations such as <code>String</code>
566     * concatenation to be avoided when the message will be ignored by the
567     * logger.
568     */
569    public final boolean isWarnEnabled() {
570        return isLevelEnabled(SimpleLog.LOG_LEVEL_WARN);
571    }
572
573    /**
574     * Return the thread context class loader if available.
575     * Otherwise return null.
576     *
577     * The thread context class loader is available for JDK 1.2
578     * or later, if certain security conditions are met.
579     *
580     * @exception LogConfigurationException if a suitable class loader
581     * cannot be identified.
582     */
583    private static ClassLoader getContextClassLoader() {
584        ClassLoader classLoader = null;
585
586        try {
587            // Are we running on a JDK 1.2 or later system?
588            final Method method = Thread.class.getMethod("getContextClassLoader", (Class[]) null);
589
590            // Get the thread context class loader (if there is one)
591            try {
592                classLoader = (ClassLoader)method.invoke(Thread.currentThread(), (Class[]) null);
593            } catch (IllegalAccessException e) {
594                // ignore
595            } catch (InvocationTargetException e) {
596                /**
597                 * InvocationTargetException is thrown by 'invoke' when
598                 * the method being invoked (getContextClassLoader) throws
599                 * an exception.
600                 *
601                 * getContextClassLoader() throws SecurityException when
602                 * the context class loader isn't an ancestor of the
603                 * calling class's class loader, or if security
604                 * permissions are restricted.
605                 *
606                 * In the first case (not related), we want to ignore and
607                 * keep going.  We cannot help but also ignore the second
608                 * with the logic below, but other calls elsewhere (to
609                 * obtain a class loader) will trigger this exception where
610                 * we can make a distinction.
611                 */
612                if (e.getTargetException() instanceof SecurityException) {
613                    // ignore
614                } else {
615                    // Capture 'e.getTargetException()' exception for details
616                    // alternate: log 'e.getTargetException()', and pass back 'e'.
617                    throw new LogConfigurationException
618                        ("Unexpected InvocationTargetException", e.getTargetException());
619                }
620            }
621        } catch (NoSuchMethodException e) {
622            // Assume we are running on JDK 1.1
623            // ignore
624        }
625
626        if (classLoader == null) {
627            classLoader = SimpleLog.class.getClassLoader();
628        }
629
630        // Return the selected class loader
631        return classLoader;
632    }
633
634    private static InputStream getResourceAsStream(final String name) {
635        return (InputStream)AccessController.doPrivileged(
636            new PrivilegedAction() {
637                public Object run() {
638                    ClassLoader threadCL = getContextClassLoader();
639
640                    if (threadCL != null) {
641                        return threadCL.getResourceAsStream(name);
642                    } else {
643                        return ClassLoader.getSystemResourceAsStream(name);
644                    }
645                }
646            });
647    }
648}
649