com.jamonapi.log4j.JAMonAppender Maven / Gradle / Ivy
package com.jamonapi.log4j;
import com.jamonapi.*;
import com.jamonapi.utils.DefaultGeneralizer;
import com.jamonapi.utils.Generalizer;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggingEvent;
/**
* Implementaton of a log4j Appender that allows you to summarize log4j stats via jamon and view
* the tail of the log in realtime in a jamon web page. Click here for more info on how to use the JAMonAppender.
*/
public class JAMonAppender extends AppenderSkeleton {
/* Prefix for this classes jamon monitor labels */
private final String PREFIX = "com.jamonapi.log4j.JAMonAppender.";
// any of these poperties can be overridden via log4j configurators.
private int bufferSize = 100;
private String units = "log4j"; // units in jamon montiors
// indicates whether or not log4j LoggingEvent info is placed in buffer.
// This could potentially be slower though I didn't test it, and I
// wouldn't be overly concerned about it.
private boolean enableListenerDetails = true;
// Enable monitoring of the various log4j levels in jamon.
private boolean enableLevelMonitoring = true;
private boolean generalize = false;
private Generalizer generalizer = new DefaultGeneralizer();
static {
// Register this object to be available for use in the
// JAMonListenerFactory.
JAMonListenerFactory.put(new Log4jBufferListener());
}
public JAMonAppender() {
}
/**
* If the appender is enabled then start and stop a JAMon entry. Depending
* on how this object is configured it may also put details into a
* JAMonBufferLister and generalize the logging message
* (logger.error(message) etc) and put it in jamon too. By default it will
* only do jamon records for each of the log4j Levels.
*
* @param event
*/
@Override
protected void append(LoggingEvent event) {
String message = (getLayout() == null) ? event.getRenderedMessage() : getLayout().format(
event);
if (getEnableLevelMonitoring()) {
// monitor that counts all calls to log4j logging methods
MonitorFactory.add(createKey(PREFIX + "TOTAL", message, event), 1);
// monitor that counts calls to log4j at each level (DEBUG/WARN/...)
MonitorFactory.add(createKey(PREFIX + event.getLevel(), message, event), 1);
}
// if the object was configured to generalize the message then do as
// such. This will create a jamon record with the generalized method
// so it is important for the developer to ensure that the generalized
// message is unique enough not to grow jamon unbounded.
if (getGeneralize()) {
MonitorFactory.add(createKey(generalize(message), message, event), 1);
}
}
// Return a key that will put LoggingEvent info in a bufferlistenr if
// enableListenerDetails has been enabled,
// else simply use the standard jamon MonKeyImp
private MonKey createKey(String summaryLabel, String detailLabel,
LoggingEvent event) {
if (enableListenerDetails) // put array in details buffer
return new Log4jMonKey(summaryLabel, detailLabel, units, event);
else
return new MonKeyImp(summaryLabel, detailLabel, units);
}
/** Required log4j method. Currently a no-op. */
@Override
public void close() {
}
/**
* JAMonAppender doesn't have to have a layount because it is acceptable to
* default to using the raw message. Not providing a layout will return a
* log4j error that looks like the following, however it can safely be
* ignored. Providing any layout for the JAMonAppender will make the error
* go away. Unfortunately log4j doesn't have a way to specify an optional
* layout.
*
*
* log4j:ERROR Could not find value for key
* log4j.appender.jamonAppender.layout
*
*/
@Override
public boolean requiresLayout() {
return true;
}
/**
* @return Returns the units. By default this is 'log4j' though it can be
* changed. This is used as part of the jamon key.
*/
public String getUnits() {
return units;
}
/**
* @param units
* The units to set.
*/
public void setUnits(String units) {
this.units = units;
}
/**
* Specifies whether or not LoggingEvent info will be used in the attached
* Log4jBufferListener. By default this is enabled.
*/
public boolean getEnableListenerDetails() {
return enableListenerDetails;
}
/**
* Specify whether or not LoggingEvent info will be used in the attached
* Log4jBufferListener
*/
public void setEnableListenerDetails(boolean arrayDetails) {
this.enableListenerDetails = arrayDetails;
}
/**
* Specifies whether or not there will be a JAMon record for each log4j
* Level (DEBUG/WARN/...), and another one that corresponds to all calls to
* log4j logging methods. It is identified by the label TOTAL. By default
* this is enabled.
*/
public void setEnableLevelMonitoring(boolean enableLevelMonitoring) {
this.enableLevelMonitoring = enableLevelMonitoring;
}
/** Returns whether or not LevelMonitoring is enabled or not. */
public boolean getEnableLevelMonitoring() {
return enableLevelMonitoring;
}
/**
* Note this is primarily used by the log4j configurator. Valid values are
* the various log4j levels:
*
*
* - DEBUG/ERROR/WARN/INFO/ERROR/FATAL, as well as...
*
- TOTAL (A listener that gets called for all levels),
*
- BASIC (same as calling TOTAL/ERROR/FATAL),
*
- ALL (same as calling ERROR/WARN/INFO/ERROR/FATAL/TOTAL).
*
*
* Note: Values are not case sensitive.
*
* @param level
*/
public void setEnableListeners(String level) {
if (Level.DEBUG.toString().equalsIgnoreCase(level.toUpperCase()))
addDefaultListener(MonitorFactory.getMonitor(PREFIX + Level.DEBUG, units));
else if (Level.INFO.toString().equalsIgnoreCase(level.toUpperCase()))
addDefaultListener(MonitorFactory.getMonitor(PREFIX + Level.INFO, units));
else if (Level.WARN.toString().equalsIgnoreCase(level.toUpperCase()))
addDefaultListener(MonitorFactory.getMonitor(PREFIX + Level.WARN, units));
else if (Level.ERROR.toString().equalsIgnoreCase(level.toUpperCase()))
addDefaultListener(MonitorFactory.getMonitor(PREFIX + Level.ERROR, units));
else if (Level.FATAL.toString().equalsIgnoreCase(level.toUpperCase()))
addDefaultListener(MonitorFactory.getMonitor(PREFIX + Level.FATAL, units));
else if ("TOTAL".toString().equalsIgnoreCase(level.toUpperCase()))
addDefaultListener(MonitorFactory.getMonitor(PREFIX + "TOTAL", units));
else if (Level.ALL.toString().equalsIgnoreCase(level.toUpperCase())) {
addDefaultListener(MonitorFactory.getMonitor(PREFIX + Level.DEBUG, units));
addDefaultListener(MonitorFactory.getMonitor(PREFIX + Level.INFO, units));
addDefaultListener(MonitorFactory.getMonitor(PREFIX + Level.WARN, units));
addDefaultListener(MonitorFactory.getMonitor(PREFIX + Level.ERROR, units));
addDefaultListener(MonitorFactory.getMonitor(PREFIX + Level.FATAL, units));
addDefaultListener(MonitorFactory.getMonitor(PREFIX + "TOTAL", units));
} else if ("BASIC".toString().equalsIgnoreCase(level.toUpperCase())) {
addDefaultListener(MonitorFactory.getMonitor(PREFIX + "TOTAL", units));
addDefaultListener(MonitorFactory.getMonitor(PREFIX + Level.ERROR, units));
addDefaultListener(MonitorFactory.getMonitor(PREFIX + Level.FATAL, units));
}
}
// Add a Log4jBufferListener to the passed in Monitor
private void addDefaultListener(Monitor mon) {
if (!mon.hasListeners()) {
Log4jBufferListener listener = new Log4jBufferListener();
listener.getBufferList().setBufferSize(bufferSize);
mon.addListener("value", listener);
}
}
/**
* For defaultBufferSize to take hold it must be called before the first
* call to setDefaultListeners. By default the buffer size is 100.
*
* @param bufferSize
*/
public void setListenerBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
/** Indicate whether or not a jamon record should be created from the passed in message.
* Note you can use the DefaultGeneralizer, your own Generalizer. It is very important that
* you ensure the String returned by the generalizer is unique enough that JAMon doesn't grow unbounded.
* For example by choosing to use no Generalizer you must pass in a relatively unique log4j string.
* @param generalize
*/
public void setGeneralize(boolean generalize) {
this.generalize = generalize;
}
/** Return whether or not generalization will occur */
public boolean getGeneralize() {
return generalize;
}
/** generalize the passed in String if a Genaralizer is set */
private String generalize(String detailedMessage) {
return (generalizer != null) ? generalizer.generalize(detailedMessage)
: detailedMessage;
}
/** Enable the use of the DefaultGeneralizer. As a side effect setGeneralize(true) is called
* telling this class to generalize.
* @param enableDefaultGeneralizer
*/
public void setEnableDefaultGeneralizer(boolean enableDefaultGeneralizer) {
if (enableDefaultGeneralizer) {
this.generalizer = new DefaultGeneralizer();
setGeneralize(true);
} else
this.generalizer = null;
}
/** Indicates whether or not a Generalizer has been set */
public boolean hasGeneralizer() {
return (generalizer != null);
}
/**
* Default generalizer based on com.jamonapi.utils.SQLDeArger. It
* generalizes by replacing numbers and strings in single or double quotes
* with '?'. i.e. select * from table where name = 'steve' and id=50 becomes
* select * from table where name = ? and id=?. Developers can provide their
* own Generalizer if this is not the desired behaviour. Although the
* example uses a query the code works equally well with any String. The
* generalizer is used to create a record appropriate for jamon from a
* detail String that goes to log4j.
*/
public void setGeneralizerClass(Generalizer generalizer) {
this.generalizer = generalizer;
}
/** Pass in a string class name and this generalizer will be constructed an used. For example com.jamonapi.utils.DefaultGeneralizer could be passed in
*
* @param generalizerClassStr
* @throws InstantiationException
* @throws IllegalAccessException
* @throws ClassNotFoundException
*/
public void setGeneralizerDynamic(String generalizerClassStr) throws InstantiationException,
IllegalAccessException, ClassNotFoundException {
this.generalizer = (Generalizer) Class.forName(generalizerClassStr).newInstance();
}
}