    001/**
    002 * Copyright (c) 2004-2012 QOS.ch
    003 * All rights reserved.
    004 *
    005 * Permission is hereby granted, free of charge, to any person obtaining
    006 * a copy of this software and associated documentation files (the
    007 * "Software"), to  deal in  the Software without  restriction, including
    008 * without limitation  the rights to  use, copy, modify,  merge, publish,
    009 * distribute,  sublicense, and/or sell  copies of  the Software,  and to
    010 * permit persons to whom the Software  is furnished to do so, subject to
    011 * the following conditions:
    012 *
    013 * The  above  copyright  notice  and  this permission  notice  shall  be
    014 * included in all copies or substantial portions of the Software.
    015 *
    016 * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
    017 * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
    018 * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
    019 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    020 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    021 * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
    022 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    023 *
    024 */
    025package org.slf4j.impl;
    026
    027import java.io.PrintStream;
    028import java.util.Date;
    029
    030import org.slf4j.Logger;
    031import org.slf4j.event.LoggingEvent;
    032import org.slf4j.helpers.FormattingTuple;
    033import org.slf4j.helpers.MarkerIgnoringBase;
    034import org.slf4j.helpers.MessageFormatter;
    035import org.slf4j.spi.LocationAwareLogger;
    036
    037/**
    038 * <p>
    039 * Simple implementation of {@link Logger} that sends all enabled log messages,
    040 * for all defined loggers, to the console ({@code System.err}). The following
    041 * system properties are supported to configure the behavior of this logger:
    042 * </p>
    043 *
    044 * <ul>
    045 * <li><code>org.slf4j.simpleLogger.logFile</code> - The output target which can
    046 * be the <em>path</em> to a file, or the special values "System.out" and
    047 * "System.err". Default is "System.err".</li>
    048 *
    049 * <li><code>org.slf4j.simpleLogger.cacheOutputStream</code> - If the output
    050 * target is set to "System.out" or "System.err" (see preceding entry), by
    051 * default, logs will be output to the latest value referenced by
    052 * <code>System.out/err</code> variables. By setting this
    053 * parameter to true, the output stream will be cached, i.e. assigned once at
    054 * initialization time and re-used independently of the current value referenced by
    055 *  <code>System.out/err</code>.
    056 * </li>
    057 *
    058 * <li><code>org.slf4j.simpleLogger.defaultLogLevel</code> - Default log level
    059 * for all instances of SimpleLogger. Must be one of ("trace", "debug", "info",
    060 * "warn", "error" or "off"). If not specified, defaults to "info".</li>
    061 *
    062 * <li><code>org.slf4j.simpleLogger.log.<em>a.b.c</em></code> - Logging detail
    063 * level for a SimpleLogger instance named "a.b.c". Right-side value must be one
    064 * of "trace", "debug", "info", "warn", "error" or "off". When a SimpleLogger
    065 * named "a.b.c" is initialized, its level is assigned from this property. If
    066 * unspecified, the level of nearest parent logger will be used, and if none is
    067 * set, then the value specified by
    068 * <code>org.slf4j.simpleLogger.defaultLogLevel</code> will be used.</li>
    069 *
    070 * <li><code>org.slf4j.simpleLogger.showDateTime</code> - Set to
    071 * <code>true</code> if you want the current date and time to be included in
    072 * output messages. Default is <code>false</code></li>
    073 *
    074 * <li><code>org.slf4j.simpleLogger.dateTimeFormat</code> - The date and time
    075 * format to be used in the output messages. The pattern describing the date and
    076 * time format is defined by <a href=
    077 * "http://docs.oracle.com/javase/1.5.0/docs/api/java/text/SimpleDateFormat.html">
    078 * <code>SimpleDateFormat</code></a>. If the format is not specified or is
    079 * invalid, the number of milliseconds since start up will be output.</li>
    080 *
    081 * <li><code>org.slf4j.simpleLogger.showThreadName</code> -Set to
    082 * <code>true</code> if you want to output the current thread name. Defaults to
    083 * <code>true</code>.</li>
    084 *
    085 * <li><code>org.slf4j.simpleLogger.showLogName</code> - Set to
    086 * <code>true</code> if you want the Logger instance name to be included in
    087 * output messages. Defaults to <code>true</code>.</li>
    088 *
    089 * <li><code>org.slf4j.simpleLogger.showShortLogName</code> - Set to
    090 * <code>true</code> if you want the last component of the name to be included
    091 * in output messages. Defaults to <code>false</code>.</li>
    092 *
    093 * <li><code>org.slf4j.simpleLogger.levelInBrackets</code> - Should the level
    094 * string be output in brackets? Defaults to <code>false</code>.</li>
    095 *
    096 * <li><code>org.slf4j.simpleLogger.warnLevelString</code> - The string value
    097 * output for the warn level. Defaults to <code>WARN</code>.</li>
    098 *
    099 * </ul>
    100 *
    101 * <p>
    102 * In addition to looking for system properties with the names specified above,
    103 * this implementation also checks for a class loader resource named
    104 * <code>"simplelogger.properties"</code>, and includes any matching definitions
    105 * from this resource (if it exists).
    106 * </p>
    107 *
    108 * <p>
    109 * With no configuration, the default output includes the relative time in
    110 * milliseconds, thread name, the level, logger name, and the message followed
    111 * by the line separator for the host. In log4j terms it amounts to the "%r [%t]
    112 * %level %logger - %m%n" pattern.
    113 * </p>
    114 * <p>
    115 * Sample output follows.
    116 * </p>
    117 *
    118 * <pre>
    119 * 176 [main] INFO examples.Sort - Populating an array of 2 elements in reverse order.
    120 * 225 [main] INFO examples.SortAlgo - Entered the sort method.
    121 * 304 [main] INFO examples.SortAlgo - Dump of integer array:
    122 * 317 [main] INFO examples.SortAlgo - Element [0] = 0
    123 * 331 [main] INFO examples.SortAlgo - Element [1] = 1
    124 * 343 [main] INFO examples.Sort - The next log statement should be an error message.
    125 * 346 [main] ERROR examples.SortAlgo - Tried to dump an uninitialized array.
    126 *   at org.log4j.examples.SortAlgo.dump(SortAlgo.java:58)
    127 *   at org.log4j.examples.Sort.main(Sort.java:64)
    128 * 467 [main] INFO  examples.Sort - Exiting main method.
    129 * </pre>
    130 *
    131 * <p>
    132 * This implementation is heavily inspired by
    133 * <a href="http://commons.apache.org/logging/">Apache Commons Logging</a>'s
    134 * SimpleLog.
    135 * </p>
    136 *
    137 * @author Ceki G&uuml;lc&uuml;
    138 * @author Scott Sanders
    139 * @author Rod Waldhoff
    140 * @author Robert Burrell Donkin
    141 * @author C&eacute;drik LIME
    142 */
    143public class SimpleLogger extends MarkerIgnoringBase {
    144
    145    private static final long serialVersionUID = -632788891211436180L;
    146
    147    private static long START_TIME = System.currentTimeMillis();
    148
    149    protected static final int LOG_LEVEL_TRACE = LocationAwareLogger.TRACE_INT;
    150    protected static final int LOG_LEVEL_DEBUG = LocationAwareLogger.DEBUG_INT;
    151    protected static final int LOG_LEVEL_INFO = LocationAwareLogger.INFO_INT;
    152    protected static final int LOG_LEVEL_WARN = LocationAwareLogger.WARN_INT;
    153    protected static final int LOG_LEVEL_ERROR = LocationAwareLogger.ERROR_INT;
    154    // The OFF level can only be used in configuration files to disable logging.
    155    // It has
    156    // no printing method associated with it in o.s.Logger interface.
    157    protected static final int LOG_LEVEL_OFF = LOG_LEVEL_ERROR + 10;
    158
    159    private static boolean INITIALIZED = false;
    160    static SimpleLoggerConfiguration CONFIG_PARAMS = null;
    161
    162    static void lazyInit() {
    163        if (INITIALIZED) {
    164            return;
    165        }
    166        INITIALIZED = true;
    167        init();
    168    }
    169
    170    // external software might be invoking this method directly. Do not rename
    171    // or change its semantics.
    172    static void init() {
    173        CONFIG_PARAMS = new SimpleLoggerConfiguration();
    174        CONFIG_PARAMS.init();
    175    }
    176
    177    /** The current log level */
    178    protected int currentLogLevel = LOG_LEVEL_INFO;
    179    /** The short name of this simple log instance */
    180    private transient String shortLogName = null;
    181
    182    /**
    183     * All system properties used by <code>SimpleLogger</code> start with this
    184     * prefix
    185     */
    186    public static final String SYSTEM_PREFIX = "org.slf4j.simpleLogger.";
    187
    188    public static final String LOG_KEY_PREFIX = SimpleLogger.SYSTEM_PREFIX + "log.";
    189
    190    public static final String CACHE_OUTPUT_STREAM_STRING_KEY = SimpleLogger.SYSTEM_PREFIX + "cacheOutputStream";
    191
    192    public static final String WARN_LEVEL_STRING_KEY = SimpleLogger.SYSTEM_PREFIX + "warnLevelString";
    193
    194    public static final String LEVEL_IN_BRACKETS_KEY = SimpleLogger.SYSTEM_PREFIX + "levelInBrackets";
    195
    196    public static final String LOG_FILE_KEY = SimpleLogger.SYSTEM_PREFIX + "logFile";
    197
    198    public static final String SHOW_SHORT_LOG_NAME_KEY = SimpleLogger.SYSTEM_PREFIX + "showShortLogName";
    199
    200    public static final String SHOW_LOG_NAME_KEY = SimpleLogger.SYSTEM_PREFIX + "showLogName";
    201
    202    public static final String SHOW_THREAD_NAME_KEY = SimpleLogger.SYSTEM_PREFIX + "showThreadName";
    203
    204    public static final String DATE_TIME_FORMAT_KEY = SimpleLogger.SYSTEM_PREFIX + "dateTimeFormat";
    205
    206    public static final String SHOW_DATE_TIME_KEY = SimpleLogger.SYSTEM_PREFIX + "showDateTime";
    207
    208    public static final String DEFAULT_LOG_LEVEL_KEY = SimpleLogger.SYSTEM_PREFIX + "defaultLogLevel";
    209
    210    /**
    211     * Package access allows only {@link SimpleLoggerFactory} to instantiate
    212     * SimpleLogger instances.
    213     */
    214    SimpleLogger(String name) {
    215        this.name = name;
    216
    217        String levelString = recursivelyComputeLevelString();
    218        if (levelString != null) {
    219            this.currentLogLevel = SimpleLoggerConfiguration.stringToLevel(levelString);
    220        } else {
    221            this.currentLogLevel = CONFIG_PARAMS.defaultLogLevel;
    222        }
    223    }
    224
    225    String recursivelyComputeLevelString() {
    226        String tempName = name;
    227        String levelString = null;
    228        int indexOfLastDot = tempName.length();
    229        while ((levelString == null) && (indexOfLastDot > -1)) {
    230            tempName = tempName.substring(0, indexOfLastDot);
    231            levelString = CONFIG_PARAMS.getStringProperty(SimpleLogger.LOG_KEY_PREFIX + tempName, null);
    232            indexOfLastDot = String.valueOf(tempName).lastIndexOf(".");
    233        }
    234        return levelString;
    235    }
    236
    237    /**
    238     * This is our internal implementation for logging regular
    239     * (non-parameterized) log messages.
    240     *
    241     * @param level
    242     *            One of the LOG_LEVEL_XXX constants defining the log level
    243     * @param message
    244     *            The message itself
    245     * @param t
    246     *            The exception whose stack trace should be logged
    247     */
    248    private void log(int level, String message, Throwable t) {
    249        if (!isLevelEnabled(level)) {
    250            return;
    251        }
    252
    253        StringBuilder buf = new StringBuilder(32);
    254
    255        // Append date-time if so configured
    256        if (CONFIG_PARAMS.showDateTime) {
    257            if (CONFIG_PARAMS.dateFormatter != null) {
    258                buf.append(getFormattedDate());
    259                buf.append(' ');
    260            } else {
    261                buf.append(System.currentTimeMillis() - START_TIME);
    262                buf.append(' ');
    263            }
    264        }
    265
    266        // Append current thread name if so configured
    267        if (CONFIG_PARAMS.showThreadName) {
    268            buf.append('[');
    269            buf.append(Thread.currentThread().getName());
    270            buf.append("] ");
    271        }
    272
    273        if (CONFIG_PARAMS.levelInBrackets)
    274            buf.append('[');
    275
    276        // Append a readable representation of the log level
    277        String levelStr = renderLevel(level);
    278        buf.append(levelStr);
    279        if (CONFIG_PARAMS.levelInBrackets)
    280            buf.append(']');
    281        buf.append(' ');
    282
    283        // Append the name of the log instance if so configured
    284        if (CONFIG_PARAMS.showShortLogName) {
    285            if (shortLogName == null)
    286                shortLogName = computeShortName();
    287            buf.append(String.valueOf(shortLogName)).append(" - ");
    288        } else if (CONFIG_PARAMS.showLogName) {
    289            buf.append(String.valueOf(name)).append(" - ");
    290        }
    291
    292        // Append the message
    293        buf.append(message);
    294
    295        write(buf, t);
    296
    297    }
    298
    299    protected String renderLevel(int level) {
    300        switch (level) {
    301        case LOG_LEVEL_TRACE:
    302            return "TRACE";
    303        case LOG_LEVEL_DEBUG:
    304            return ("DEBUG");
    305        case LOG_LEVEL_INFO:
    306            return "INFO";
    307        case LOG_LEVEL_WARN:
    308            return CONFIG_PARAMS.warnLevelString;
    309        case LOG_LEVEL_ERROR:
    310            return "ERROR";
    311        }
    312        throw new IllegalStateException("Unrecognized level [" + level + "]");
    313    }
    314
    315    void write(StringBuilder buf, Throwable t) {
    316        PrintStream targetStream = CONFIG_PARAMS.outputChoice.getTargetPrintStream();
    317
    318        targetStream.println(buf.toString());
    319        writeThrowable(t, targetStream);
    320        targetStream.flush();
    321    }
    322
    323    protected void writeThrowable(Throwable t, PrintStream targetStream) {
    324        if (t != null) {
    325            t.printStackTrace(targetStream);
    326        }
    327    }
    328
    329    private String getFormattedDate() {
    330        Date now = new Date();
    331        String dateText;
    332        synchronized (CONFIG_PARAMS.dateFormatter) {
    333            dateText = CONFIG_PARAMS.dateFormatter.format(now);
    334        }
    335        return dateText;
    336    }
    337
    338    private String computeShortName() {
    339        return name.substring(name.lastIndexOf(".") + 1);
    340    }
    341
    342    /**
    343     * For formatted messages, first substitute arguments and then log.
    344     *
    345     * @param level
    346     * @param format
    347     * @param arg1
    348     * @param arg2
    349     */
    350    private void formatAndLog(int level, String format, Object arg1, Object arg2) {
    351        if (!isLevelEnabled(level)) {
    352            return;
    353        }
    354        FormattingTuple tp = MessageFormatter.format(format, arg1, arg2);
    355        log(level, tp.getMessage(), tp.getThrowable());
    356    }
    357
    358    /**
    359     * For formatted messages, first substitute arguments and then log.
    360     *
    361     * @param level
    362     * @param format
    363     * @param arguments
    364     *            a list of 3 ore more arguments
    365     */
    366    private void formatAndLog(int level, String format, Object... arguments) {
    367        if (!isLevelEnabled(level)) {
    368            return;
    369        }
    370        FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments);
    371        log(level, tp.getMessage(), tp.getThrowable());
    372    }
    373
    374    /**
    375     * Is the given log level currently enabled?
    376     *
    377     * @param logLevel
    378     *            is this level enabled?
    379     */
    380    protected boolean isLevelEnabled(int logLevel) {
    381        // log level are numerically ordered so can use simple numeric
    382        // comparison
    383        return (logLevel >= currentLogLevel);
    384    }
    385
    386    /** Are {@code trace} messages currently enabled? */
    387    public boolean isTraceEnabled() {
    388        return isLevelEnabled(LOG_LEVEL_TRACE);
    389    }
    390
    391    /**
    392     * A simple implementation which logs messages of level TRACE according to
    393     * the format outlined above.
    394     */
    395    public void trace(String msg) {
    396        log(LOG_LEVEL_TRACE, msg, null);
    397    }
    398
    399    /**
    400     * Perform single parameter substitution before logging the message of level
    401     * TRACE according to the format outlined above.
    402     */
    403    public void trace(String format, Object param1) {
    404        formatAndLog(LOG_LEVEL_TRACE, format, param1, null);
    405    }
    406
    407    /**
    408     * Perform double parameter substitution before logging the message of level
    409     * TRACE according to the format outlined above.
    410     */
    411    public void trace(String format, Object param1, Object param2) {
    412        formatAndLog(LOG_LEVEL_TRACE, format, param1, param2);
    413    }
    414
    415    /**
    416     * Perform double parameter substitution before logging the message of level
    417     * TRACE according to the format outlined above.
    418     */
    419    public void trace(String format, Object... argArray) {
    420        formatAndLog(LOG_LEVEL_TRACE, format, argArray);
    421    }
    422
    423    /** Log a message of level TRACE, including an exception. */
    424    public void trace(String msg, Throwable t) {
    425        log(LOG_LEVEL_TRACE, msg, t);
    426    }
    427
    428    /** Are {@code debug} messages currently enabled? */
    429    public boolean isDebugEnabled() {
    430        return isLevelEnabled(LOG_LEVEL_DEBUG);
    431    }
    432
    433    /**
    434     * A simple implementation which logs messages of level DEBUG according to
    435     * the format outlined above.
    436     */
    437    public void debug(String msg) {
    438        log(LOG_LEVEL_DEBUG, msg, null);
    439    }
    440
    441    /**
    442     * Perform single parameter substitution before logging the message of level
    443     * DEBUG according to the format outlined above.
    444     */
    445    public void debug(String format, Object param1) {
    446        formatAndLog(LOG_LEVEL_DEBUG, format, param1, null);
    447    }
    448
    449    /**
    450     * Perform double parameter substitution before logging the message of level
    451     * DEBUG according to the format outlined above.
    452     */
    453    public void debug(String format, Object param1, Object param2) {
    454        formatAndLog(LOG_LEVEL_DEBUG, format, param1, param2);
    455    }
    456
    457    /**
    458     * Perform double parameter substitution before logging the message of level
    459     * DEBUG according to the format outlined above.
    460     */
    461    public void debug(String format, Object... argArray) {
    462        formatAndLog(LOG_LEVEL_DEBUG, format, argArray);
    463    }
    464
    465    /** Log a message of level DEBUG, including an exception. */
    466    public void debug(String msg, Throwable t) {
    467        log(LOG_LEVEL_DEBUG, msg, t);
    468    }
    469
    470    /** Are {@code info} messages currently enabled? */
    471    public boolean isInfoEnabled() {
    472        return isLevelEnabled(LOG_LEVEL_INFO);
    473    }
    474
    475    /**
    476     * A simple implementation which logs messages of level INFO according to
    477     * the format outlined above.
    478     */
    479    public void info(String msg) {
    480        log(LOG_LEVEL_INFO, msg, null);
    481    }
    482
    483    /**
    484     * Perform single parameter substitution before logging the message of level
    485     * INFO according to the format outlined above.
    486     */
    487    public void info(String format, Object arg) {
    488        formatAndLog(LOG_LEVEL_INFO, format, arg, null);
    489    }
    490
    491    /**
    492     * Perform double parameter substitution before logging the message of level
    493     * INFO according to the format outlined above.
    494     */
    495    public void info(String format, Object arg1, Object arg2) {
    496        formatAndLog(LOG_LEVEL_INFO, format, arg1, arg2);
    497    }
    498
    499    /**
    500     * Perform double parameter substitution before logging the message of level
    501     * INFO according to the format outlined above.
    502     */
    503    public void info(String format, Object... argArray) {
    504        formatAndLog(LOG_LEVEL_INFO, format, argArray);
    505    }
    506
    507    /** Log a message of level INFO, including an exception. */
    508    public void info(String msg, Throwable t) {
    509        log(LOG_LEVEL_INFO, msg, t);
    510    }
    511
    512    /** Are {@code warn} messages currently enabled? */
    513    public boolean isWarnEnabled() {
    514        return isLevelEnabled(LOG_LEVEL_WARN);
    515    }
    516
    517    /**
    518     * A simple implementation which always logs messages of level WARN
    519     * according to the format outlined above.
    520     */
    521    public void warn(String msg) {
    522        log(LOG_LEVEL_WARN, msg, null);
    523    }
    524
    525    /**
    526     * Perform single parameter substitution before logging the message of level
    527     * WARN according to the format outlined above.
    528     */
    529    public void warn(String format, Object arg) {
    530        formatAndLog(LOG_LEVEL_WARN, format, arg, null);
    531    }
    532
    533    /**
    534     * Perform double parameter substitution before logging the message of level
    535     * WARN according to the format outlined above.
    536     */
    537    public void warn(String format, Object arg1, Object arg2) {
    538        formatAndLog(LOG_LEVEL_WARN, format, arg1, arg2);
    539    }
    540
    541    /**
    542     * Perform double parameter substitution before logging the message of level
    543     * WARN according to the format outlined above.
    544     */
    545    public void warn(String format, Object... argArray) {
    546        formatAndLog(LOG_LEVEL_WARN, format, argArray);
    547    }
    548
    549    /** Log a message of level WARN, including an exception. */
    550    public void warn(String msg, Throwable t) {
    551        log(LOG_LEVEL_WARN, msg, t);
    552    }
    553
    554    /** Are {@code error} messages currently enabled? */
    555    public boolean isErrorEnabled() {
    556        return isLevelEnabled(LOG_LEVEL_ERROR);
    557    }
    558
    559    /**
    560     * A simple implementation which always logs messages of level ERROR
    561     * according to the format outlined above.
    562     */
    563    public void error(String msg) {
    564        log(LOG_LEVEL_ERROR, msg, null);
    565    }
    566
    567    /**
    568     * Perform single parameter substitution before logging the message of level
    569     * ERROR according to the format outlined above.
    570     */
    571    public void error(String format, Object arg) {
    572        formatAndLog(LOG_LEVEL_ERROR, format, arg, null);
    573    }
    574
    575    /**
    576     * Perform double parameter substitution before logging the message of level
    577     * ERROR according to the format outlined above.
    578     */
    579    public void error(String format, Object arg1, Object arg2) {
    580        formatAndLog(LOG_LEVEL_ERROR, format, arg1, arg2);
    581    }
    582
    583    /**
    584     * Perform double parameter substitution before logging the message of level
    585     * ERROR according to the format outlined above.
    586     */
    587    public void error(String format, Object... argArray) {
    588        formatAndLog(LOG_LEVEL_ERROR, format, argArray);
    589    }
    590
    591    /** Log a message of level ERROR, including an exception. */
    592    public void error(String msg, Throwable t) {
    593        log(LOG_LEVEL_ERROR, msg, t);
    594    }
    595
    596    public void log(LoggingEvent event) {
    597        int levelInt = event.getLevel().toInt();
    598
    599        if (!isLevelEnabled(levelInt)) {
    600            return;
    601        }
    602        FormattingTuple tp = MessageFormatter.arrayFormat(event.getMessage(), event.getArgumentArray(), event.getThrowable());
    603        log(levelInt, tp.getMessage(), event.getThrowable());
    604    }
    605
    606}