    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