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