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