package org.olat.core.util.threadlog;
import org.apache.log4j.Appender;
import org.apache.log4j.Category;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
import org.apache.log4j.spi.LoggingEvent;
/**
* Specialization of the generic log4j Logger which allows
* thread based log level control.
* <p>
* That is, the log levels can be controlled in two ways:
* <ul>
* <li>the standard setLevel() or via the config file, log4j way</li>
* <li>via {@link ThreadLocalLogLevelManager#forceThreadLocalLogLevel(Priority)} overwriting
* the above level if it is lower</li>
* </ul>
* <P>
* Performance note: Logging can be critical to performance
* and any modification of log4j is therefore prone to cause
* performance 'leaks'. This method has been written with
* this in mind but does indeed add a (although only very minimal)
* overhead to the normal isDebugEnabled()/debug() performance.
* The impact should be minimal though. Still, enabling this in
* production should be optional.
* <p>
* Initial Date: 05.08.2010 <br>
* @author Stefan
*/
public class ThreadLocalAwareLogger extends Logger {
/** The ThreadLocal used for controlling thread based log levels - passed to this Logger in the constructor **/
private final ThreadLocal<LogConfig> threadLocalLogLevel_;
/**
(from log4j Category)
The fully qualified name of the Category class. See also the
getFQCN method. */
private static final String FQCN = Category.class.getName();
private final LogMessageModifier logMessageModifier_;
/**
* Creates a new ThreadLocalAwareLogger of the given name,
* using the given ThreadLocal - with which log levels can
* be overwritten on a ThreadLocal basis - and uses
* an optional (i.e. can be null) LogMessageModifier
* @param name the name of this Logger
* @param threadLocalLogLevel the ThreadLocal to use to fetch
* overwritten log levels
* @param logMessageModifier optional, allows to modify
* log messages when they are affected by a ThreadLocal
* log level overwrite - NOTE: This will convert the message
* passed to the
*/
ThreadLocalAwareLogger(String name, ThreadLocal<LogConfig> threadLocalLogLevel, LogMessageModifier logMessageModifier) {
super(name);
if (threadLocalLogLevel==null) {
throw new IllegalArgumentException("threadLocalLogLevel must not be null");
}
threadLocalLogLevel_ = threadLocalLogLevel;
logMessageModifier_ = logMessageModifier;
}
/**
* Internal helper method to check whether this logger
* would log the given level anyway - anyway meaning without
* the feature 'threadLocalLogLevel_'.
* @param level the level which should be checked if that would anyway be logged
* @return true if the given level would anyway be logged by the superclass
*/
private final boolean isLoggingAnyway(Priority priority) {
if(repository.isDisabled(priority.toInt()))
return false;
return (priority.isGreaterOrEqual(this.getEffectiveLevel()));
}
/**
* Internal helper method to check whether the
* 'threadLocalLogLevel_' would force the given level
* to be logged - i.e. if the 'threadLocalLogLevel_'
* is smaller or equal to the given level.
* <p>
* Note that this is the core of this ThreadLocalAwareLogger feature.
* @param level the level which should be checked if that is now forced
* to be logged by the 'threadLocalLogLevel_'
* @return true if the given level is now forced to be logged
* by the 'threadLocalLogLevel_'
*/
private final boolean isForcedToLog(Priority priority) {
final LogConfig logConfig = threadLocalLogLevel_.get();
return (logConfig!=null) && (logConfig.getPriority()!=null) && priority.isGreaterOrEqual(logConfig.getPriority());
}
/**
* Return the Appender if set in this ThreadLocal - used when isForcedToLog returns true
* @return the Appender if set in this ThreadLocal
*/
private final Appender getForcedAppender() {
final LogConfig logConfig = threadLocalLogLevel_.get();
return (logConfig==null) ? null : logConfig.getAppender();
}
/**
* Internal helper method to convert an original log message
* using the optional logMessageModifier.
* <p>
* The logMessageModifier is a hook for users of the
* ThreadLocalAwareLogger to e.g. prepend every log message
* which was forced to log (i.e. isForcedToLog returns true)
* with a prefix.
* @param originalMessage the original message which should be modified if
* there is a logMessageModifier
* @return the originalMessage if no logMessageModifier is set
* or the result from the logMessageModifier.modifyLogMessage call
*/
private final Object modifyLogMessage(Object originalMessage) {
if (logMessageModifier_!=null) {
return logMessageModifier_.modifyLogMessage(originalMessage);
} else {
return originalMessage;
}
}
/**
* Logger worker method - does the actual checks whether the given message
* should be logged at the given priority - taking into account this logger's
* loglevel plus the threadLocalLogLevel.
* @param priority the priority at which the given message should be logged
* @param message the message which should be logged (or not, depending on levels)
*/
private final void threadLocalAwareLog(String fQCN, Priority priority, Object message, Throwable t) {
if (isForcedToLog(priority)) {
final Appender forcedAppender = getForcedAppender();
// force the logging and modify the log message (the latter might return the original message)
if (forcedAppender!=null) {
// only call the forced appender
forcedAppender.doAppend(new LoggingEvent(fQCN, this, priority, modifyLogMessage(message), t));
} else {
// go via the normal appenders
forcedLog(fQCN, priority, modifyLogMessage(message), t);
}
} else {
// else not forced to log - use default behaviour
super.log(fQCN, priority, message, t);
}
}
@Override
public void trace(Object message) {
threadLocalAwareLog(FQCN, Level.TRACE, message, null);
}
@Override
public void trace(Object message, Throwable t) {
threadLocalAwareLog(FQCN, Level.TRACE, message, t);
}
@Override
public void debug(Object message) {
threadLocalAwareLog(FQCN, Level.DEBUG, message, null);
}
@Override
public void debug(Object message, Throwable t) {
threadLocalAwareLog(FQCN, Level.DEBUG, message, t);
}
@Override
public void info(Object message) {
threadLocalAwareLog(FQCN, Level.INFO, message, null);
}
@Override
public void info(Object message, Throwable t) {
threadLocalAwareLog(FQCN, Level.INFO, message, t);
}
@Override
public void warn(Object message) {
threadLocalAwareLog(FQCN, Level.WARN, message, null);
}
@Override
public void warn(Object message, Throwable t) {
threadLocalAwareLog(FQCN, Level.WARN, message, t);
}
@Override
public void error(Object message) {
threadLocalAwareLog(FQCN, Level.WARN, message, null);
}
@Override
public void error(Object message, Throwable t) {
threadLocalAwareLog(FQCN, Level.WARN, message, t);
}
@Override
public void fatal(Object message) {
threadLocalAwareLog(FQCN, Level.FATAL, message, null);
}
@Override
public void fatal(Object message, Throwable t) {
threadLocalAwareLog(FQCN, Level.FATAL, message, t);
}
@Override
public void log(Priority priority, Object message) {
threadLocalAwareLog(FQCN, priority, message, null);
}
@Override
public void log(Priority priority, Object message, Throwable t) {
threadLocalAwareLog(FQCN, priority, message, t);
}
@Override
public void log(String callerFQCN, Priority priority, Object message, Throwable t) {
threadLocalAwareLog(callerFQCN, priority, message, t);
}
@Override
public boolean isTraceEnabled() {
return isLoggingAnyway(Level.TRACE) || isForcedToLog(Level.TRACE);
}
@Override
public boolean isDebugEnabled() {
return isLoggingAnyway(Level.DEBUG) || isForcedToLog(Level.DEBUG);
}
@Override
public boolean isEnabledFor(Priority priority) {
return isLoggingAnyway(priority) || isForcedToLog(priority);
}
@Override
public boolean isInfoEnabled() {
return isLoggingAnyway(Level.INFO) || isForcedToLog(Level.INFO);
}
@Override
public void l7dlog(Priority priority, String key, Object[] params, Throwable t) {
if (isForcedToLog(priority)) {
// from super.l7dlog:
String pattern = getResourceBundleString(key);
String msg;
if(pattern == null)
msg = key;
else
msg = java.text.MessageFormat.format(pattern, params);
final Appender forcedAppender = getForcedAppender();
// force the logging and modify the log message (the latter might return the original message)
if (forcedAppender!=null) {
// only call the forced appender
forcedAppender.doAppend(new LoggingEvent(FQCN, this, priority, modifyLogMessage(msg), t));
} else {
// go via the normal appenders
forcedLog(FQCN, priority, modifyLogMessage(msg), t);
}
} else {
super.l7dlog(priority, key, params, t);
}
}
@Override
public void l7dlog(Priority priority, String key, Throwable t) {
if (isForcedToLog(priority)) {
// from super.l7dlog:
String msg = getResourceBundleString(key);
// if message corresponding to 'key' could not be found in the
// resource bundle, then default to 'key'.
if(msg == null) {
msg = key;
}
final Appender forcedAppender = getForcedAppender();
// force the logging and modify the log message (the latter might return the original message)
if (forcedAppender!=null) {
// only call the forced appender
forcedAppender.doAppend(new LoggingEvent(FQCN, this, priority, modifyLogMessage(msg), t));
} else {
// go via the normal appenders
forcedLog(FQCN, priority, modifyLogMessage(msg), t);
}
} else {
super.l7dlog(priority, key, t);
}
}
}