blob: 006e56a3544dff33464a0595548647358910dd88 [file] [log] [blame]
/**
* 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.oozie.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The <code>XLog</code> class extends the functionality of the Apache common-logging <code>Log</code> interface. <p>
* It provides common prefix support, message templating with variable parameters and selective tee logging to multiple
* logs. <p> It provides also the LogFactory functionality.
*/
public class XLog implements Log {
public static final String INSTRUMENTATION_LOG_NAME = "oozieinstrumentation";
/**
* <code>LogInfo</code> stores contextual information to create log prefixes. <p> <code>LogInfo</code> uses a
* <code>ThreadLocal</code> to propagate the context. <p> <code>LogInfo</code> context parameters are configurable
* singletons.
*/
public static class Info {
private static String template = "";
private String prefix = "";
private static List<String> parameterNames = new ArrayList<String>();
private static ThreadLocal<Info> tlLogInfo = new ThreadLocal<Info>() {
@Override
protected Info initialValue() {
return new Info();
}
};
/**
* Define a <code>LogInfo</code> context parameter. <p> The parameter name and its contextual value will be
* used to create all prefixes.
*
* @param name name of the context parameter.
*/
public static void defineParameter(String name) {
ParamChecker.notEmpty(name, "name");
int count = parameterNames.size();
if (count > 0) {
template += " ";
}
template += name + "[{" + count + "}]";
parameterNames.add(name);
}
/**
* Remove all defined context parameters.
*/
public static void reset() {
template = "";
parameterNames.clear();
}
/**
* Return the <code>LogInfo</code> instance in context.
*
* @return The thread local instance of LogInfo
*/
public static Info get() {
return tlLogInfo.get();
}
/**
* Remove the <code>LogInfo</code> instance in context.
*/
public static void remove() {
tlLogInfo.remove();
}
private Map<String, String> parameters = new HashMap<String, String>();
/**
* Constructs an empty LogInfo.
*/
public Info() {
}
/**
* Construct a new LogInfo object from an existing one.
*
* @param logInfo LogInfo object to copy parameters from.
*/
public Info(Info logInfo) {
setParameters(logInfo);
}
/**
* Clear all parameters set in this logInfo instance.
*/
public void clear() {
parameters.clear();
resetPrefix();
}
/**
* Set a parameter value in the <code>LogInfo</code> context.
*
* @param name parameter name.
* @param value parameter value.
*/
public void setParameter(String name, String value) {
if (!parameterNames.contains(name)) {
throw new IllegalArgumentException(format("Parameter[{0}] not defined", name));
}
parameters.put(name, value);
}
/**
* Returns the specified parameter.
*
* @param name parameter name.
* @return the parameter value.
*/
public String getParameter(String name) {
return parameters.get(name);
}
/**
* Clear a parameter value from the <code>LogInfo</code> context.
*
* @param name parameter name.
*/
public void clearParameter(String name) {
if (!parameterNames.contains(name)) {
throw new IllegalArgumentException(format("Parameter[{0}] not defined", name));
}
parameters.remove(name);
}
/**
* Set all the parameter values from the given <code>LogInfo</code>.
*
* @param logInfo <code>LogInfo</code> to copy all parameter values from.
*/
public void setParameters(Info logInfo) {
parameters.clear();
parameters.putAll(logInfo.parameters);
}
/**
* Create the <code>LogInfo</code> prefix using the current parameter values.
*
* @return the <code>LogInfo</code> prefix.
*/
public String createPrefix() {
String[] params = new String[parameterNames.size()];
for (int i = 0; i < params.length; i++) {
params[i] = parameters.get(parameterNames.get(i));
if (params[i] == null) {
params[i] = "-";
}
}
return MessageFormat.format(template, (Object[]) params);
}
public String resetPrefix() {
return prefix = createPrefix();
}
public String getPrefix() {
return prefix;
}
}
/**
* Return the named logger.
*
* @param name logger name.
* @return the named logger.
*/
public static XLog getLog(String name) {
return new XLog(LogFactory.getLog(name));
}
/**
* Return the named logger.
*
* @param clazz from which the logger name will be derived.
* @return the named logger.
*/
public static XLog getLog(Class clazz) {
return new XLog(LogFactory.getLog(clazz));
}
/**
* Reset the logger prefix
*
* @param log the named logger
* @return the named logger with reset prefix
*/
public static XLog resetPrefix(XLog log) {
log.setMsgPrefix(Info.get().createPrefix());
return log;
}
/**
* Mask for logging to the standard log.
*/
public static final int STD = 1;
/**
* Mask for tee logging to the OPS log.
*/
public static final int OPS = 4;
private static final int ALL = STD | OPS;
private static final int[] LOGGER_MASKS = {STD, OPS};
//package private for testing purposes.
Log[] loggers;
private String prefix = null;
/**
* Create a <code>XLog</code> with no prefix.
*
* @param log Log instance to use for logging.
*/
public XLog(Log log) {
loggers = new Log[2];
loggers[0] = log;
loggers[1] = LogFactory.getLog("oozieops");
}
/**
* Return the common prefix.
*
* @return the common prefix.
*/
public String getMsgPrefix() {
return prefix;
}
/**
* Set the common prefix.
*
* @param prefix the common prefix to set.
*/
public void setMsgPrefix(String prefix) {
this.prefix = prefix;
}
//All the methods from the commonsLogging Log interface will log to the default logger only.
/**
* Log a debug message to the common <code>Log</code>.
*
* @param o message.
*/
@Override
public void debug(Object o) {
log(Level.DEBUG, STD, "{0}", o);
}
/**
* Log a debug message and <code>Exception</code> to the common <code>Log</code>.
*
* @param o message.
* @param throwable exception.
*/
@Override
public void debug(Object o, Throwable throwable) {
log(Level.DEBUG, STD, "{0}", o, throwable);
}
/**
* Log a error message to the common <code>Log</code>.
*
* @param o message.
*/
@Override
public void error(Object o) {
log(Level.ERROR, STD, "{0}", o);
}
/**
* Log a error message and <code>Exception</code> to the common <code>Log</code>.
*
* @param o message.
* @param throwable exception.
*/
@Override
public void error(Object o, Throwable throwable) {
log(Level.ERROR, STD, "{0}", o, throwable);
}
/**
* Log a fatal message to the common <code>Log</code>.
*
* @param o message.
*/
@Override
public void fatal(Object o) {
log(Level.FATAL, STD, "{0}", o);
}
/**
* Log a fatal message and <code>Exception</code> to the common <code>Log</code>.
*
* @param o message.
* @param throwable exception.
*/
@Override
public void fatal(Object o, Throwable throwable) {
log(Level.FATAL, STD, "{0}", o, throwable);
}
/**
* Log a info message to the common <code>Log</code>.
*
* @param o message.
*/
@Override
public void info(Object o) {
log(Level.INFO, STD, "{0}", o);
}
/**
* Log a info message and <code>Exception</code> to the common <code>Log</code>.
*
* @param o message.
* @param throwable exception.
*/
@Override
public void info(Object o, Throwable throwable) {
log(Level.INFO, STD, "{0}", o, throwable);
}
/**
* Log a trace message to the common <code>Log</code>.
*
* @param o message.
*/
@Override
public void trace(Object o) {
log(Level.TRACE, STD, "{0}", o);
}
/**
* Log a trace message and <code>Exception</code> to the common <code>Log</code>.
*
* @param o message.
* @param throwable exception.
*/
@Override
public void trace(Object o, Throwable throwable) {
log(Level.TRACE, STD, "{0}", o, throwable);
}
/**
* Log a warn message to the common <code>Log</code>.
*
* @param o message.
*/
@Override
public void warn(Object o) {
log(Level.WARN, STD, "{0}", o);
}
/**
* Log a warn message and <code>Exception</code> to the common <code>Log</code>.
*
* @param o message.
* @param throwable exception.
*/
@Override
public void warn(Object o, Throwable throwable) {
log(Level.WARN, STD, "{0}", o, throwable);
}
/**
* Return if debug logging is enabled.
*
* @return <code>true</code> if debug logging is enable, <code>false</code> if not.
*/
@Override
public boolean isDebugEnabled() {
return isEnabled(Level.DEBUG, ALL);
}
/**
* Return if error logging is enabled.
*
* @return <code>true</code> if error logging is enable, <code>false</code> if not.
*/
@Override
public boolean isErrorEnabled() {
return isEnabled(Level.ERROR, ALL);
}
/**
* Return if fatal logging is enabled.
*
* @return <code>true</code> if fatal logging is enable, <code>false</code> if not.
*/
@Override
public boolean isFatalEnabled() {
return isEnabled(Level.FATAL, ALL);
}
/**
* Return if info logging is enabled.
*
* @return <code>true</code> if info logging is enable, <code>false</code> if not.
*/
@Override
public boolean isInfoEnabled() {
return isEnabled(Level.INFO, ALL);
}
/**
* Return if trace logging is enabled.
*
* @return <code>true</code> if trace logging is enable, <code>false</code> if not.
*/
@Override
public boolean isTraceEnabled() {
return isEnabled(Level.TRACE, ALL);
}
/**
* Return if warn logging is enabled.
*
* @return <code>true</code> if warn logging is enable, <code>false</code> if not.
*/
@Override
public boolean isWarnEnabled() {
return isEnabled(Level.WARN, ALL);
}
public enum Level {
FATAL, ERROR, INFO, WARN, DEBUG, TRACE
}
private boolean isEnabled(Level level, int loggerMask) {
for (int i = 0; i < loggers.length; i++) {
if ((LOGGER_MASKS[i] & loggerMask) != 0) {
boolean enabled = false;
switch (level) {
case FATAL:
enabled = loggers[i].isFatalEnabled();
break;
case ERROR:
enabled = loggers[i].isErrorEnabled();
break;
case INFO:
enabled = loggers[i].isInfoEnabled();
break;
case WARN:
enabled = loggers[i].isWarnEnabled();
break;
case DEBUG:
enabled = loggers[i].isDebugEnabled();
break;
case TRACE:
enabled = loggers[i].isTraceEnabled();
break;
}
if (enabled) {
return true;
}
}
}
return false;
}
private void log(Level level, int loggerMask, String msgTemplate, Object... params) {
loggerMask |= STD;
if (isEnabled(level, loggerMask)) {
String prefix = getMsgPrefix() != null ? getMsgPrefix() : Info.get().getPrefix();
prefix = (prefix != null && prefix.length() > 0) ? prefix + " " : "";
String msg = prefix + format(msgTemplate, params);
Throwable throwable = getCause(params);
for (int i = 0; i < LOGGER_MASKS.length; i++) {
if (isEnabled(level, loggerMask & LOGGER_MASKS[i])) {
Log log = loggers[i];
switch (level) {
case FATAL:
log.fatal(msg, throwable);
break;
case ERROR:
log.error(msg, throwable);
break;
case INFO:
log.info(msg, throwable);
break;
case WARN:
log.warn(msg, throwable);
break;
case DEBUG:
log.debug(msg, throwable);
break;
case TRACE:
log.trace(msg, throwable);
break;
}
}
}
}
}
/**
* Log a fatal message <code>Exception</code> to the common <code>Log</code>.
*
* @param msgTemplate message template.
* @param params parameters for the message template. If the last parameter is an exception it is logged as such.
*/
public void fatal(String msgTemplate, Object... params) {
log(Level.FATAL, STD, msgTemplate, params);
}
/**
* Log a error message <code>Exception</code> to the common <code>Log</code>.
*
* @param msgTemplate message template.
* @param params parameters for the message template. If the last parameter is an exception it is logged as such.
*/
public void error(String msgTemplate, Object... params) {
log(Level.ERROR, STD, msgTemplate, params);
}
/**
* Log a info message <code>Exception</code> to the common <code>Log</code>.
*
* @param msgTemplate message template.
* @param params parameters for the message template. If the last parameter is an exception it is logged as such.
*/
public void info(String msgTemplate, Object... params) {
log(Level.INFO, STD, msgTemplate, params);
}
/**
* Log a warn message <code>Exception</code> to the common <code>Log</code>.
*
* @param msgTemplate message template.
* @param params parameters for the message template. If the last parameter is an exception it is logged as such.
*/
public void warn(String msgTemplate, Object... params) {
log(Level.WARN, STD, msgTemplate, params);
}
/**
* Log a debug message <code>Exception</code> to the common <code>Log</code>.
*
* @param msgTemplate message template.
* @param params parameters for the message template. If the last parameter is an exception it is logged as such.
*/
public void debug(String msgTemplate, Object... params) {
log(Level.DEBUG, STD, msgTemplate, params);
}
/**
* Log a trace message <code>Exception</code> to the common <code>Log</code>.
*
* @param msgTemplate message template.
* @param params parameters for the message template. If the last parameter is an exception it is logged as such.
*/
public void trace(String msgTemplate, Object... params) {
log(Level.TRACE, STD, msgTemplate, params);
}
/**
* Tee Log a fatal message <code>Exception</code> to the common log and specified <code>Log</code>s.
*
* @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
* @param msgTemplate message template.
* @param params parameters for the message template.
*/
public void fatal(int loggerMask, String msgTemplate, Object... params) {
log(Level.FATAL, loggerMask, msgTemplate, params);
}
/**
* Tee Log a error message <code>Exception</code> to the common log and specified <code>Log</code>s.
*
* @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
* @param msgTemplate message template.
* @param params parameters for the message template.
*/
public void error(int loggerMask, String msgTemplate, Object... params) {
log(Level.ERROR, loggerMask, msgTemplate, params);
}
/**
* Tee Log a info message <code>Exception</code> to the common log and specified <code>Log</code>s.
*
* @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
* @param msgTemplate message template.
* @param params parameters for the message template.
*/
public void info(int loggerMask, String msgTemplate, Object... params) {
log(Level.INFO, loggerMask, msgTemplate, params);
}
/**
* Tee Log a warn message <code>Exception</code> to the common log and specified <code>Log</code>s.
*
* @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
* @param msgTemplate message template.
* @param params parameters for the message template.
*/
public void warn(int loggerMask, String msgTemplate, Object... params) {
log(Level.WARN, loggerMask, msgTemplate, params);
}
/**
* Tee Log a debug message <code>Exception</code> to the common log and specified <code>Log</code>s.
*
* @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
* @param msgTemplate message template.
* @param params parameters for the message template.
*/
public void debug(int loggerMask, String msgTemplate, Object... params) {
log(Level.DEBUG, loggerMask, msgTemplate, params);
}
/**
* Tee Log a trace message <code>Exception</code> to the common log and specified <code>Log</code>s.
*
* @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
* @param msgTemplate message template.
* @param params parameters for the message template.
*/
public void trace(int loggerMask, String msgTemplate, Object... params) {
log(Level.TRACE, loggerMask, msgTemplate, params);
}
/**
* Utility method that does uses the <code>StringFormat</code> to format the message template using the provided
* parameters. <p> In addition to the <code>StringFormat</code> syntax for message templates, it supports
* <code>{E}</code> for ENTER. <p> The last parameter is ignored for the formatting if it is an Exception.
*
* @param msgTemplate message template.
* @param params paramaters to use in the template. If the last parameter is an Exception, it is ignored.
* @return formatted message.
*/
public static String format(String msgTemplate, Object... params) {
ParamChecker.notEmpty(msgTemplate, "msgTemplate");
msgTemplate = msgTemplate.replace("{E}", System.getProperty("line.separator"));
msgTemplate = replaceEmptyPositions(msgTemplate);
if (params != null && params.length > 0) {
msgTemplate = MessageFormat.format(msgTemplate, params);
}
return msgTemplate;
}
private static String replaceEmptyPositions(String msgTemplate) {
int pos = 0;
while (msgTemplate.contains("{}")) {
msgTemplate = msgTemplate.replace("{}", String.format("{%d}", pos));
pos++;
}
return msgTemplate;
}
/**
* Utility method that extracts the <code>Throwable</code>, if present, from the parameters.
*
* @param params parameters.
* @return a <code>Throwable</code> instance if it is the last parameter, <code>null</code> otherwise.
*/
public static Throwable getCause(Object... params) {
Throwable throwable = null;
if (params != null && params.length > 0 && params[params.length - 1] instanceof Throwable) {
throwable = (Throwable) params[params.length - 1];
}
return throwable;
}
}