SimpleLog.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.logging.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogConfigurationException;
/**
* Simple implementation of Log that sends all enabled log messages,
* for all defined loggers, to System.err. The following system properties
* are supported to configure the behavior of this logger:
* <ul>
* <li>{@code org.apache.commons.logging.simplelog.defaultlog} -
* Default logging detail level for all instances of SimpleLog.
* Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").
* If not specified, defaults to "info". </li>
* <li>{@code org.apache.commons.logging.simplelog.log.xxxxx} -
* Logging detail level for a SimpleLog instance named "xxxxx".
* Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").
* If not specified, the default logging detail level is used.</li>
* <li>{@code org.apache.commons.logging.simplelog.showlogname} -
* Set to {@code true} if you want the Log instance name to be
* included in output messages. Defaults to {@code false}.</li>
* <li>{@code org.apache.commons.logging.simplelog.showShortLogname} -
* Set to {@code true} if you want the last component of the name to be
* included in output messages. Defaults to {@code true}.</li>
* <li>{@code org.apache.commons.logging.simplelog.showdatetime} -
* Set to {@code true} if you want the current date and time
* to be included in output messages. Default is {@code false}.</li>
* <li>{@code org.apache.commons.logging.simplelog.dateTimeFormat} -
* The date and time format to be used in the output messages.
* The pattern describing the date and time format is the same that is
* used in {@link java.text.SimpleDateFormat}. If the format is not
* specified or is invalid, the default format is used.
* The default format is {@code yyyy/MM/dd HH:mm:ss:SSS zzz}.</li>
* </ul>
* <p>
* In addition to looking for system properties with the names specified
* above, this implementation also checks for a class loader resource named
* {@code "simplelog.properties"}, and includes any matching definitions
* from this resource (if it exists).
* </p>
*/
public class SimpleLog implements Log, Serializable {
/** Serializable version identifier. */
private static final long serialVersionUID = 136942970684951178L;
/** All system properties used by {@code SimpleLog} start with this */
static protected final String systemPrefix = "org.apache.commons.logging.simplelog.";
/** Properties loaded from simplelog.properties */
static protected final Properties simpleLogProps = new Properties();
/** The default format to use when formating dates */
static protected final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz";
/** Include the instance name in the log message? */
static volatile protected boolean showLogName;
/**
* Include the short name (last component) of the logger in the log
* message. Defaults to true - otherwise we'll be lost in a flood of
* messages without knowing who sends them.
*/
static volatile protected boolean showShortName = true;
/** Include the current time in the log message */
static volatile protected boolean showDateTime;
/** The date and time format to use in the log message */
static volatile protected String dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
/**
* Used to format times.
* <p>
* Any code that accesses this object should first obtain a lock on it,
* that is, use synchronized(dateFormatter); this requirement was introduced
* in 1.1.1 to fix an existing thread safety bug (SimpleDateFormat.format
* is not thread-safe).
* </p>
*/
static protected DateFormat dateFormatter;
/** "Trace" level logging. */
public static final int LOG_LEVEL_TRACE = 1;
/** "Debug" level logging. */
public static final int LOG_LEVEL_DEBUG = 2;
/** "Info" level logging. */
public static final int LOG_LEVEL_INFO = 3;
/** "Warn" level logging. */
public static final int LOG_LEVEL_WARN = 4;
/** "Error" level logging. */
public static final int LOG_LEVEL_ERROR = 5;
/** "Fatal" level logging. */
public static final int LOG_LEVEL_FATAL = 6;
/** Enable all logging levels */
public static final int LOG_LEVEL_ALL = LOG_LEVEL_TRACE - 1;
/** Enable no logging levels */
public static final int LOG_LEVEL_OFF = LOG_LEVEL_FATAL + 1;
// Initialize class attributes.
// Load properties file, if found.
// Override with system properties.
static {
// Add props from the resource simplelog.properties
try (InputStream in = getResourceAsStream("simplelog.properties")) {
if (null != in) {
simpleLogProps.load(in);
}
} catch (final IOException ignore) {
// Ignore
}
showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName);
showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName);
showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime);
if (showDateTime) {
dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat", dateTimeFormat);
try {
dateFormatter = new SimpleDateFormat(dateTimeFormat);
} catch (final IllegalArgumentException e) {
// If the format pattern is invalid - use the default format
dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
dateFormatter = new SimpleDateFormat(dateTimeFormat);
}
}
}
private static boolean getBooleanProperty(final String name, final boolean defaultValue) {
final String prop = getStringProperty(name);
return prop == null ? defaultValue : Boolean.parseBoolean(prop);
}
/**
* Gets the thread context class loader if available. Otherwise return null.
*
* The thread context class loader is available if certain security conditions are met.
*
* @throws LogConfigurationException if a suitable class loader cannot be identified.
*/
private static ClassLoader getContextClassLoader() {
ClassLoader classLoader = null;
// Get the thread context class loader (if there is one)
try {
classLoader = Thread.currentThread().getContextClassLoader();
} catch (final RuntimeException e) {
/**
* getContextClassLoader() throws SecurityException when the context class loader isn't an ancestor of the calling class's class loader, or if
* security permissions are restricted.
*
* 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
* calls elsewhere (to obtain a class loader) will trigger this exception where we can make a distinction.
*/
// Capture 'e.getTargetException()' exception for details
// alternate: log 'e.getTargetException()', and pass back 'e'.
if (!(e instanceof SecurityException)) {
throw new LogConfigurationException("Unexpected SecurityException", e);
}
}
if (classLoader == null) {
classLoader = SimpleLog.class.getClassLoader();
}
// Return the selected class loader
return classLoader;
}
private static InputStream getResourceAsStream(final String name) {
return AccessController.doPrivileged((PrivilegedAction<InputStream>) () -> {
final ClassLoader threadCL = getContextClassLoader();
if (threadCL != null) {
return threadCL.getResourceAsStream(name);
}
return ClassLoader.getSystemResourceAsStream(name);
});
}
private static String getStringProperty(final String name) {
String prop = null;
try {
prop = System.getProperty(name);
} catch (final SecurityException e) {
// Ignore
}
return prop == null ? simpleLogProps.getProperty(name) : prop;
}
private static String getStringProperty(final String name, final String defaultValue) {
final String prop = getStringProperty(name);
return prop == null ? defaultValue : prop;
}
/** The name of this simple log instance */
protected volatile String logName;
/** The current log level */
protected volatile int currentLogLevel;
/** The short name of this simple log instance */
private volatile String shortLogName;
/**
* Constructs a simple log with given name.
*
* @param name log name
*/
public SimpleLog(String name) {
logName = name;
// Set initial log level
// Used to be: set default log level to ERROR
// IMHO it should be lower, but at least info (costin).
setLevel(LOG_LEVEL_INFO);
// Set log level from properties
String level = getStringProperty(systemPrefix + "log." + logName);
int i = String.valueOf(name).lastIndexOf(".");
while (level == null && i > -1) {
name = name.substring(0, i);
level = getStringProperty(systemPrefix + "log." + name);
i = String.valueOf(name).lastIndexOf(".");
}
if (level == null) {
level = getStringProperty(systemPrefix + "defaultlog");
}
if (level != null) {
level = level.toLowerCase(Locale.ROOT);
}
if (level != null) {
switch (level) {
case "all":
setLevel(LOG_LEVEL_ALL);
break;
case "trace":
setLevel(LOG_LEVEL_TRACE);
break;
case "debug":
setLevel(LOG_LEVEL_DEBUG);
break;
case "info":
setLevel(LOG_LEVEL_INFO);
break;
case "warn":
setLevel(LOG_LEVEL_WARN);
break;
case "error":
setLevel(LOG_LEVEL_ERROR);
break;
case "fatal":
setLevel(LOG_LEVEL_FATAL);
break;
case "off":
setLevel(LOG_LEVEL_OFF);
break;
default:
// do nothing
}
}
}
/**
* Logs a message with
* {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG}.
*
* @param message to log
* @see org.apache.commons.logging.Log#debug(Object)
*/
@Override
public final void debug(final Object message) {
if (isLevelEnabled(LOG_LEVEL_DEBUG)) {
log(LOG_LEVEL_DEBUG, message, null);
}
}
/**
* Logs a message with
* {@code org.apache.commons.logging.impl.LOG_LEVEL_DEBUG}.
*
* @param message to log
* @param t log this cause
* @see org.apache.commons.logging.Log#debug(Object, Throwable)
*/
@Override
public final void debug(final Object message, final Throwable t) {
if (isLevelEnabled(LOG_LEVEL_DEBUG)) {
log(LOG_LEVEL_DEBUG, message, t);
}
}
/**
* Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR}.
*
* @param message to log
* @see org.apache.commons.logging.Log#error(Object)
*/
@Override
public final void error(final Object message) {
if (isLevelEnabled(LOG_LEVEL_ERROR)) {
log(LOG_LEVEL_ERROR, message, null);
}
}
/**
* Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR}.
*
* @param message to log
* @param t log this cause
* @see org.apache.commons.logging.Log#error(Object, Throwable)
*/
@Override
public final void error(final Object message, final Throwable t) {
if (isLevelEnabled(LOG_LEVEL_ERROR)) {
log(LOG_LEVEL_ERROR, message, t);
}
}
/**
* Log a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL}.
*
* @param message to log
* @see org.apache.commons.logging.Log#fatal(Object)
*/
@Override
public final void fatal(final Object message) {
if (isLevelEnabled(LOG_LEVEL_FATAL)) {
log(LOG_LEVEL_FATAL, message, null);
}
}
/**
* Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL}.
*
* @param message to log
* @param t log this cause
* @see org.apache.commons.logging.Log#fatal(Object, Throwable)
*/
@Override
public final void fatal(final Object message, final Throwable t) {
if (isLevelEnabled(LOG_LEVEL_FATAL)) {
log(LOG_LEVEL_FATAL, message, t);
}
}
/**
* Gets logging level.
*
* @return logging level.
*/
public int getLevel() {
return currentLogLevel;
}
/**
* Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO}.
*
* @param message to log
* @see org.apache.commons.logging.Log#info(Object)
*/
@Override
public final void info(final Object message) {
if (isLevelEnabled(LOG_LEVEL_INFO)) {
log(LOG_LEVEL_INFO,message,null);
}
}
/**
* Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO}.
*
* @param message to log
* @param t log this cause
* @see org.apache.commons.logging.Log#info(Object, Throwable)
*/
@Override
public final void info(final Object message, final Throwable t) {
if (isLevelEnabled(LOG_LEVEL_INFO)) {
log(LOG_LEVEL_INFO, message, t);
}
}
/**
* Tests whether debug messages are enabled.
* <p>
* This allows expensive operations such as {@code String}
* concatenation to be avoided when the message will be ignored by the
* logger.
* </p>
*/
@Override
public final boolean isDebugEnabled() {
return isLevelEnabled(LOG_LEVEL_DEBUG);
}
/**
* Tests whether error messages are enabled.
* <p>
* This allows expensive operations such as {@code String}
* concatenation to be avoided when the message will be ignored by the
* logger.
* </p>
*/
@Override
public final boolean isErrorEnabled() {
return isLevelEnabled(LOG_LEVEL_ERROR);
}
/**
* Tests whether fatal messages are enabled.
* <p>
* This allows expensive operations such as {@code String}
* concatenation to be avoided when the message will be ignored by the
* logger.
* </p>
*/
@Override
public final boolean isFatalEnabled() {
return isLevelEnabled(LOG_LEVEL_FATAL);
}
/**
* Tests whether info messages are enabled.
* <p>
* This allows expensive operations such as {@code String}
* concatenation to be avoided when the message will be ignored by the
* logger.
* </p>
*/
@Override
public final boolean isInfoEnabled() {
return isLevelEnabled(LOG_LEVEL_INFO);
}
/**
* Tests whether the given level is enabled.
*
* @param logLevel is this level enabled?
* @return whether the given log level currently enabled.
*/
protected boolean isLevelEnabled(final int logLevel) {
// log level are numerically ordered so can use simple numeric
// comparison
return logLevel >= currentLogLevel;
}
/**
* Tests whether trace messages are enabled.
* <p>
* This allows expensive operations such as {@code String}
* concatenation to be avoided when the message will be ignored by the
* logger.
* </p>
*/
@Override
public final boolean isTraceEnabled() {
return isLevelEnabled(LOG_LEVEL_TRACE);
}
/**
* Tests whether warn messages are enabled.
* <p>
* This allows expensive operations such as {@code String}
* concatenation to be avoided when the message will be ignored by the
* logger.
* </p>
*/
@Override
public final boolean isWarnEnabled() {
return isLevelEnabled(LOG_LEVEL_WARN);
}
/**
* Do the actual logging.
* <p>
* This method assembles the message and then calls {@code write()}
* to cause it to be written.
* </p>
*
* @param type One of the LOG_LEVEL_XXX constants defining the log level
* @param message The message itself (typically a String)
* @param t The exception whose stack trace should be logged
*/
protected void log(final int type, final Object message, final Throwable t) {
// Use a string buffer for better performance
final StringBuilder buf = new StringBuilder();
// Append date-time if so configured
if (showDateTime) {
final Date now = new Date();
String dateText;
synchronized (dateFormatter) {
dateText = dateFormatter.format(now);
}
buf.append(dateText);
buf.append(" ");
}
// Append a readable representation of the log level
switch (type) {
case LOG_LEVEL_TRACE:
buf.append("[TRACE] ");
break;
case LOG_LEVEL_DEBUG:
buf.append("[DEBUG] ");
break;
case LOG_LEVEL_INFO:
buf.append("[INFO] ");
break;
case LOG_LEVEL_WARN:
buf.append("[WARN] ");
break;
case LOG_LEVEL_ERROR:
buf.append("[ERROR] ");
break;
case LOG_LEVEL_FATAL:
buf.append("[FATAL] ");
break;
default:
// Or throw?
buf.append("[UNDEFINED] ");
break;
}
// Append the name of the log instance if so configured
if (showShortName) {
if (shortLogName == null) {
// Cut all but the last component of the name for both styles
final String slName = logName.substring(logName.lastIndexOf(".") + 1);
shortLogName = slName.substring(slName.lastIndexOf("/") + 1);
}
buf.append(String.valueOf(shortLogName)).append(" - ");
} else if (showLogName) {
buf.append(String.valueOf(logName)).append(" - ");
}
// Append the message
buf.append(String.valueOf(message));
// Append stack trace if not null
if (t != null) {
buf.append(" <");
buf.append(t.toString());
buf.append(">");
final StringWriter sw = new StringWriter(1024);
try (PrintWriter pw = new PrintWriter(sw)) {
t.printStackTrace(pw);
}
buf.append(sw.toString());
}
// Print to the appropriate destination
write(buf);
}
/**
* Sets logging level.
*
* @param currentLogLevel new logging level
*/
public void setLevel(final int currentLogLevel) {
this.currentLogLevel = currentLogLevel;
}
/**
* Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE}.
*
* @param message to log
* @see org.apache.commons.logging.Log#trace(Object)
*/
@Override
public final void trace(final Object message) {
if (isLevelEnabled(LOG_LEVEL_TRACE)) {
log(LOG_LEVEL_TRACE, message, null);
}
}
/**
* Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE}.
*
* @param message to log
* @param t log this cause
* @see org.apache.commons.logging.Log#trace(Object, Throwable)
*/
@Override
public final void trace(final Object message, final Throwable t) {
if (isLevelEnabled(LOG_LEVEL_TRACE)) {
log(LOG_LEVEL_TRACE, message, t);
}
}
/**
* Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN}.
*
* @param message to log
* @see org.apache.commons.logging.Log#warn(Object)
*/
@Override
public final void warn(final Object message) {
if (isLevelEnabled(LOG_LEVEL_WARN)) {
log(LOG_LEVEL_WARN, message, null);
}
}
/**
* Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN}.
*
* @param message to log
* @param t log this cause
* @see org.apache.commons.logging.Log#warn(Object, Throwable)
*/
@Override
public final void warn(final Object message, final Throwable t) {
if (isLevelEnabled(LOG_LEVEL_WARN)) {
log(LOG_LEVEL_WARN, message, t);
}
}
/**
* Writes the content of the message accumulated in the specified
* {@code StringBuffer} to the appropriate output destination. The
* default implementation writes to {@code System.err}.
*
* @param buffer A {@code StringBuffer} containing the accumulated
* text to be logged
*/
private void write(final Object buffer) {
System.err.println(Objects.toString(buffer));
}
/**
* Writes the content of the message accumulated in the specified
* {@code StringBuffer} to the appropriate output destination. The
* default implementation writes to {@code System.err}.
*
* @param buffer A {@code StringBuffer} containing the accumulated
* text to be logged
*/
protected void write(final StringBuffer buffer) {
System.err.println(Objects.toString(buffer));
}
}