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