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