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