org.openbp.common.logger.LogUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openbp-common Show documentation
Show all versions of openbp-common Show documentation
Common base for all OpenBP projects
The newest version!
/*
* Licensed 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.openbp.common.logger;
import java.util.concurrent.ConcurrentHashMap;
import org.openbp.common.ExceptionUtil;
import org.openbp.common.MsgFormat;
import org.openbp.common.string.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides utility methods for an implementation-independant and business object-centered logging.
*
* There are a number of differences to regular logging:
*
* - Any Throwable argument given in the argument list at any position will be removed from the argument list and instead will be used as Throwable argument to the logger. The exception messages of all exceptions in the hierarchy will be collected and appended to the log message.
* - The message will be expanded by {@link MsgFormat} and supports the usual placeholders such as {0} etc. In addition, the placeholder $0 will be processed in the same way as {0} and in addition wrapped in single quotes.
* - The expanded log message will be returned by the log methods if the log level is enabled (error log messages will always be expanded regardless of the log level setting so they can be e. g. used as exception messages even if error logging has been disabled).
*
*/
public final class LogUtil
{
/** Line separator buffer */
private static final String LINE_SEPARATOR = System.getProperty("line.separator", "\n");
/**
* Logger map caches loggers to avoid multiple calls to the SLF4J LoggerFactory.
* for the same log name.
*/
private static ConcurrentHashMap loggerMap = new ConcurrentHashMap(100);
/**
* Private constructor prevents instantiation.
*/
private LogUtil()
{
}
//////////////////////////////////////////////////
// @@ Logging shortcuts
//////////////////////////////////////////////////
/**
* Logs a debug message with no arguments.
*/
public static String debug(Class cls, String msg)
{
return log(LogLevel.DEBUG, cls, msg);
}
/**
* Logs a debug message with a single argument.
*/
public static String debug(Class cls, String msg, Object arg1)
{
return log(LogLevel.DEBUG, cls, msg, arg1);
}
/**
* Logs a debug message with two arguments.
*/
public static String debug(Class cls, String msg, Object arg1, Object arg2)
{
return log(LogLevel.DEBUG, cls, msg, arg1, arg2);
}
/**
* Logs a debug message with tree arguments.
*/
public static String debug(Class cls, String msg, Object arg1, Object arg2, Object arg3)
{
return log(LogLevel.DEBUG, cls, msg, arg1, arg2, arg3);
}
/**
* Logs a debug message with four arguments.
*/
public static String debug(Class cls, String msg, Object arg1, Object arg2, Object arg3, Object arg4)
{
return log(LogLevel.DEBUG, cls, msg, arg1, arg2, arg3, arg4);
}
/**
* Logs a debug message using an argument array.
*/
public static String debug(Class cls, String msg, Object [] args)
{
return log(LogLevel.DEBUG, cls, msg, args);
}
/**
* Logs a trace message with no arguments.
*/
public static String trace(Class cls, String msg)
{
return log(LogLevel.TRACE, cls, msg);
}
/**
* Logs a trace message with a single argument.
*/
public static String trace(Class cls, String msg, Object arg1)
{
return log(LogLevel.TRACE, cls, msg, arg1);
}
/**
* Logs a trace message with two arguments.
*/
public static String trace(Class cls, String msg, Object arg1, Object arg2)
{
return log(LogLevel.TRACE, cls, msg, arg1, arg2);
}
/**
* Logs a trace message with tree arguments.
*/
public static String trace(Class cls, String msg, Object arg1, Object arg2, Object arg3)
{
return log(LogLevel.TRACE, cls, msg, arg1, arg2, arg3);
}
/**
* Logs a trace message with four arguments.
*/
public static String trace(Class cls, String msg, Object arg1, Object arg2, Object arg3, Object arg4)
{
return log(LogLevel.TRACE, cls, msg, arg1, arg2, arg3, arg4);
}
/**
* Logs a trace message using an argument array.
*/
public static String trace(Class cls, String msg, Object [] args)
{
return log(LogLevel.TRACE, cls, msg, args);
}
/**
* Logs an informational message with no arguments.
*/
public static String info(Class cls, String msg)
{
return log(LogLevel.INFO, cls, msg);
}
/**
* Logs an informational message with a single argument.
*/
public static String info(Class cls, String msg, Object arg1)
{
return log(LogLevel.INFO, cls, msg, arg1);
}
/**
* Logs an informational message with two arguments.
*/
public static String info(Class cls, String msg, Object arg1, Object arg2)
{
return log(LogLevel.INFO, cls, msg, arg1, arg2);
}
/**
* Logs an informational message with tree arguments.
*/
public static String info(Class cls, String msg, Object arg1, Object arg2, Object arg3)
{
return log(LogLevel.INFO, cls, msg, arg1, arg2, arg3);
}
/**
* Logs an informational message with four arguments.
*/
public static String info(Class cls, String msg, Object arg1, Object arg2, Object arg3, Object arg4)
{
return log(LogLevel.INFO, cls, msg, arg1, arg2, arg3, arg4);
}
/**
* Logs an informational message using an argument array.
*/
public static String info(Class cls, String msg, Object [] args)
{
return log(LogLevel.INFO, cls, msg, args);
}
/**
* Logs a warning message with no arguments.
*/
public static String warn(Class cls, String msg)
{
return log(LogLevel.WARN, cls, msg);
}
/**
* Logs a warning message with a single argument.
*/
public static String warn(Class cls, String msg, Object arg1)
{
return log(LogLevel.WARN, cls, msg, arg1);
}
/**
* Logs a warning message with two arguments.
*/
public static String warn(Class cls, String msg, Object arg1, Object arg2)
{
return log(LogLevel.WARN, cls, msg, arg1, arg2);
}
/**
* Logs a warning message with tree arguments.
*/
public static String warn(Class cls, String msg, Object arg1, Object arg2, Object arg3)
{
return log(LogLevel.WARN, cls, msg, arg1, arg2, arg3);
}
/**
* Logs a warning message with four arguments.
*/
public static String warn(Class cls, String msg, Object arg1, Object arg2, Object arg3, Object arg4)
{
return log(LogLevel.WARN, cls, msg, arg1, arg2, arg3, arg4);
}
/**
* Logs a warning message using an argument array.
*/
public static String warn(Class cls, String msg, Object [] args)
{
return log(LogLevel.WARN, cls, msg, args);
}
/**
* Logs an error message with no arguments.
*/
public static String error(Class cls, String msg)
{
return log(LogLevel.ERROR, cls, msg);
}
/**
* Logs an error message with a single argument.
*/
public static String error(Class cls, String msg, Object arg1)
{
return log(LogLevel.ERROR, cls, msg, arg1);
}
/**
* Logs an error message with two arguments.
*/
public static String error(Class cls, String msg, Object arg1, Object arg2)
{
return log(LogLevel.ERROR, cls, msg, arg1, arg2);
}
/**
* Logs an error message with tree arguments.
*/
public static String error(Class cls, String msg, Object arg1, Object arg2, Object arg3)
{
return log(LogLevel.ERROR, cls, msg, arg1, arg2, arg3);
}
/**
* Logs an error message with four arguments.
*/
public static String error(Class cls, String msg, Object arg1, Object arg2, Object arg3, Object arg4)
{
return log(LogLevel.ERROR, cls, msg, arg1, arg2, arg3, arg4);
}
/**
* Logs an error message using an argument array.
*/
public static String error(Class cls, String msg, Object [] args)
{
return log(LogLevel.ERROR, cls, msg, args);
}
//////////////////////////////////////////////////
// @@ Argument formatting - general
//////////////////////////////////////////////////
/**
* Logs a message with no arguments.
*/
public static String log(String logLevel, Class cls, String msg)
{
if (!shouldDoLog(logLevel, cls))
return null;
Object [] nullArgs = null; // To prevent compiler warning about unnecessary cast
return log(logLevel, cls, msg, nullArgs);
}
/**
* Logs a message with a single argument.
*/
public static String log(String logLevel, Class cls, String msg, Object arg1)
{
if (!shouldDoLog(logLevel, cls))
return null;
return log(logLevel, cls, msg, new Object [] { arg1 });
}
/**
* Logs a message with two arguments.
*/
public static String log(String logLevel, Class cls, String msg, Object arg1, Object arg2)
{
if (!shouldDoLog(logLevel, cls))
return null;
return log(logLevel, cls, msg, new Object [] { arg1, arg2 });
}
/**
* Logs a message with tree arguments.
*/
public static String log(String logLevel, Class cls, String msg, Object arg1, Object arg2, Object arg3)
{
if (!shouldDoLog(logLevel, cls))
return null;
return log(logLevel, cls, msg, new Object [] { arg1, arg2, arg3 });
}
/**
* Logs a message with four arguments.
*/
public static String log(String logLevel, Class cls, String msg, Object arg1, Object arg2, Object arg3, Object arg4)
{
if (!shouldDoLog(logLevel, cls))
return null;
return log(logLevel, cls, msg, new Object [] { arg1, arg2, arg3, arg4 });
}
/**
* Logs a message using an argument array.
* This method formats a string message together with an argument array
* using the standard {@link MsgFormat} mechanism, but adds extra
* processing for null arguments (which are formatted as [null]), and
* handles an extra exception that might be contained as last value
* in the argument array.
*
* @param logLevel Log level (priority)
* @param cls Class to be used for logger identification
* @param msg The message containing placeholder's like {0} etc.
* Special convenience: Arguments that should appear in simple quotes ('\'') do not need to be
* provided as "...''{0}''..." in the log message, the shortcut "...$0..." can be used instead.
* If the "$" is followed by a non-digit character, it will be printed as it is.
* This should eliminate the common error of forgetting a quote, which would confuse MessageFormat.
* @param args Array of message arguments
* @return The formatted message if log logging is enabled for this log level (or if the level is ERROR or higher) or null otherwise
*/
public static String log(String logLevel, Class cls, String msg, Object [] args)
{
return doLog(logLevel, cls, msg, args);
}
/**
* Logs a message using an argument array.
* This method formats a string message together with an argument array
* using the standard {@link MsgFormat} mechanism, but adds extra
* processing for null arguments (which are formatted as [null]), and
* handles an extra exception that might be contained as last value
* in the argument array.
*
* @param logLevel Log level (priority)
* @param loggerName Logger name to be used for logger identification
* @param msg The message containing placeholder's like {0} etc.
* Special convenience: Arguments that should appear in simple quotes ('\'') do not need to be
* provided as "...''{0}''..." in the log message, the shortcut "...$0..." can be used instead.
* If the "$" is followed by a non-digit character, it will be printed as it is.
* This should eliminate the common error of forgetting a quote, which would confuse MessageFormat.
* @param args Array of message arguments
* @return The formatted message if log logging is enabled for this log level (or if the level is ERROR or higher) or null otherwise
*/
public static String log(String logLevel, String loggerName, String msg, Object [] args)
{
return doLog(logLevel, loggerName, msg, args);
}
/**
* Logs a message using an argument array.
* This method formats a string message together with an argument array
* using the standard {@link MsgFormat} mechanism, but adds extra
* processing for null arguments (which are formatted as [null]), and
* handles an extra exception that might be contained as last value
* in the argument array.
*
* @param logLevel Log level (priority)
* @param cls Class to be used for logger identification
* @param msg The message containing placeholder's like {0} etc.
* Special convenience: Arguments that should appear in simple quotes ('\'') do not need to be
* provided as "...''{0}''..." in the log message, the shortcut "...$0..." can be used instead.
* If the "$" is followed by a non-digit character, it will be printed as it is.
* This should eliminate the common error of forgetting a quote, which would confuse MessageFormat.
* @param args Array of message arguments
* If the last parameter is a java.lang.Throwable, it will not be used as message argument, instead
* it will be passed to the underlying logger method as exception argument.
* @return The formatted message if log logging is enabled for this log level (or if the level is ERROR or higher) or null otherwise
* This message can be used e. g. as exception message if an exception is to be thrown after the log entry.
*/
public static String doLog(String logLevel, Class cls, String msg, Object [] args)
{
return doLog(logLevel, cls != null ? cls.getName() : null, msg, args);
}
//////////////////////////////////////////////////
// @@ Code guards
//////////////////////////////////////////////////
/**
* Checks if debug logging is enabled for the given logger.
*
* @param cls Class to be used for logger identification
*/
public static boolean isDebugEnabled(Class cls)
{
return isLoggerEnabled(cls, LogLevel.DEBUG);
}
/**
* Checks if info logging is enabled for the given logger.
*
* @param cls Class to be used for logger identification
*/
public static boolean isInfoEnabled(Class cls)
{
return isLoggerEnabled(cls, LogLevel.INFO);
}
/**
* Checks if trace logging is enabled for the given logger.
*
* @param cls Class to be used for logger identification
*/
public static boolean isTraceEnabled(Class cls)
{
return isLoggerEnabled(cls, LogLevel.TRACE);
}
/**
* Checks if warn logging is enabled for the given logger.
*
* @param cls Class to be used for logger identification
*/
public static boolean isWarnEnabled(Class cls)
{
return isLoggerEnabled(cls, LogLevel.WARN);
}
/**
* Checks if error logging is enabled for the given logger.
*
* @param cls Class to be used for logger identification
*/
public static boolean isErrorEnabled(Class cls)
{
return isLoggerEnabled(cls, LogLevel.ERROR);
}
/**
* Checks if the specified logger is enabled.
*/
public static boolean isLoggerEnabled(Class cls, String logLevel)
{
return isLoggerEnabled(cls != null ? cls.getName() : null, logLevel);
}
/**
* Checks if the specified logger is enabled.
*/
public static boolean isLoggerEnabled(String loggerName, String logLevel)
{
Logger logger = mapLogger(loggerName);
if (LogLevel.DEBUG.equalsIgnoreCase(logLevel))
return logger.isDebugEnabled();
if (LogLevel.TRACE.equalsIgnoreCase(logLevel))
return logger.isTraceEnabled();
if (LogLevel.INFO.equalsIgnoreCase(logLevel))
return logger.isInfoEnabled();
if (LogLevel.WARN.equalsIgnoreCase(logLevel))
return logger.isWarnEnabled();
if (LogLevel.ERROR.equalsIgnoreCase(logLevel))
return logger.isErrorEnabled();
return true;
}
//////////////////////////////////////////////////
// @@ Logging implementation
//////////////////////////////////////////////////
private static boolean shouldDoLog(String logLevel, Class cls)
{
if (LogLevel.ERROR.equals(logLevel))
{
// Always look at high log levels - maybe just for producing an error string
return true;
}
return isLoggerEnabled(cls, logLevel);
}
private static void printLog(String loggerName, String logLevel, String msg, Throwable throwable)
{
Logger logger = mapLogger(loggerName);
if (LogLevel.DEBUG.equalsIgnoreCase(logLevel))
{
if (throwable != null)
logger.debug(msg, throwable);
else
logger.debug(msg);
}
else if (LogLevel.TRACE.equalsIgnoreCase(logLevel))
{
if (throwable != null)
logger.trace(msg, throwable);
else
logger.trace(msg);
}
else if (LogLevel.INFO.equalsIgnoreCase(logLevel))
{
if (throwable != null)
logger.info(msg, throwable);
else
logger.info(msg);
}
else if (LogLevel.WARN.equalsIgnoreCase(logLevel))
{
if (throwable != null)
logger.warn(msg, throwable);
else
logger.warn(msg);
}
else if (LogLevel.ERROR.equalsIgnoreCase(logLevel))
{
if (throwable != null)
logger.error(msg, throwable);
else
logger.error(msg);
}
else
{
if (throwable != null)
logger.warn("Unknown log level for message '" + msg + "' for logger '" + loggerName + "'.");
else
logger.warn("Unknown log level for message '" + msg + "' for logger '" + loggerName + "'.", throwable);
}
}
//////////////////////////////////////////////////
// @@ Argument formatting - general
//////////////////////////////////////////////////
/**
* Logs a message using an argument array.
* This method formats a string message together with an argument array
* using the standard {@link MsgFormat} mechanism, but adds extra
* processing for null arguments (which are formatted as [null]), and
* handles an extra exception that might be contained as last value
* in the argument array.
*
* @param logLevel Log level (priority)
* @param loggerName Logger name to be used for logger identification
* @param msg The message containing placeholder's like {0} etc.
* Special convenience: Arguments that should appear in simple quotes ('\'') do not need to be
* provided as "...''{0}''..." in the log message, the shortcut "...$0..." can be used instead.
* If the "$" is followed by a non-digit character, it will be printed as it is.
* This should eliminate the common error of forgetting a quote, which would confuse MessageFormat.
* @param args Array of message arguments
* If the last parameter is a java.lang.Throwable, it will not be used as message argument, instead
* it will be passed to the underlying logger method as exception argument.
* @return The formatted message if log logging is enabled for this log level (or if the level is ERROR or higher) or null otherwise
* This message can be used e. g. as exception message if an exception is to be thrown after the log entry.
*/
public static String doLog(String logLevel, String loggerName, String msg, Object [] args)
{
boolean enabled = true;
if (!isLoggerEnabled(loggerName, logLevel))
{
// In case of errors, always generate the message; for any other types, exit if type disabled.
if (logLevel != LogLevel.ERROR)
return null;
enabled = false;
}
Throwable throwable = null;
if (args != null && args.length > 0)
{
boolean needFormatting = true;
Object t = args [args.length - 1];
if (t instanceof Throwable)
{
throwable = (Throwable) t;
if (args.length == 1)
{
// The throwable was our only argument
needFormatting = false;
}
}
if (needFormatting)
{
// Replace null arguments with symbolic strings.
for (int i = 0; i < args.length; i++)
{
if (args [i] == null)
{
args [i] = "(null)";
}
}
try
{
// Format the mesage.
msg = MsgFormat.format(msg, args);
}
catch (Exception e)
{
// Build a replacement messsage.
String fMsg = "Logging error: Can't format message '" + msg + "' with " + args.length + " arguments.\n\tException:\n\t" + ExceptionUtil.getNestedTrace(e);
// Enforce showing the replacement message (with ERROR level).
mapLogger(LogUtil.class.getName()).error(fMsg);
}
}
}
// The message we have processed is identical for the logged and the returned message - so far
String retMsg = msg;
String logMsg = msg;
// Append exception information to the message
if (throwable != null)
{
// Append the exception messages to the returned message
String exceptionMsg = ExceptionUtil.getNestedMessage(throwable);
if (exceptionMsg != null)
{
retMsg += '\n';
retMsg += exceptionMsg;
if (enabled)
{
logMsg += '\n';
logMsg += exceptionMsg;
}
}
}
// Write the log message if the logger is enabled
if (enabled)
{
// Log4J doesn't convert simple newlines within the message to os-dependent newlines,
// so we do it here.
if (!LINE_SEPARATOR.equals("\n"))
{
logMsg = StringUtil.substitute(logMsg, "\n", LINE_SEPARATOR);
}
printLog(loggerName, logLevel, logMsg, throwable);
}
// Return the return message we have prepared
return retMsg;
}
private static Logger mapLogger(String loggerName)
{
if (loggerName == null)
loggerName = Logger.ROOT_LOGGER_NAME;
Logger logger = loggerMap.get(loggerName);
if (logger == null)
{
logger = LoggerFactory.getLogger(loggerName);
loggerMap.put(loggerName, logger);
}
return logger;
}
/**
* Retrieves the caller class name from call stack using an exception.
*
* @return name of the calling class
*/
@SuppressWarnings("restriction")
private static String getCallerClassName()
{
return sun.reflect.Reflection.getCallerClass(2).getName();
// return new Exception().getStackTrace()[2].getClassName();
}
}