Index: pom.xml
===================================================================
--- pom.xml (revision 1462911)
+++ pom.xml (working copy)
@@ -115,8 +115,8 @@
2.9
2.12.4
target/osgi/MANIFEST.MF
- 1.5
- 1.5
+ 1.6
+ 1.6
Site Documentation
@@ -556,6 +556,7 @@
flume-ng
web
samples
+ log4j-async
Index: core/src/main/java/org/apache/logging/log4j/core/LogEvent.java
===================================================================
--- core/src/main/java/org/apache/logging/log4j/core/LogEvent.java (revision 1462911)
+++ core/src/main/java/org/apache/logging/log4j/core/LogEvent.java (working copy)
@@ -104,4 +104,47 @@
*/
String getFQCN();
+ /**
+ * Returns whether the source of the logging request is required downstream.
+ * Asynchronous Loggers and Appenders use this flag to determine whether
+ * to take a {@code StackTrace} snapshot or not before handing off this
+ * event to another thread.
+ * @return {@code true} if the source of the logging request is required
+ * downstream, {@code false} otherwise.
+ * @see #getSource()
+ */
+ // see also LOG4J2-153
+ boolean isIncludeLocation();
+
+ /**
+ * Sets whether the source of the logging request is required downstream.
+ * Asynchronous Loggers and Appenders use this flag to determine whether
+ * to take a {@code StackTrace} snapshot or not before handing off this
+ * event to another thread.
+ * @param locationRequired {@code true} if the source of the logging request
+ * is required downstream, {@code false} otherwise.
+ * @see #getSource()
+ */
+ void setIncludeLocation(boolean locationRequired);
+
+ /**
+ * Returns {@code true} if this event is the last one in a batch,
+ * {@code false} otherwise. Used by asynchronous Loggers and Appenders to
+ * signal to buffered downstream components when to flush to disk, as a
+ * more efficient alternative to the {@code immediateFlush=true}
+ * configuration.
+ * @return whether this event is the last one in a batch.
+ */
+ // see also LOG4J2-164
+ boolean isEndOfBatch();
+
+ /**
+ * Sets whether this event is the last one in a batch.
+ * Used by asynchronous Loggers and Appenders to signal to buffered
+ * downstream components when to flush to disk, as a more efficient
+ * alternative to the {@code immediateFlush=true} configuration.
+ * @param endOfBatch {@code true} if this event is the last one in a batch,
+ * {@code false} otherwise.
+ */
+ void setEndOfBatch(boolean endOfBatch);
}
Index: core/src/main/java/org/apache/logging/log4j/core/Logger.java
===================================================================
--- core/src/main/java/org/apache/logging/log4j/core/Logger.java (revision 1462911)
+++ core/src/main/java/org/apache/logging/log4j/core/Logger.java (working copy)
@@ -234,8 +234,9 @@
* The binding between a Logger and its configuration.
*/
protected class PrivateConfig {
- private final LoggerConfig loggerConfig;
- private final Configuration config;
+ // config fields are public to make them visible to Logger subclasses
+ public final LoggerConfig loggerConfig;
+ public final Configuration config;
private final Level level;
private final int intLevel;
private final Logger logger;
@@ -264,7 +265,8 @@
this.logger = pc.logger;
}
- protected void logEvent(final LogEvent event) {
+ // LOG4J2-151: changed visibility to public
+ public void logEvent(LogEvent event) {
config.getConfigurationMonitor().checkConfiguration();
loggerConfig.log(event);
}
Index: core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
===================================================================
--- core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java (revision 1462911)
+++ core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java (working copy)
@@ -326,8 +326,8 @@
}
}
-
- private Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
+ // LOG4J2-151: changed visibility from private to protected
+ protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
return new Logger(ctx, name, messageFactory);
}
Index: core/src/main/java/org/apache/logging/log4j/core/appender/AsynchAppender.java
===================================================================
--- core/src/main/java/org/apache/logging/log4j/core/appender/AsynchAppender.java (revision 1462911)
+++ core/src/main/java/org/apache/logging/log4j/core/appender/AsynchAppender.java (working copy)
@@ -56,18 +56,21 @@
private final Configuration config;
private final AppenderRef[] appenderRefs;
private final String errorRef;
+ private final boolean includeLocation;
private AppenderControl errorAppender;
private AsynchThread thread;
private AsynchAppender(final String name, final Filter filter, final AppenderRef[] appenderRefs,
final String errorRef, final int queueSize, final boolean blocking,
- final boolean handleExceptions, final Configuration config) {
+ final boolean handleExceptions, final Configuration config,
+ final boolean includeLocation) {
super(name, filter, null, handleExceptions);
this.queue = new ArrayBlockingQueue(queueSize);
this.blocking = blocking;
this.config = config;
this.appenderRefs = appenderRefs;
this.errorRef = errorRef;
+ this.includeLocation = includeLocation;
}
@Override
@@ -123,13 +126,14 @@
boolean appendSuccessful = false;
if (blocking){
try {
- queue.put(Log4jLogEvent.serialize((Log4jLogEvent) event)); // wait for free slots in the queue
+ // wait for free slots in the queue
+ queue.put(Log4jLogEvent.serialize((Log4jLogEvent) event, includeLocation));
appendSuccessful = true;
} catch (InterruptedException e) {
LOGGER.warn("Interrupted while waiting for a free slots in the LogEvent-queue at the AsynchAppender {}", getName());
}
} else {
- appendSuccessful = queue.offer(Log4jLogEvent.serialize((Log4jLogEvent) event));
+ appendSuccessful = queue.offer(Log4jLogEvent.serialize((Log4jLogEvent) event, includeLocation));
if (!appendSuccessful) {
error("Appender " + getName() + " is unable to write primary appenders. queue is full");
}
@@ -147,6 +151,7 @@
* @param blocking True if the Appender should wait when the queue is full. The default is true.
* @param size The size of the event queue. The default is 128.
* @param name The name of the Appender.
+ * @param includeLocation whether to include location information. The default is false.
* @param filter The Filter or null.
* @param config The Configuration.
* @param suppress "true" if exceptions should be hidden from the application, "false" otherwise.
@@ -156,14 +161,15 @@
*/
@PluginFactory
public static AsynchAppender createAppender(
- @PluginElement("appender-ref") final AppenderRef[] appenderRefs,
- @PluginAttr("error-ref") final String errorRef,
- @PluginAttr("blocking") final String blocking,
- @PluginAttr("bufferSize") final String size,
- @PluginAttr("name") final String name,
- @PluginElement("filter") final Filter filter,
- @PluginConfiguration final Configuration config,
- @PluginAttr("suppressExceptions") final String suppress) {
+ @PluginElement("appender-ref") final AppenderRef[] appenderRefs,
+ @PluginAttr("error-ref") final String errorRef,
+ @PluginAttr("blocking") final String blocking,
+ @PluginAttr("bufferSize") final String size,
+ @PluginAttr("name") final String name,
+ @PluginAttr("includeLocation") final String includeLocation,
+ @PluginElement("filter") final Filter filter,
+ @PluginConfiguration final Configuration config,
+ @PluginAttr("suppressExceptions") final String suppress) {
if (name == null) {
LOGGER.error("No name provided for AsynchAppender");
return null;
@@ -174,11 +180,13 @@
final boolean isBlocking = blocking == null ? true : Boolean.valueOf(blocking);
final int queueSize = size == null ? DEFAULT_QUEUE_SIZE : Integer.parseInt(size);
+ final boolean isIncludeLocation = includeLocation == null ? false :
+ Boolean.parseBoolean(includeLocation);
final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress);
- return new AsynchAppender(name, filter, appenderRefs, errorRef, queueSize, isBlocking, handleExceptions,
- config);
+ return new AsynchAppender(name, filter, appenderRefs, errorRef,
+ queueSize, isBlocking, handleExceptions, config, isIncludeLocation);
}
/**
@@ -210,8 +218,9 @@
continue;
}
final Log4jLogEvent event = Log4jLogEvent.deserialize(s);
+ event.setEndOfBatch(queue.isEmpty());
boolean success = false;
- for (final AppenderControl control : appenders) {
+ for (final AppenderControl> control : appenders) {
try {
control.callAppender(event);
success = true;
@@ -233,7 +242,8 @@
Serializable s = queue.take();
if (s instanceof Log4jLogEvent) {
final Log4jLogEvent event = Log4jLogEvent.deserialize(s);
- for (final AppenderControl control : appenders) {
+ event.setEndOfBatch(queue.isEmpty());
+ for (final AppenderControl> control : appenders) {
control.callAppender(event);
}
}
Index: core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
===================================================================
--- core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java (revision 1462911)
+++ core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java (working copy)
@@ -25,9 +25,11 @@
import org.apache.logging.log4j.core.LifeCycle;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.filter.AbstractFilterable;
+import org.apache.logging.log4j.core.helpers.Constants;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.impl.LogEventFactory;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttr;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
@@ -38,6 +40,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -61,13 +64,13 @@
private LogEventFactory logEventFactory;
private Level level;
private boolean additive = true;
+ private boolean includeLocation = true;
private LoggerConfig parent;
private final AtomicInteger counter = new AtomicInteger();
private boolean shutdown = false;
private final Map properties;
private final Configuration config;
-
/**
* Default constructor.
*/
@@ -81,11 +84,13 @@
/**
* Constructor that sets the name, level and additive values.
+ *
* @param name The Logger name.
* @param level The Level.
* @param additive true if the Logger is additive, false otherwise.
*/
- public LoggerConfig(final String name, final Level level, final boolean additive) {
+ public LoggerConfig(final String name, final Level level,
+ final boolean additive) {
this.logEventFactory = this;
this.name = name;
this.level = level;
@@ -94,14 +99,18 @@
this.config = null;
}
- protected LoggerConfig(final String name, final List appenders, final Filter filter, final Level level,
- final boolean additive, final Property[] properties, final Configuration config) {
+ protected LoggerConfig(final String name,
+ final List appenders, final Filter filter,
+ final Level level, final boolean additive,
+ final Property[] properties, final Configuration config,
+ final boolean includeLocation) {
super(filter);
this.logEventFactory = this;
this.name = name;
this.appenderRefs = appenders;
this.level = level;
this.additive = additive;
+ this.includeLocation = includeLocation;
this.config = config;
if (properties != null && properties.length > 0) {
this.properties = new HashMap(properties.length);
@@ -121,6 +130,7 @@
/**
* Returns the name of the LoggerConfig.
+ *
* @return the name of the LoggerConfig.
*/
public String getName() {
@@ -129,6 +139,7 @@
/**
* Sets the parent of this LoggerConfig.
+ *
* @param parent the parent LoggerConfig.
*/
public void setParent(final LoggerConfig parent) {
@@ -137,6 +148,7 @@
/**
* Returns the parent of this LoggerConfig.
+ *
* @return the LoggerConfig that is the parent of this one.
*/
public LoggerConfig getParent() {
@@ -145,16 +157,20 @@
/**
* Adds an Appender to the LoggerConfig.
+ *
* @param appender The Appender to add.
* @param level The Level to use.
* @param filter A Filter for the Appender reference.
*/
- public void addAppender(final Appender appender, final Level level, final Filter filter) {
- appenders.put(appender.getName(), new AppenderControl(appender, level, filter));
+ public void addAppender(final Appender appender, final Level level,
+ final Filter filter) {
+ appenders.put(appender.getName(), new AppenderControl(appender, level,
+ filter));
}
/**
* Removes the Appender with the specific name.
+ *
* @param name The name of the Appender.
*/
public void removeAppender(final String name) {
@@ -166,11 +182,14 @@
/**
* Returns all Appenders as a Map.
- * @return a Map with the Appender name as the key and the Appender as the value.
+ *
+ * @return a Map with the Appender name as the key and the Appender as the
+ * value.
*/
public Map> getAppenders() {
final Map> map = new HashMap>();
- for (final Map.Entry> entry : appenders.entrySet()) {
+ for (final Map.Entry> entry : appenders
+ .entrySet()) {
map.put(entry.getKey(), entry.getValue().getAppender());
}
return map;
@@ -202,6 +221,7 @@
/**
* Returns the Appender references.
+ *
* @return a List of all the Appender names attached to this LoggerConfig.
*/
public List getAppenderRefs() {
@@ -210,6 +230,7 @@
/**
* Sets the logging Level.
+ *
* @param level The logging Level.
*/
public void setLevel(final Level level) {
@@ -218,6 +239,7 @@
/**
* Returns the logging Level.
+ *
* @return the logging Level.
*/
public Level getLevel() {
@@ -226,6 +248,7 @@
/**
* Returns the LogEventFactory.
+ *
* @return the LogEventFactory.
*/
public LogEventFactory getLogEventFactory() {
@@ -233,7 +256,9 @@
}
/**
- * Sets the LogEventFactory. Usually the LogEventFactory will be this LoggerConfig.
+ * Sets the LogEventFactory. Usually the LogEventFactory will be this
+ * LoggerConfig.
+ *
* @param logEventFactory the LogEventFactory.
*/
public void setLogEventFactory(final LogEventFactory logEventFactory) {
@@ -242,6 +267,7 @@
/**
* Returns the valid of the additive flag.
+ *
* @return true if the LoggerConfig is additive, false otherwise.
*/
public boolean isAdditive() {
@@ -250,14 +276,47 @@
/**
* Sets the additive setting.
- * @param additive true if thee LoggerConfig should be additive, false otherwise.
+ *
+ * @param additive true if the LoggerConfig should be additive, false
+ * otherwise.
*/
public void setAdditive(final boolean additive) {
this.additive = additive;
}
/**
+ * Returns the value of logger configuration attribute {@code includeLocation},
+ * or, if no such attribute was configured, {@code true} if logging is
+ * synchronous or {@code false} if logging is asynchronous.
+ *
+ * @return whether location should be passed downstream
+ */
+ public boolean isIncludeLocation() {
+ return includeLocation;
+ }
+
+ /**
+ * Returns an unmodifiable map with the configuration properties, or
+ * {@code null} if this {@code LoggerConfig} does not have any configuration
+ * properties.
+ *
+ * For each {@code Property} key in the map, the value is {@code true} if
+ * the property value has a variable that needs to be substituted.
+ *
+ * @return an unmodifiable map with the configuration properties, or
+ * {@code null}
+ * @see Configuration#getSubst()
+ * @see StrSubstitutor
+ */
+ // LOG4J2-157
+ public Map getProperties() {
+ return properties == null ? null : Collections
+ .unmodifiableMap(properties);
+ }
+
+ /**
* Logs an event.
+ *
* @param loggerName The name of the Logger.
* @param marker A Marker or null if none is present.
* @param fqcn The fully qualified class name of the caller.
@@ -265,24 +324,29 @@
* @param data The Message.
* @param t A Throwable or null.
*/
- public void log(final String loggerName, final Marker marker, final String fqcn, final Level level,
- final Message data, final Throwable t) {
+ public void log(final String loggerName, final Marker marker,
+ final String fqcn, final Level level, final Message data,
+ final Throwable t) {
List props = null;
if (properties != null) {
props = new ArrayList(properties.size());
- for (final Map.Entry entry : properties.entrySet()) {
+ for (final Map.Entry entry : properties
+ .entrySet()) {
final Property prop = entry.getKey();
- final String value = entry.getValue() ? config.getSubst().replace(prop.getValue()) : prop.getValue();
+ final String value = entry.getValue() ? config.getSubst()
+ .replace(prop.getValue()) : prop.getValue();
props.add(Property.createProperty(prop.getName(), value));
}
}
- final LogEvent event = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);
+ final LogEvent event = logEventFactory.createEvent(loggerName, marker,
+ fqcn, level, data, props, t);
log(event);
}
/**
- * Waits for all log events to complete before shutting down this loggerConfig.
+ * Waits for all log events to complete before shutting down this
+ * loggerConfig.
*/
private synchronized void waitForCompletion() {
if (shutdown) {
@@ -303,6 +367,7 @@
/**
* Logs an event.
+ *
* @param event The log event.
*/
public void log(final LogEvent event) {
@@ -313,6 +378,8 @@
return;
}
+ event.setIncludeLocation(isIncludeLocation());
+
callAppenders(event);
if (additive && parent != null) {
@@ -330,7 +397,7 @@
}
}
- private void callAppenders(final LogEvent event) {
+ protected void callAppenders(final LogEvent event) {
for (final AppenderControl control : appenders.values()) {
control.callAppender(event);
}
@@ -338,6 +405,7 @@
/**
* Creates a log event.
+ *
* @param loggerName The name of the Logger.
* @param marker An optional Marker.
* @param fqcn The fully qualified class name of the caller.
@@ -347,9 +415,11 @@
* @param t An optional Throwable.
* @return The LogEvent.
*/
- public LogEvent createEvent(final String loggerName, final Marker marker, final String fqcn, final Level level,
- final Message data, final List properties, final Throwable t) {
- return new Log4jLogEvent(loggerName, marker, fqcn, level, data, properties, t);
+ public LogEvent createEvent(final String loggerName, final Marker marker,
+ final String fqcn, final Level level, final Message data,
+ final List properties, final Throwable t) {
+ return new Log4jLogEvent(loggerName, marker, fqcn, level, data,
+ properties, t);
}
@Override
@@ -359,9 +429,11 @@
/**
* Factory method to create a LoggerConfig.
+ *
* @param additivity True if additive, false otherwise.
* @param levelName The Level to be associated with the Logger.
* @param loggerName The name of the Logger.
+ * @param includeLocation whether location should be passed downstream
* @param refs An array of Appender names.
* @param properties Properties to pass to the Logger.
* @param config The Configuration.
@@ -369,13 +441,15 @@
* @return A new LoggerConfig.
*/
@PluginFactory
- public static LoggerConfig createLogger(@PluginAttr("additivity") final String additivity,
- @PluginAttr("level") final String levelName,
- @PluginAttr("name") final String loggerName,
- @PluginElement("appender-ref") final AppenderRef[] refs,
- @PluginElement("properties") final Property[] properties,
- @PluginConfiguration final Configuration config,
- @PluginElement("filters") final Filter filter) {
+ public static LoggerConfig createLogger(
+ @PluginAttr("additivity") final String additivity,
+ @PluginAttr("level") final String levelName,
+ @PluginAttr("name") final String loggerName,
+ @PluginAttr("includeLocation") final String includeLocation,
+ @PluginElement("appender-ref") final AppenderRef[] refs,
+ @PluginElement("properties") final Property[] properties,
+ @PluginConfiguration final Configuration config,
+ @PluginElement("filters") final Filter filter) {
if (loggerName == null) {
LOGGER.error("Loggers cannot be configured without a name");
return null;
@@ -386,14 +460,29 @@
try {
level = Level.toLevel(levelName, Level.ERROR);
} catch (final Exception ex) {
- LOGGER.error("Invalid Log level specified: {}. Defaulting to Error", levelName);
+ LOGGER.error(
+ "Invalid Log level specified: {}. Defaulting to Error",
+ levelName);
level = Level.ERROR;
}
final String name = loggerName.equals("root") ? "" : loggerName;
- final boolean additive = additivity == null ? true : Boolean.parseBoolean(additivity);
+ final boolean additive = additivity == null ? true : Boolean
+ .parseBoolean(additivity);
- return new LoggerConfig(name, appenderRefs, filter, level, additive, properties, config);
+ return new LoggerConfig(name, appenderRefs, filter, level, additive,
+ properties, config, includeLocation(includeLocation));
}
+
+ // Note: for asynchronous loggers, includeLocation default is FALSE,
+ // for synchronous loggers, includeLocation default is TRUE.
+ private static boolean includeLocation(String includeLocationConfigValue) {
+ if (includeLocationConfigValue == null) {
+ final boolean sync = !"org.apache.logging.log4j.async.AsyncLoggerContextSelector"
+ .equals(System.getProperty(Constants.LOG4J_CONTEXT_SELECTOR));
+ return sync;
+ }
+ return Boolean.parseBoolean(includeLocationConfigValue);
+ }
/**
* The root Logger.
@@ -402,24 +491,30 @@
public static class RootLogger extends LoggerConfig {
@PluginFactory
- public static LoggerConfig createLogger(@PluginAttr("additivity") final String additivity,
- @PluginAttr("level") final String levelName,
- @PluginElement("appender-ref") final AppenderRef[] refs,
- @PluginElement("properties") final Property[] properties,
- @PluginConfiguration final Configuration config,
- @PluginElement("filters") final Filter filter) {
+ public static LoggerConfig createLogger(
+ @PluginAttr("additivity") final String additivity,
+ @PluginAttr("level") final String levelName,
+ @PluginAttr("includeLocation") final String includeLocation,
+ @PluginElement("appender-ref") final AppenderRef[] refs,
+ @PluginElement("properties") final Property[] properties,
+ @PluginConfiguration final Configuration config,
+ @PluginElement("filters") final Filter filter) {
final List appenderRefs = Arrays.asList(refs);
Level level;
try {
level = Level.toLevel(levelName, Level.ERROR);
} catch (final Exception ex) {
- LOGGER.error("Invalid Log level specified: {}. Defaulting to Error", levelName);
+ LOGGER.error(
+ "Invalid Log level specified: {}. Defaulting to Error",
+ levelName);
level = Level.ERROR;
}
- final boolean additive = additivity == null ? true : Boolean.parseBoolean(additivity);
+ final boolean additive = additivity == null ? true : Boolean
+ .parseBoolean(additivity);
- return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, level, additive, properties,
- config);
+ return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs,
+ filter, level, additive, properties, config,
+ includeLocation(includeLocation));
}
}
Index: core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
===================================================================
--- core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java (revision 1462911)
+++ core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java (working copy)
@@ -49,6 +49,8 @@
private final ThreadContext.ContextStack ndc;
private String threadName = null;
private StackTraceElement location;
+ private boolean includeLocation;
+ private boolean endOfBatch = false;
/**
* Constructor.
@@ -222,43 +224,67 @@
* @return the StackTraceElement for the caller.
*/
public StackTraceElement getSource() {
+ if (location != null) {
+ return location;
+ }
+ if (fqcnOfLogger == null || !includeLocation) {
+ return null;
+ }
+ location = calcLocation(fqcnOfLogger);
+ return location;
+ }
+
+ public static StackTraceElement calcLocation(String fqcnOfLogger) {
if (fqcnOfLogger == null) {
return null;
}
- if (location == null) {
- final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
- boolean next = false;
- for (final StackTraceElement element : stackTrace) {
- final String className = element.getClassName();
- if (next) {
- if (fqcnOfLogger.equals(className)) {
- continue;
- }
- location = element;
- break;
- }
-
+ final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ boolean next = false;
+ for (final StackTraceElement element : stackTrace) {
+ final String className = element.getClassName();
+ if (next) {
if (fqcnOfLogger.equals(className)) {
- next = true;
- } else if (NOT_AVAIL.equals(className)) {
- break;
+ continue;
}
+ return element;
}
+
+ if (fqcnOfLogger.equals(className)) {
+ next = true;
+ } else if (NOT_AVAIL.equals(className)) {
+ break;
+ }
}
+ return null;
+ }
- return location;
+ public boolean isIncludeLocation() {
+ return includeLocation;
}
+ public void setIncludeLocation(boolean includeLocation) {
+ this.includeLocation = includeLocation;
+ }
+
+ public boolean isEndOfBatch() {
+ return endOfBatch;
+ }
+
+ public void setEndOfBatch(boolean endOfBatch) {
+ this.endOfBatch = endOfBatch;
+ }
+
/**
* Creates a LogEventProxy that can be serialized.
* @return a LogEventProxy.
*/
protected Object writeReplace() {
- return new LogEventProxy(this);
+ return new LogEventProxy(this, this.includeLocation);
}
- public static Serializable serialize(final Log4jLogEvent event) {
- return new LogEventProxy(event);
+ public static Serializable serialize(final Log4jLogEvent event,
+ final boolean includeLocation) {
+ return new LogEventProxy(event, includeLocation);
}
public static Log4jLogEvent deserialize(final Serializable event) {
@@ -267,8 +293,13 @@
}
if (event instanceof LogEventProxy) {
final LogEventProxy proxy = (LogEventProxy) event;
- return new Log4jLogEvent(proxy.name, proxy.marker, proxy.fqcnOfLogger, proxy.level, proxy.message,
- proxy.throwable, proxy.mdc, proxy.ndc, proxy.threadName, proxy.location, proxy.timestamp);
+ Log4jLogEvent result = new Log4jLogEvent(proxy.name, proxy.marker,
+ proxy.fqcnOfLogger, proxy.level, proxy.message,
+ proxy.throwable, proxy.mdc, proxy.ndc, proxy.threadName,
+ proxy.location, proxy.timestamp);
+ result.setEndOfBatch(proxy.isEndOfBatch);
+ result.setIncludeLocation(proxy.isLocationRequired);
+ return result;
}
throw new IllegalArgumentException("Event is not a serialized LogEvent: " + event.toString());
}
@@ -304,8 +335,10 @@
private final ThreadContext.ContextStack ndc;
private final String threadName;
private final StackTraceElement location;
+ private final boolean isLocationRequired;
+ private final boolean isEndOfBatch;
- public LogEventProxy(final Log4jLogEvent event) {
+ public LogEventProxy(final Log4jLogEvent event, boolean includeLocation) {
this.fqcnOfLogger = event.fqcnOfLogger;
this.marker = event.marker;
this.level = event.level;
@@ -315,8 +348,10 @@
this.throwable = event.throwable;
this.mdc = event.mdc;
this.ndc = event.ndc;
- this.location = event.getSource();
+ this.location = includeLocation ? event.getSource() : null;
this.threadName = event.getThreadName();
+ this.isLocationRequired = includeLocation;
+ this.isEndOfBatch = event.endOfBatch;
}
/**
@@ -324,10 +359,12 @@
* @return Log4jLogEvent.
*/
protected Object readResolve() {
- return new Log4jLogEvent(name, marker, fqcnOfLogger, level, message, throwable, mdc, ndc, threadName,
- location, timestamp);
+ Log4jLogEvent result = new Log4jLogEvent(name, marker, fqcnOfLogger,
+ level, message, throwable, mdc, ndc, threadName, location,
+ timestamp);
+ result.setEndOfBatch(isEndOfBatch);
+ result.setIncludeLocation(isLocationRequired);
+ return result;
}
-
}
-
}
Index: core/src/test/java/org/apache/logging/log4j/core/appender/AsynchAppenderNoLocationTest.java
===================================================================
--- core/src/test/java/org/apache/logging/log4j/core/appender/AsynchAppenderNoLocationTest.java (revision 0)
+++ core/src/test/java/org/apache/logging/log4j/core/appender/AsynchAppenderNoLocationTest.java (working copy)
@@ -0,0 +1,87 @@
+/*
+ * 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.logging.log4j.core.appender;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.XMLConfigurationFactory;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class AsynchAppenderNoLocationTest {
+ private static final String CONFIG = "log4j-asynch-no-location.xml";
+ private static Configuration config;
+ private static ListAppender app;
+ private static LoggerContext ctx;
+
+ @BeforeClass
+ public static void setupClass() {
+ System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY, CONFIG);
+ ctx = (LoggerContext) LogManager.getContext(false);
+ config = ctx.getConfiguration();
+ for (final Map.Entry> entry : config.getAppenders().entrySet()) {
+ if (entry.getKey().equals("List")) {
+ app = (ListAppender) entry.getValue();
+ break;
+ }
+ }
+ }
+
+ @AfterClass
+ public static void cleanupClass() {
+ System.clearProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY);
+ ctx.reconfigure();
+ StatusLogger.getLogger().reset();
+ }
+
+ @After
+ public void after() {
+ app.clear();
+ }
+
+ @Test
+ public void testNoLocation() throws Exception {
+ final Logger logger = LogManager.getLogger(AsynchAppender.class);
+ logger.error("This is a test");
+ logger.warn("Hello world!");
+ Thread.sleep(100);
+ final List list = app.getMessages();
+ assertNotNull("No events generated", list);
+ assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
+ String msg = list.get(0);
+ String expected = "? This is a test";
+ assertTrue("Expected " + expected + ", Actual " + msg, expected.equals(msg));
+ msg = list.get(1);
+ expected = "? Hello world!";
+ assertTrue("Expected " + expected + ", Actual " + msg, expected.equals(msg));
+ }
+}
Index: core/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.java
===================================================================
--- core/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.java (revision 1462911)
+++ core/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.java (working copy)
@@ -94,5 +94,19 @@
public String getFQCN() {
return null;
}
+
+ public boolean isEndOfBatch() {
+ return false;
+ }
+
+ public void setEndOfBatch(boolean endOfBatch) {
+ }
+
+ public boolean isIncludeLocation() {
+ return false;
+ }
+
+ public void setIncludeLocation(boolean locationRequired) {
+ }
}
}
Index: core/src/test/resources/log4j-asynch-no-location.xml
===================================================================
--- core/src/test/resources/log4j-asynch-no-location.xml (revision 0)
+++ core/src/test/resources/log4j-asynch-no-location.xml (working copy)
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: core/src/test/resources/log4j-asynch.xml
===================================================================
--- core/src/test/resources/log4j-asynch.xml (revision 1462911)
+++ core/src/test/resources/log4j-asynch.xml (working copy)
@@ -25,7 +25,7 @@
-
+
Index: flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEvent.java
===================================================================
--- flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEvent.java (revision 1462911)
+++ flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEvent.java (working copy)
@@ -277,4 +277,24 @@
public ThreadContext.ContextStack getContextStack() {
return event.getContextStack();
}
+
+ @Override
+ public boolean isIncludeLocation() {
+ return event.isIncludeLocation();
+ }
+
+ @Override
+ public void setIncludeLocation(boolean includeLocation) {
+ event.setIncludeLocation(includeLocation);
+ }
+
+ @Override
+ public boolean isEndOfBatch() {
+ return event.isEndOfBatch();
+ }
+
+ @Override
+ public void setEndOfBatch(boolean endOfBatch) {
+ event.setEndOfBatch(endOfBatch);
+ }
}
Index: log4j-async/pom.xml
===================================================================
--- log4j-async/pom.xml (revision 0)
+++ log4j-async/pom.xml (working copy)
@@ -0,0 +1,250 @@
+
+
+
+ 4.0.0
+
+ log4j
+ org.apache.logging.log4j
+ 2.0-beta5-SNAPSHOT
+ ../
+
+
+ org.apache.logging.log4j
+ log4j-async
+ jar
+ Apache Log4j Async
+ Log4j 2.0 Asynchronous Loggers for Low Latency Logging
+
+ UTF-8
+ ${basedir}/..
+ Asynchronous Logging Documentation
+ /log4j-async
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.apache.logging.log4j
+ log4j-core
+ test-jar
+ test
+
+
+ com.lmax
+ disruptor
+ 3.0.0.beta3
+
+
+
+ org.apache.logging.log4j.adapters
+ log4j-1.2-api
+ test
+
+
+ org.slf4j
+ slf4j-api
+ test
+
+
+ ch.qos.logback
+ logback-core
+ test
+
+
+ ch.qos.logback
+ logback-classic
+ test
+
+
+ junit
+ junit
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ test-jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ always
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.2.1
+
+
+ process-classes
+
+ java
+
+
+
+
+ org.apache.logging.log4j.core.config.plugins.PluginManager
+
+ ${project.build.outputDirectory}
+ org.apache.logging.log4j.async
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-changes-plugin
+ ${changes.plugin.version}
+
+
+
+ changes-report
+
+
+
+
+ %URL%/show_bug.cgi?id=%ISSUE%
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 2.7
+
+
+ ${log4jParentDir}/checkstyle.xml
+ ${log4jParentDir}/checkstyle-suppressions.xml
+ false
+ basedir=${basedir}
+ licensedir=${log4jParentDir}/checkstyle-header.txt
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${javadoc.plugin.version}
+
+ Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved. Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, and the
+ Apache Logging project logo are trademarks of The Apache Software Foundation.
+
+ false
+ true
+
+
+ issue
+ a
+ JIRA issue:
+
+
+ doubt
+ a
+ Troublesome:
+
+
+ compare
+ a
+ Compare with:
+
+
+
+
+
+ non-aggregate
+
+ javadoc
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jxr-plugin
+ 2.3
+
+
+ non-aggregate
+
+ jxr
+
+
+
+ aggregate
+
+ aggregate
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-pmd-plugin
+ ${pmd.plugin.version}
+
+ 1.5
+
+
+
+ org.codehaus.mojo
+ cobertura-maven-plugin
+ 2.2
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLogger.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLogger.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLogger.java (working copy)
@@ -0,0 +1,245 @@
+/*
+ * 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.logging.log4j.async;
+
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import com.lmax.disruptor.BlockingWaitStrategy;
+import com.lmax.disruptor.EventHandler;
+import com.lmax.disruptor.ExceptionHandler;
+import com.lmax.disruptor.RingBuffer;
+import com.lmax.disruptor.SleepingWaitStrategy;
+import com.lmax.disruptor.WaitStrategy;
+import com.lmax.disruptor.YieldingWaitStrategy;
+import com.lmax.disruptor.dsl.Disruptor;
+import com.lmax.disruptor.dsl.ProducerType;
+import com.lmax.disruptor.util.Util;
+
+/**
+ * AsyncLogger is a logger designed for high throughput and low latency logging.
+ * It does not perform any I/O in the calling (application) thread, but instead
+ * hands off the work to another thread as soon as possible. The actual logging
+ * is performed in the background thread. It uses the LMAX Disruptor library for
+ * inter-thread communication. (http://lmax-exchange.github.com/disruptor/)
+ *
+ * To use AsyncLogger, specify the System property
+ * -DLog4jContextSelector=log4j.async.AsyncLoggerContextSelector before you
+ * obtain a Logger, and all Loggers returned by LogManager.getLogger will be
+ * AsyncLoggers.
+ *
+ * Note that for performance reasons, this logger does not support source
+ * location. Any %class, %location or %line conversion patterns in your
+ * log4j.xml configuration will produce either a "?" character or no output at
+ * all.
+ *
+ * For best performance, use AsyncLogger with the FastFileAppender or
+ * FastRollingFileAppender, with immediateFlush=false. These appenders have
+ * built-in support for the batching mechanism used by the Disruptor library,
+ * and they will flush to disk at the end of each batch. This means that even
+ * with immediateFlush=false, there will never be any items left in the buffer;
+ * all log events will all be written to disk in a very efficient manner.
+ */
+public class AsyncLogger extends Logger { // depends on LOG4J2-151
+ private static final StatusLogger LOGGER = StatusLogger.getLogger();
+ private static volatile Disruptor disruptor;
+ private static Clock clock = ClockFactory.getClock();
+
+ private static ExecutorService executor = Executors
+ .newSingleThreadExecutor();
+ private ThreadLocal threadlocalInfo = new ThreadLocal();
+
+ static {
+ int ringBufferSize = calculateRingBufferSize();
+
+ WaitStrategy waitStrategy = createWaitStrategy();
+ disruptor = new Disruptor(
+ RingBufferLogEvent.FACTORY, ringBufferSize, executor,
+ ProducerType.MULTI, waitStrategy);
+ EventHandler[] handlers = new RingBufferLogEventHandler[] { new RingBufferLogEventHandler() };
+ disruptor.handleExceptionsWith(getExceptionHandler());
+ disruptor.handleEventsWith(handlers);
+
+ LOGGER.debug(
+ "Starting AsyncLogger disruptor with ringbuffer size {}...",
+ disruptor.getRingBuffer().getBufferSize());
+ disruptor.start();
+ }
+
+ private static int calculateRingBufferSize() {
+ String userPreferredRBSize = System.getProperty(
+ "AsyncLogger.RingBufferSize", "256000");
+ int ringBufferSize = 256000; // default
+ try {
+ int size = Integer.parseInt(userPreferredRBSize);
+ if (size < 128) {
+ size = 128;
+ LOGGER.warn(
+ "Invalid RingBufferSize {}, using minimum size 128.",
+ userPreferredRBSize);
+ }
+ ringBufferSize = size;
+ } catch (Exception ex) {
+ LOGGER.warn("Invalid RingBufferSize {}, using default size.",
+ userPreferredRBSize);
+ }
+ return Util.ceilingNextPowerOfTwo(ringBufferSize);
+ }
+
+ private static WaitStrategy createWaitStrategy() {
+ String strategy = System.getProperty("AsyncLogger.WaitStrategy");
+ LOGGER.debug("property AsyncLogger.WaitStrategy={}", strategy);
+ if ("Sleep".equals(strategy)) {
+ LOGGER.debug("disruptor event handler uses SleepingWaitStrategy");
+ return new SleepingWaitStrategy();
+ } else if ("Yield".equals(strategy)) {
+ LOGGER.debug("disruptor event handler uses YieldingWaitStrategy");
+ return new YieldingWaitStrategy();
+ } else if ("Block".equals(strategy)) {
+ LOGGER.debug("disruptor event handler uses BlockingWaitStrategy");
+ return new BlockingWaitStrategy();
+ }
+ LOGGER.debug("disruptor event handler uses SleepingWaitStrategy");
+ return new SleepingWaitStrategy();
+ }
+
+ private static ExceptionHandler getExceptionHandler() {
+ String cls = System.getProperty("AsyncLogger.ExceptionHandler");
+ if (cls == null) {
+ LOGGER.debug("No AsyncLogger.ExceptionHandler specified");
+ return null;
+ }
+ try {
+ @SuppressWarnings("unchecked")
+ Class extends ExceptionHandler> klass = (Class extends ExceptionHandler>) Class
+ .forName(cls);
+ ExceptionHandler result = klass.newInstance();
+ LOGGER.debug("AsyncLogger.ExceptionHandler=" + result);
+ return result;
+ } catch (Exception ignored) {
+ LOGGER.debug(
+ "AsyncLogger.ExceptionHandler not set: error creating "
+ + cls + ": ", ignored);
+ return null;
+ }
+ }
+
+ private static class Info {
+ RingBufferLogEventTranslator translator;
+ String cachedThreadName;
+ }
+
+ public AsyncLogger(LoggerContext context, String name,
+ MessageFactory messageFactory) {
+ super(context, name, messageFactory);
+ }
+
+ @Override
+ public void log(Marker marker, String fqcn, Level level, Message data,
+ Throwable t) {
+ Info info = threadlocalInfo.get();
+ if (info == null) {
+ info = new Info();
+ info.translator = new RingBufferLogEventTranslator();
+ info.cachedThreadName = Thread.currentThread().getName();
+ threadlocalInfo.set(info);
+ }
+
+ Boolean includeLocation = config.loggerConfig.isIncludeLocation();
+ info.translator.setValues(this, getName(), marker, fqcn, level, data,
+ t, //
+
+ // config properties are taken care of in the EventHandler
+ // thread in the #actualAsyncLog method
+
+ // needs shallow copy to be fast (LOG4J2-154)
+ ThreadContext.getImmutableContext(),//
+
+ // needs shallow copy to be fast (LOG4J2-154)
+ ThreadContext.getImmutableStack(), //
+
+ // Thread.currentThread().getName(), //
+ info.cachedThreadName, //
+
+ // location: very expensive operation. LOG4J2-153:
+ // Only include if "includeLocation=true" is specified,
+ // exclude if not specified or if "false" was specified.
+ includeLocation != null && includeLocation ? location(fqcn)
+ : null,
+
+ // System.currentTimeMillis());
+ // CoarseCachedClock: 20% faster than system clock, 16ms gaps
+ // CachedClock: 10% faster than system clock, smaller gaps
+ clock.currentTimeMillis());
+
+ disruptor.publishEvent(info.translator);
+ }
+
+ private StackTraceElement location(String fqcnOfLogger) {
+ return Log4jLogEvent.calcLocation(fqcnOfLogger);
+ }
+
+ /**
+ * This method is called by the EventHandler that processes the
+ * RingBufferLogEvent in a separate thread.
+ *
+ * @param event the event to log
+ */
+ public void actualAsyncLog(RingBufferLogEvent event) {
+ Map properties = config.loggerConfig.getProperties();
+ event.mergePropertiesIntoContextMap(properties,
+ config.config.getSubst());
+ config.logEvent(event);
+ }
+
+ public static void stop() {
+ Disruptor temp = disruptor;
+
+ // Must guarantee that publishing to the RingBuffer has stopped
+ // before we call disruptor.shutdown()
+ disruptor = null; // client code fails with NPE if log after stop = OK
+ temp.shutdown();
+
+ // wait up to 10 seconds for the ringbuffer to drain
+ RingBuffer ringBuffer = temp.getRingBuffer();
+ for (int i = 0; i < 20; i++) {
+ if (ringBuffer.hasAvailableCapacity(ringBuffer.getBufferSize())) {
+ break;
+ }
+ try {
+ Thread.sleep(500); // give ringbuffer some time to drain...
+ } catch (InterruptedException e) {
+ }
+ }
+ executor.shutdown(); // finally, kill the processor thread
+ }
+
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLoggerContext.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLoggerContext.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLoggerContext.java (working copy)
@@ -0,0 +1,57 @@
+/*
+ * 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.logging.log4j.async;
+
+import java.net.URI;
+
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.message.MessageFactory;
+
+// depends on LOG4J2-151
+public class AsyncLoggerContext extends LoggerContext {
+
+ public AsyncLoggerContext(String name) {
+ super(name);
+ }
+
+ public AsyncLoggerContext(String name, Object externalContext) {
+ super(name, externalContext);
+ }
+
+ public AsyncLoggerContext(String name, Object externalContext,
+ URI configLocn) {
+ super(name, externalContext, configLocn);
+ }
+
+ public AsyncLoggerContext(String name, Object externalContext,
+ String configLocn) {
+ super(name, externalContext, configLocn);
+ }
+
+ // @Override
+ protected Logger newInstance(LoggerContext ctx, String name,
+ MessageFactory messageFactory) {
+ return new AsyncLogger(ctx, name, messageFactory);
+ }
+
+ @Override
+ public void stop() {
+ AsyncLogger.stop();
+ super.stop();
+ }
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLoggerContextSelector.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLoggerContextSelector.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLoggerContextSelector.java (working copy)
@@ -0,0 +1,48 @@
+/*
+ * 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.logging.log4j.async;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.selector.ContextSelector;
+
+public class AsyncLoggerContextSelector implements ContextSelector {
+
+ private static final AsyncLoggerContext context = new AsyncLoggerContext(
+ "Default");
+
+ public LoggerContext getContext(String fqcn, ClassLoader loader,
+ boolean currentContext) {
+ return context;
+ }
+
+ public List getLoggerContexts() {
+ List list = new ArrayList();
+ list.add(context);
+ return Collections.unmodifiableList(list);
+ }
+
+ public LoggerContext getContext(String fqcn, ClassLoader loader,
+ boolean currentContext, URI configLocation) {
+ return context;
+ }
+
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/CachedClock.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/CachedClock.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/CachedClock.java (working copy)
@@ -0,0 +1,56 @@
+/*
+ * 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.logging.log4j.async;
+
+import com.lmax.disruptor.util.Util;
+
+
+public class CachedClock implements Clock {
+ private static CachedClock instance = new CachedClock();
+ private volatile long millis = System.currentTimeMillis();
+ private volatile short count = 0;
+ private final Thread updater = new Thread("Clock Updater Thread") {
+ public void run() {
+ while (true) {
+ long time = System.currentTimeMillis();
+ millis = time;
+ Util.getUnsafe().park(true, time + 1); // abs (millis)
+ // Util.getUnsafe().park(false, 1000 * 1000);// relative(nanos)
+ }
+ }
+ };
+
+ public static CachedClock instance() {
+ return instance;
+ }
+
+ private CachedClock() {
+ updater.setDaemon(true);
+ updater.start();
+ }
+
+ // @Override
+ public long currentTimeMillis() {
+
+ // improve granularity: also update time field every 1024 calls.
+ // (the bit fiddling means we don't need to worry about overflows)
+ if ((++count & 0x3FF) == 0x3FF) {
+ millis = System.currentTimeMillis();
+ }
+ return millis;
+ }
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/Clock.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/Clock.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/Clock.java (working copy)
@@ -0,0 +1,21 @@
+/*
+ * 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.logging.log4j.async;
+
+public interface Clock {
+ long currentTimeMillis();
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/ClockFactory.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/ClockFactory.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/ClockFactory.java (working copy)
@@ -0,0 +1,41 @@
+package org.apache.logging.log4j.async;
+
+import org.apache.logging.log4j.status.StatusLogger;
+
+public class ClockFactory {
+
+ public static final String PROPERTY_NAME = "AsyncLogger.Clock";
+ //private static final Clock clock = createClock();
+
+ public static Clock getClock() {
+ return createClock();
+ }
+
+ private static Clock createClock() {
+ final StatusLogger LOGGER = StatusLogger.getLogger();
+ String userRequest = System.getProperty(PROPERTY_NAME);
+ if (userRequest == null || "SystemClock".equals(userRequest)) {
+ LOGGER.debug("Using default SystemClock for timestamps");
+ return new SystemClock();
+ }
+ if ("org.apache.logging.log4j.async.CachedClock".equals(userRequest)
+ || "CachedClock".equals(userRequest)) {
+ LOGGER.debug("Using specified CachedClock for timestamps");
+ return CachedClock.instance();
+ }
+ if ("org.apache.logging.log4j.async.CoarseCachedClock"
+ .equals(userRequest) || "CoarseCachedClock".equals(userRequest)) {
+ LOGGER.debug("Using specified CoarseCachedClock for timestamps");
+ return CoarseCachedClock.instance();
+ }
+ try {
+ Clock result = (Clock) Class.forName(userRequest).newInstance();
+ LOGGER.debug("Using {} for timestamps", userRequest);
+ return result;
+ } catch (Exception e) {
+ String fmt = "Could not create {}: {}, using default SystemClock for timestamps";
+ LOGGER.error(fmt, userRequest, e);
+ return new SystemClock();
+ }
+ }
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/CoarseCachedClock.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/CoarseCachedClock.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/CoarseCachedClock.java (working copy)
@@ -0,0 +1,53 @@
+/*
+ * 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.logging.log4j.async;
+
+import com.lmax.disruptor.util.Util;
+
+
+/**
+ * This Clock implementation is similar to CachedClock. It is slightly faster at
+ * the cost of some accuracy.
+ */
+public class CoarseCachedClock implements Clock {
+ private static CoarseCachedClock instance = new CoarseCachedClock();
+ private volatile long millis = System.currentTimeMillis();
+ private final Thread updater = new Thread("Clock Updater Thread") {
+ public void run() {
+ while (true) {
+ long time = System.currentTimeMillis();
+ millis = time;
+ Util.getUnsafe().park(true, time + 1); // abs (millis)
+ // Util.getUnsafe().park(false, 1000 * 1000);// relative(nanos)
+ }
+ }
+ };
+
+ public static CoarseCachedClock instance() {
+ return instance;
+ }
+
+ private CoarseCachedClock() {
+ updater.setDaemon(true);
+ updater.start();
+ }
+
+ // @Override
+ public long currentTimeMillis() {
+ return millis;
+ }
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEvent.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEvent.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEvent.java (working copy)
@@ -0,0 +1,207 @@
+/*
+ * 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.logging.log4j.async;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.ThreadContext.ContextStack;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.SimpleMessage;
+
+import com.lmax.disruptor.EventFactory;
+
+/**
+ * When the Disruptor is started, the RingBuffer is populated with event
+ * objects. These objects are then re-used during the life of the RingBuffer.
+ */
+public class RingBufferLogEvent implements LogEvent {
+ private static final long serialVersionUID = 8462119088943934758L;
+
+ public static final Factory FACTORY = new Factory();
+
+ /**
+ * Creates the events that will be put in the RingBuffer.
+ */
+ private static class Factory implements EventFactory {
+ // @Override
+ public RingBufferLogEvent newInstance() {
+ return new RingBufferLogEvent();
+ }
+ }
+
+ private AsyncLogger asyncLogger;
+ private String loggerName;
+ private Marker marker;
+ private String fqcn;
+ private Level level;
+ private Message message;
+ private Throwable thrown;
+ private Map contextMap;
+ private ContextStack contextStack;
+ private String threadName;
+ private StackTraceElement location;
+ private long currentTimeMillis;
+ private boolean endOfBatch;
+ private boolean includeLocation;
+
+ public void setValues(AsyncLogger asyncLogger, String loggerName,
+ Marker marker, String fqcn, Level level, Message data, Throwable t,
+ Map map, ContextStack contextStack,
+ String threadName, StackTraceElement location,
+ long currentTimeMillis) {
+ this.asyncLogger = asyncLogger;
+ this.loggerName = loggerName;
+ this.marker = marker;
+ this.fqcn = fqcn;
+ this.level = level;
+ this.message = data;
+ this.thrown = t;
+ this.contextMap = map;
+ this.contextStack = contextStack;
+ this.threadName = threadName;
+ this.location = location;
+ this.currentTimeMillis = currentTimeMillis;
+ }
+
+ /**
+ * Event processor that reads the event from the ringbuffer can call this
+ * method.
+ *
+ * @param endOfBatch flag to indicate if this is the last event in a batch
+ * from the RingBuffer
+ */
+ public void execute(boolean endOfBatch) {
+ this.endOfBatch = endOfBatch;
+ asyncLogger.actualAsyncLog(this);
+ }
+
+ /**
+ * Returns {@code true} if this event is the end of a batch, {@code false}
+ * otherwise.
+ *
+ * @return {@code true} if this event is the end of a batch, {@code false}
+ * otherwise
+ */
+ public boolean isEndOfBatch() {
+ return endOfBatch;
+ }
+
+ public void setEndOfBatch(boolean endOfBatch) {
+ this.endOfBatch = endOfBatch;
+ }
+
+ public boolean isIncludeLocation() {
+ return includeLocation;
+ }
+
+ public void setIncludeLocation(boolean includeLocation) {
+ this.includeLocation = includeLocation;
+ }
+
+ // @Override
+ public String getLoggerName() {
+ return loggerName;
+ }
+
+ // @Override
+ public Marker getMarker() {
+ return marker;
+ }
+
+ // @Override
+ public String getFQCN() {
+ return fqcn;
+ }
+
+ // @Override
+ public Level getLevel() {
+ return level;
+ }
+
+ // @Override
+ public Message getMessage() {
+ if (message == null) {
+ message = new SimpleMessage("");
+ }
+ return message;
+ }
+
+ // @Override
+ public Throwable getThrown() {
+ return thrown;
+ }
+
+ // @Override
+ public Map getContextMap() {
+ return contextMap;
+ }
+
+ // @Override
+ public ContextStack getContextStack() {
+ return contextStack;
+ }
+
+ // @Override
+ public String getThreadName() {
+ return threadName;
+ }
+
+ // @Override
+ public StackTraceElement getSource() {
+ return location;
+ }
+
+ // @Override
+ public long getMillis() {
+ return currentTimeMillis;
+ }
+
+ /**
+ * Merges the contents of the specified map into the contextMap, after
+ * replacing any variables in the property values with the
+ * StrSubstitutor-supplied actual values.
+ *
+ * @param properties configured properties
+ * @param strSubstitutor used to lookup values of variables in properties
+ */
+ public void mergePropertiesIntoContextMap(
+ Map properties, StrSubstitutor strSubstitutor) {
+ if (properties == null) {
+ return; // nothing to do
+ }
+
+ Map map = (contextMap == null) ? new HashMap()
+ : new HashMap(contextMap);
+
+ for (Map.Entry entry : properties.entrySet()) {
+ Property prop = entry.getKey();
+ if (map.containsKey(prop.getName())) {
+ continue; // contextMap overrides config properties
+ }
+ String value = entry.getValue() ? strSubstitutor.replace(prop
+ .getValue()) : prop.getValue();
+ map.put(prop.getName(), value);
+ }
+ contextMap = map;
+ }
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEventHandler.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEventHandler.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEventHandler.java (working copy)
@@ -0,0 +1,36 @@
+/*
+ * 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.logging.log4j.async;
+
+import com.lmax.disruptor.EventHandler;
+
+/**
+ * This event handler gets passed messages from the RingBuffer as they become
+ * available. Processing of these messages is done in a separate thread,
+ * controlled by the {@code Executor} passed to the {@code Disruptor}
+ * constructor.
+ */
+public class RingBufferLogEventHandler implements
+ EventHandler {
+
+ // @Override
+ public void onEvent(RingBufferLogEvent event, long sequence,
+ boolean endOfBatch) throws Exception {
+ event.execute(endOfBatch);
+ }
+
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEventTranslator.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEventTranslator.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEventTranslator.java (working copy)
@@ -0,0 +1,70 @@
+/*
+ * 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.logging.log4j.async;
+
+import java.util.Map;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.ThreadContext.ContextStack;
+import org.apache.logging.log4j.message.Message;
+
+import com.lmax.disruptor.EventTranslator;
+
+public class RingBufferLogEventTranslator implements
+ EventTranslator {
+
+ private AsyncLogger asyncLogger;
+ private String loggerName;
+ private Marker marker;
+ private String fqcn;
+ private Level level;
+ private Message message;
+ private Throwable thrown;
+ private Map contextMap;
+ private ContextStack contextStack;
+ private String threadName;
+ private StackTraceElement location;
+ private long currentTimeMillis;
+
+ // @Override
+ public void translateTo(RingBufferLogEvent event, long sequence) {
+ event.setValues(asyncLogger, loggerName, marker, fqcn, level, message,
+ thrown, contextMap, contextStack, threadName, location,
+ currentTimeMillis);
+ }
+
+ public void setValues(AsyncLogger asyncLogger, String loggerName,
+ Marker marker, String fqcn, Level level, Message message,
+ Throwable thrown, Map contextMap,
+ ContextStack contextStack, String threadName,
+ StackTraceElement location, long currentTimeMillis) {
+ this.asyncLogger = asyncLogger;
+ this.loggerName = loggerName;
+ this.marker = marker;
+ this.fqcn = fqcn;
+ this.level = level;
+ this.message = message;
+ this.thrown = thrown;
+ this.contextMap = contextMap;
+ this.contextStack = contextStack;
+ this.threadName = threadName;
+ this.location = location;
+ this.currentTimeMillis = currentTimeMillis;
+ }
+
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/SystemClock.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/SystemClock.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/SystemClock.java (working copy)
@@ -0,0 +1,26 @@
+/*
+ * 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.logging.log4j.async;
+
+public class SystemClock implements Clock {
+
+ // @Override
+ public long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastFileAppender.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastFileAppender.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastFileAppender.java (working copy)
@@ -0,0 +1,165 @@
+/*
+ * 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.logging.log4j.async.appender;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttr;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.net.Advertiser;
+
+/**
+ * File Appender.
+ */
+@Plugin(name = "FastFile", type = "Core", elementType = "appender", printObject = true)
+public final class FastFileAppender extends AbstractOutputStreamAppender {
+
+ private final String fileName;
+ private Object advertisement;
+ private final Advertiser advertiser;
+
+ private FastFileAppender(String name, Layout> layout, Filter filter,
+ FastFileManager manager, String filename, boolean handleException,
+ boolean immediateFlush, Advertiser advertiser) {
+ super(name, layout, filter, handleException, immediateFlush, manager);
+ if (advertiser != null) {
+ Map configuration = new HashMap(
+ layout.getContentFormat());
+ configuration.putAll(manager.getContentFormat());
+ configuration.put("contentType", layout.getContentType());
+ configuration.put("name", name);
+ advertisement = advertiser.advertise(configuration);
+ }
+ this.fileName = filename;
+ this.advertiser = advertiser;
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+ if (advertiser != null) {
+ advertiser.unadvertise(advertisement);
+ }
+ }
+
+ /**
+ * Write the log entry rolling over the file when required.
+ *
+ * @param event The LogEvent.
+ */
+ @Override
+ public void append(LogEvent event) {
+
+ // Leverage the nice batching behaviour of async Loggers/Appenders:
+ // we can signal the file manager that it needs to flush the buffer
+ // to disk at the end of a batch.
+ // From a user's point of view, this means that all log events are
+ // _always_ available in the log file, without incurring the overhead
+ // of immediateFlush=true.
+ //
+ // without LOG4J2-164:
+ // if (event.getClass() == RingBufferLogEvent.class) {
+ // boolean isEndOfBatch = ((RingBufferLogEvent) event).isEndOfBatch();
+ // ((FastFileManager) getManager()).setEndOfBatch(isEndOfBatch);
+ // }
+ ((FastFileManager) getManager()).setEndOfBatch(event.isEndOfBatch());
+ super.append(event);
+ }
+
+ /**
+ * Returns the file name this appender is associated with.
+ *
+ * @return The File name.
+ */
+ public String getFileName() {
+ return this.fileName;
+ }
+
+ // difference from standard File Appender:
+ // locking is not supported and buffering cannot be switched off
+ /**
+ * Create a File Appender.
+ *
+ * @param fileName The name and path of the file.
+ * @param append "True" if the file should be appended to, "false" if it
+ * should be overwritten. The default is "true".
+ * @param name The name of the Appender.
+ * @param immediateFlush "true" if the contents should be flushed on every
+ * write, "false" otherwise. The default is "true".
+ * @param suppress "true" if exceptions should be hidden from the
+ * application, "false" otherwise. The default is "true".
+ * @param layout The layout to use to format the event. If no layout is
+ * provided the default PatternLayout will be used.
+ * @param filter The filter, if any, to use.
+ * @param advertise "true" if the appender configuration should be
+ * advertised, "false" otherwise.
+ * @param advertiseURI The advertised URI which can be used to retrieve the
+ * file contents.
+ * @param config The Configuration.
+ * @return The FileAppender.
+ */
+ @PluginFactory
+ public static FastFileAppender createAppender(
+ @PluginAttr("fileName") String fileName,
+ @PluginAttr("append") String append,
+ @PluginAttr("name") String name,
+ @PluginAttr("immediateFlush") String immediateFlush,
+ @PluginAttr("suppressExceptions") String suppress,
+ @PluginElement("layout") Layout> layout,
+ @PluginElement("filters") final Filter filter,
+ @PluginAttr("advertise") final String advertise,
+ @PluginAttr("advertiseURI") final String advertiseURI,
+ @PluginConfiguration final Configuration config) {
+
+ boolean isAppend = append == null ? true : Boolean.valueOf(append);
+ boolean isFlush = immediateFlush == null ? true : Boolean.valueOf(immediateFlush);
+ boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress);
+ boolean isAdvertise = advertise == null ? false : Boolean.valueOf(advertise);
+
+ if (name == null) {
+ LOGGER.error("No name provided for FileAppender");
+ return null;
+ }
+
+ if (fileName == null) {
+ LOGGER.error("No filename provided for FileAppender with name "
+ + name);
+ return null;
+ }
+
+ FastFileManager manager = FastFileManager.getFileManager(fileName,
+ isAppend, isFlush, advertiseURI);
+ if (manager == null) {
+ return null;
+ }
+ if (layout == null) {
+ layout = PatternLayout.createLayout(null, null, null, null);
+ }
+ return new FastFileAppender(name, layout, filter, manager, fileName,
+ handleExceptions, isFlush, isAdvertise ? config.getAdvertiser() : null);
+ }
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastFileManager.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastFileManager.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastFileManager.java (working copy)
@@ -0,0 +1,195 @@
+/*
+ * 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.logging.log4j.async.appender;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.logging.log4j.core.appender.AppenderRuntimeException;
+import org.apache.logging.log4j.core.appender.ManagerFactory;
+import org.apache.logging.log4j.core.appender.OutputStreamManager;
+
+/**
+ * Manages actual File I/O for File Appenders.
+ */
+public class FastFileManager extends OutputStreamManager {
+
+ private static final FastFileManagerFactory factory = new FastFileManagerFactory();
+
+ private final boolean isImmediateFlush;
+ private final String advertiseURI;
+ private final RandomAccessFile randomAccessFile;
+ private final ByteBuffer buffer;
+ private ThreadLocal isEndOfBatch = new ThreadLocal();
+
+ protected FastFileManager(RandomAccessFile file, String fileName,
+ OutputStream os, boolean immediateFlush, String advertiseURI) {
+ super(os, fileName);
+ this.isImmediateFlush = immediateFlush;
+ this.randomAccessFile = file;
+ this.advertiseURI = advertiseURI;
+ isEndOfBatch.set(Boolean.FALSE);
+ buffer = ByteBuffer.allocate(256 * 1024); // TODO make configurable?
+ }
+
+ /**
+ * Returns the FastFileManager.
+ *
+ * @param fileName
+ * The name of the file to manage.
+ * @param append
+ * true if the file should be appended to, false if it should be
+ * overwritten.
+ * @param isFlush
+ * true if the contents should be flushed to disk on every write
+ * @param advertiseURI the URI to use when advertising the file
+ * @return A FastFileManager for the File.
+ */
+ public static FastFileManager getFileManager(String fileName,
+ boolean append, boolean isFlush, String advertiseURI) {
+ return (FastFileManager) getManager(fileName, new FactoryData(append,
+ isFlush, advertiseURI), factory);
+ }
+
+ public Boolean isEndOfBatch() {
+ return isEndOfBatch.get();
+ }
+
+ public void setEndOfBatch(boolean isEndOfBatch) {
+ this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch));
+ }
+
+ @Override
+ protected synchronized void write(byte[] bytes, int offset, int length) {
+ super.write(bytes, offset, length); // writes to dummy output stream
+
+ if (length > buffer.remaining()) {
+ flush();
+ }
+ buffer.put(bytes, offset, length);
+ if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) {
+ flush();
+ }
+ }
+
+ @Override
+ public void flush() {
+ buffer.flip();
+ try {
+ randomAccessFile.write(buffer.array(), 0, buffer.limit());
+ } catch (IOException ex) {
+ String msg = "Error writing to RandomAccessFile " + getName();
+ throw new AppenderRuntimeException(msg, ex);
+ }
+ buffer.clear();
+ }
+
+ /**
+ * Returns the name of the File being managed.
+ *
+ * @return The name of the File being managed.
+ */
+ public String getFileName() {
+ return getName();
+ }
+
+ private static class DummyOutputStream extends OutputStream {
+ @Override
+ public void write(int b) throws IOException {
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ }
+ }
+
+ /**
+ * FileManager's content format is specified by:
+ * Key: "fileURI" Value: provided "advertiseURI" param
+ * @return Map of content format keys supporting FileManager
+ */
+ public Map getContentFormat() {
+ Map result = new HashMap(super.getContentFormat());
+ result.put("fileURI", advertiseURI);
+ return result;
+ }
+
+ /**
+ * Factory Data.
+ */
+ private static class FactoryData {
+ private final boolean append;
+ private final boolean immediateFlush;
+ private final String advertiseURI;
+
+ /**
+ * Constructor.
+ *
+ * @param append
+ * Append status.
+ */
+ public FactoryData(boolean append, boolean immediateFlush, String advertiseURI) {
+ this.append = append;
+ this.immediateFlush = immediateFlush;
+ this.advertiseURI = advertiseURI;
+ }
+ }
+
+ /**
+ * Factory to create a FastFileManager.
+ */
+ private static class FastFileManagerFactory implements
+ ManagerFactory {
+
+ /**
+ * Create a FastFileManager.
+ *
+ * @param name
+ * The name of the File.
+ * @param data
+ * The FactoryData
+ * @return The FastFileManager for the File.
+ */
+ public FastFileManager createManager(String name, FactoryData data) {
+ File file = new File(name);
+ final File parent = file.getParentFile();
+ if (null != parent && !parent.exists()) {
+ parent.mkdirs();
+ }
+ if (!data.append) {
+ file.delete();
+ }
+
+ OutputStream os = new DummyOutputStream();
+ RandomAccessFile raf;
+ try {
+ raf = new RandomAccessFile(name, "rw");
+ return new FastFileManager(raf, name, os, data.immediateFlush,
+ data.advertiseURI);
+ } catch (Exception ex) {
+ LOGGER.error("FastFileManager (" + name + ") " + ex);
+ }
+ return null;
+ }
+ }
+
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastRollingFileAppender.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastRollingFileAppender.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastRollingFileAppender.java (working copy)
@@ -0,0 +1,193 @@
+/*
+ * 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.logging.log4j.async.appender;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender;
+import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
+import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
+import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
+import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttr;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.net.Advertiser;
+
+/**
+ * An appender that writes to random access files and can roll over at
+ * intervals.
+ */
+@Plugin(name = "FastRollingFile", type = "Core", elementType = "appender", printObject = true)
+public final class FastRollingFileAppender extends AbstractOutputStreamAppender {
+
+ private final String fileName;
+ private final String filePattern;
+ private Object advertisement;
+ private final Advertiser advertiser;
+
+ private FastRollingFileAppender(String name, Layout> layout, Filter filter,
+ RollingFileManager manager, String fileName,
+ String filePattern, boolean handleException,
+ boolean immediateFlush, Advertiser advertiser) {
+ super(name, layout, filter, handleException, immediateFlush, manager);
+ if (advertiser != null) {
+ Map configuration = new HashMap(layout.getContentFormat());
+ configuration.put("contentType", layout.getContentType());
+ configuration.put("name", name);
+ advertisement = advertiser.advertise(configuration);
+ }
+ this.fileName = fileName;
+ this.filePattern = filePattern;
+ this.advertiser = advertiser;
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+ if (advertiser != null) {
+ advertiser.unadvertise(advertisement);
+ }
+ }
+
+ /**
+ * Write the log entry rolling over the file when required.
+
+ * @param event The LogEvent.
+ */
+ @Override
+ public void append(final LogEvent event) {
+ ((RollingFileManager) getManager()).checkRollover(event);
+
+ // Leverage the nice batching behaviour of async Loggers/Appenders:
+ // we can signal the file manager that it needs to flush the buffer
+ // to disk at the end of a batch.
+ // From a user's point of view, this means that all log events are
+ // _always_ available in the log file, without incurring the overhead
+ // of immediateFlush=true.
+ //
+ // without LOG4J2-164:
+ // if (event.getClass() == RingBufferLogEvent.class) {
+ // boolean isEndOfBatch = ((RingBufferLogEvent) event).isEndOfBatch();
+ // ((FastRollingFileManager) getManager()).setEndOfBatch(isEndOfBatch);
+ // }
+ ((FastRollingFileManager) getManager()).setEndOfBatch(event.isEndOfBatch());
+ super.append(event);
+ }
+
+ /**
+ * Returns the File name for the Appender.
+ * @return The file name.
+ */
+ public String getFileName() {
+ return fileName;
+ }
+
+ /**
+ * Returns the file pattern used when rolling over.
+ * @return The file pattern.
+ */
+ public String getFilePattern() {
+ return filePattern;
+ }
+
+ /**
+ * Create a FastRollingFileAppender.
+ * @param fileName The name of the file that is actively written to. (required).
+ * @param filePattern The pattern of the file name to use on rollover. (required).
+ * @param append If true, events are appended to the file. If false, the file
+ * is overwritten when opened. Defaults to "true"
+ * @param name The name of the Appender (required).
+ * @param immediateFlush When true, events are immediately flushed. Defaults to "true".
+ * @param policy The triggering policy. (required).
+ * @param strategy The rollover strategy. Defaults to DefaultRolloverStrategy.
+ * @param layout The layout to use (defaults to the default PatternLayout).
+ * @param filter The Filter or null.
+ * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise.
+ * The default is "true".
+ * @param advertise "true" if the appender configuration should be advertised, "false" otherwise.
+ * @param advertiseURI The advertised URI which can be used to retrieve the file contents.
+ * @param config The Configuration.
+ * @return A FastRollingFileAppender.
+ */
+ @PluginFactory
+ public static FastRollingFileAppender createAppender(@PluginAttr("fileName") final String fileName,
+ @PluginAttr("filePattern") final String filePattern,
+ @PluginAttr("append") final String append,
+ @PluginAttr("name") final String name,
+ @PluginAttr("immediateFlush") final String immediateFlush,
+ @PluginElement("policy") final TriggeringPolicy policy,
+ @PluginElement("strategy") RolloverStrategy strategy,
+ @PluginElement("layout") Layout> layout,
+ @PluginElement("filter") final Filter filter,
+ @PluginAttr("suppressExceptions") final String suppress,
+ @PluginAttr("advertise") final String advertise,
+ @PluginAttr("advertiseURI") final String advertiseURI,
+ @PluginConfiguration final Configuration config) {
+
+ final boolean isAppend = append == null ? true : Boolean.valueOf(append);
+ final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress);
+ final boolean isFlush = immediateFlush == null ? true : Boolean.valueOf(immediateFlush);
+ boolean isAdvertise = advertise == null ? false : Boolean.valueOf(advertise);
+
+ if (name == null) {
+ LOGGER.error("No name provided for FileAppender");
+ return null;
+ }
+
+ if (fileName == null) {
+ LOGGER.error("No filename was provided for FileAppender with name " + name);
+ return null;
+ }
+
+ if (filePattern == null) {
+ LOGGER.error("No filename pattern provided for FileAppender with name " + name);
+ return null;
+ }
+
+ if (policy == null) {
+ LOGGER.error("A TriggeringPolicy must be provided");
+ return null;
+ }
+
+ if (strategy == null) {
+ strategy = DefaultRolloverStrategy.createStrategy(null, null, "true", config);
+ }
+
+ final FastRollingFileManager manager = FastRollingFileManager
+ .getFastRollingFileManager(fileName, filePattern, isAppend,
+ isFlush, policy, strategy, advertiseURI);
+ if (manager == null) {
+ return null;
+ }
+
+ if (layout == null) {
+ layout = PatternLayout.createLayout(null, null, null, null);
+ }
+
+ return new FastRollingFileAppender(name, layout, filter, manager, fileName, filePattern,
+ handleExceptions, isFlush, isAdvertise ? config.getAdvertiser() : null);
+ }
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastRollingFileManager.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastRollingFileManager.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastRollingFileManager.java (working copy)
@@ -0,0 +1,177 @@
+/*
+ * 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.logging.log4j.async.appender;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+
+import org.apache.logging.log4j.core.appender.AppenderRuntimeException;
+import org.apache.logging.log4j.core.appender.ManagerFactory;
+import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
+import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
+import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
+
+public class FastRollingFileManager extends RollingFileManager {
+ private static FastRollingFileManagerFactory factory = new FastRollingFileManagerFactory();
+
+ private final boolean isImmediateFlush;
+ private final RandomAccessFile randomAccessFile;
+ private final ByteBuffer buffer;
+ private ThreadLocal isEndOfBatch = new ThreadLocal();
+
+ public FastRollingFileManager(RandomAccessFile raf, String fileName,
+ String pattern, OutputStream os, boolean append,
+ boolean immediateFlush, long size, long time, TriggeringPolicy policy,
+ RolloverStrategy strategy, String advertiseURI) {
+ super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI);
+ this.isImmediateFlush = immediateFlush;
+ this.randomAccessFile = raf;
+ isEndOfBatch.set(Boolean.FALSE);
+ buffer = ByteBuffer.allocate(256 * 1024); // TODO make configurable?
+ }
+
+ public static FastRollingFileManager getFastRollingFileManager(
+ String fileName, String filePattern, boolean isAppend,
+ boolean immediateFlush, TriggeringPolicy policy,
+ RolloverStrategy strategy, String advertiseURI) {
+ return (FastRollingFileManager) getManager(fileName, new FactoryData(
+ filePattern, isAppend, immediateFlush, policy, strategy,
+ advertiseURI), factory);
+ }
+
+ public Boolean isEndOfBatch() {
+ return isEndOfBatch.get();
+ }
+
+ public void setEndOfBatch(boolean isEndOfBatch) {
+ this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch));
+ }
+
+ @Override
+ protected synchronized void write(byte[] bytes, int offset, int length) {
+ super.write(bytes, offset, length); // writes to dummy output stream
+
+ if (length > buffer.remaining()) {
+ flush();
+ }
+ buffer.put(bytes, offset, length);
+ if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) {
+ flush();
+ }
+ }
+
+ @Override
+ public void flush() {
+ buffer.flip();
+ try {
+ randomAccessFile.write(buffer.array(), 0, buffer.limit());
+ } catch (IOException ex) {
+ String msg = "Error writing to RandomAccessFile " + getName();
+ throw new AppenderRuntimeException(msg, ex);
+ }
+ buffer.clear();
+ }
+
+ /**
+ * Factory to create a FastRollingFileManager.
+ */
+ private static class FastRollingFileManagerFactory implements
+ ManagerFactory {
+
+ /**
+ * Create the FastRollingFileManager.
+ *
+ * @param name
+ * The name of the entity to manage.
+ * @param data
+ * The data required to create the entity.
+ * @return a RollingFileManager.
+ */
+ public FastRollingFileManager createManager(String name,
+ FactoryData data) {
+ File file = new File(name);
+ final File parent = file.getParentFile();
+ if (null != parent && !parent.exists()) {
+ parent.mkdirs();
+ }
+ if (!data.append) {
+ file.delete();
+ }
+ long size = data.append ? file.length() : 0;
+ long time = file.lastModified();
+
+ RandomAccessFile raf;
+ try {
+ raf = new RandomAccessFile(name, "rw");
+ return new FastRollingFileManager(raf, name, data.pattern,
+ new DummyOutputStream(), data.append,
+ data.immediateFlush, size, time, data.policy,
+ data.strategy, data.advertiseURI);
+ } catch (FileNotFoundException ex) {
+ LOGGER.error("FastRollingFileManager (" + name + ") " + ex);
+ }
+ return null;
+ }
+ }
+
+ private static class DummyOutputStream extends OutputStream {
+ @Override
+ public void write(int b) throws IOException {
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ }
+ }
+
+ /**
+ * Factory data.
+ */
+ private static class FactoryData {
+ private final String pattern;
+ private final boolean append;
+ private final boolean immediateFlush;
+ private final TriggeringPolicy policy;
+ private final RolloverStrategy strategy;
+ private final String advertiseURI;
+
+ /**
+ * Create the data for the factory.
+ *
+ * @param pattern
+ * The pattern.
+ * @param append
+ * The append flag.
+ * @param immediateFlush
+ */
+ public FactoryData(String pattern, boolean append,
+ boolean immediateFlush, TriggeringPolicy policy,
+ RolloverStrategy strategy, String advertiseURI) {
+ this.pattern = pattern;
+ this.append = append;
+ this.immediateFlush = immediateFlush;
+ this.policy = policy;
+ this.strategy = strategy;
+ this.advertiseURI = advertiseURI;
+ }
+ }
+
+}
Index: log4j-async/src/main/java/org/apache/logging/log4j/async/config/AsyncLoggerConfig.java
===================================================================
--- log4j-async/src/main/java/org/apache/logging/log4j/async/config/AsyncLoggerConfig.java (revision 0)
+++ log4j-async/src/main/java/org/apache/logging/log4j/async/config/AsyncLoggerConfig.java (working copy)
@@ -0,0 +1,354 @@
+/*
+ * 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.logging.log4j.async.config;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.LoggerContext.Status;
+import org.apache.logging.log4j.core.config.AppenderRef;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttr;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import com.lmax.disruptor.BlockingWaitStrategy;
+import com.lmax.disruptor.EventFactory;
+import com.lmax.disruptor.EventHandler;
+import com.lmax.disruptor.EventTranslator;
+import com.lmax.disruptor.ExceptionHandler;
+import com.lmax.disruptor.RingBuffer;
+import com.lmax.disruptor.SleepingWaitStrategy;
+import com.lmax.disruptor.WaitStrategy;
+import com.lmax.disruptor.YieldingWaitStrategy;
+import com.lmax.disruptor.dsl.Disruptor;
+import com.lmax.disruptor.dsl.ProducerType;
+import com.lmax.disruptor.util.Util;
+
+/**
+ * Asynchronous Logger object that is created via configuration.
+ */
+@Plugin(name = "asyncLogger", type = "Core", printObject = true)
+public class AsyncLoggerConfig extends LoggerConfig {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static volatile Disruptor disruptor;
+ private static ExecutorService executor = Executors
+ .newSingleThreadExecutor();
+
+ private ThreadLocal currentLogEvent = new ThreadLocal();
+
+ /**
+ * RingBuffer events contain all information necessary to perform the work
+ * in a separate thread.
+ */
+ private static class RingBufferLog4jEvent {
+ private AsyncLoggerConfig loggerConfig;
+ private LogEvent event;
+ }
+
+ /**
+ * Factory used to populate the RingBuffer with events. These event objects
+ * are then re-used during the life of the RingBuffer.
+ */
+ private static final EventFactory FACTORY = new EventFactory() {
+ @Override
+ public RingBufferLog4jEvent newInstance() {
+ return new RingBufferLog4jEvent();
+ }
+ };
+
+ /**
+ * Object responsible for passing on data to a specific RingBuffer event.
+ */
+ private final EventTranslator translator = new EventTranslator() {
+ @Override
+ public void translateTo(RingBufferLog4jEvent event, long sequence) {
+ event.event = currentLogEvent.get();
+ event.loggerConfig = AsyncLoggerConfig.this;
+ }
+ };
+
+ /**
+ * EventHandler performs the work in a separate thread.
+ */
+ private static class RingBufferLog4jEventHandler implements
+ EventHandler {
+ @Override
+ public void onEvent(RingBufferLog4jEvent event, long sequence,
+ boolean endOfBatch) throws Exception {
+ event.loggerConfig.asyncCallAppenders(event.event, endOfBatch);
+ }
+ }
+
+ /**
+ * Default constructor.
+ */
+ public AsyncLoggerConfig() {
+ super();
+ }
+
+ /**
+ * Constructor that sets the name, level and additive values.
+ *
+ * @param name The Logger name.
+ * @param level The Level.
+ * @param additive true if the Logger is additive, false otherwise.
+ */
+ public AsyncLoggerConfig(final String name, final Level level,
+ final boolean additive) {
+ super(name, level, additive);
+ }
+
+ protected AsyncLoggerConfig(final String name,
+ final List appenders, final Filter filter,
+ final Level level, final boolean additive,
+ final Property[] properties, final Configuration config,
+ final boolean includeLocation) {
+ super(name, appenders, filter, level, additive, properties, config,
+ includeLocation);
+ }
+
+ /**
+ * Passes on the event to a separate thread that will call
+ * {@link #asyncCallAppenders(LogEvent)}.
+ */
+ @Override
+ protected void callAppenders(LogEvent event) {
+ // populate lazily initialized fields
+ event.getSource();
+ event.getThreadName();
+
+ // pass on the event to a separate thread
+ currentLogEvent.set(event);
+ disruptor.publishEvent(translator);
+ }
+
+ /** Called by RingBufferLog4jEventHandler. */
+ private void asyncCallAppenders(LogEvent event, boolean endOfBatch) {
+ event.setEndOfBatch(endOfBatch);
+ super.callAppenders(event);
+ }
+
+ @Override
+ public void startFilter() {
+ if (disruptor == null) {
+ int ringBufferSize = calculateRingBufferSize();
+ WaitStrategy waitStrategy = createWaitStrategy();
+ disruptor = new Disruptor(FACTORY,
+ ringBufferSize, executor, ProducerType.MULTI, waitStrategy);
+ EventHandler[] handlers = new RingBufferLog4jEventHandler[] { new RingBufferLog4jEventHandler() };
+ disruptor.handleExceptionsWith(getExceptionHandler());
+ disruptor.handleEventsWith(handlers);
+
+ LOGGER.debug(
+ "Starting AsyncLoggerConfig disruptor with ringbuffer size {}...",
+ disruptor.getRingBuffer().getBufferSize());
+ disruptor.start();
+ }
+ super.startFilter();
+ }
+
+ private WaitStrategy createWaitStrategy() {
+ String strategy = System.getProperty("AsyncLoggerConfig.WaitStrategy");
+ LOGGER.debug("property AsyncLoggerConfig.WaitStrategy={}", strategy);
+ if ("Sleep".equals(strategy)) {
+ LOGGER.debug("disruptor event handler uses SleepingWaitStrategy");
+ return new SleepingWaitStrategy();
+ } else if ("Yield".equals(strategy)) {
+ LOGGER.debug("disruptor event handler uses YieldingWaitStrategy");
+ return new YieldingWaitStrategy();
+ } else if ("Block".equals(strategy)) {
+ LOGGER.debug("disruptor event handler uses BlockingWaitStrategy");
+ return new BlockingWaitStrategy();
+ }
+ LOGGER.debug("disruptor event handler uses SleepingWaitStrategy");
+ return new SleepingWaitStrategy();
+ }
+
+ private static int calculateRingBufferSize() {
+ String userPreferredRBSize = System.getProperty(
+ "AsyncLoggerConfig.RingBufferSize", "256000");
+ int ringBufferSize = 256000; // default
+ try {
+ int size = Integer.parseInt(userPreferredRBSize);
+ if (size < 128) {
+ size = 128;
+ LOGGER.warn(
+ "Invalid RingBufferSize {}, using minimum size 128.",
+ userPreferredRBSize);
+ }
+ ringBufferSize = size;
+ } catch (Exception ex) {
+ LOGGER.warn("Invalid RingBufferSize {}, using default size.",
+ userPreferredRBSize);
+ }
+ return Util.ceilingNextPowerOfTwo(ringBufferSize);
+ }
+
+ private static ExceptionHandler getExceptionHandler() {
+ String cls = System.getProperty("AsyncLoggerConfig.ExceptionHandler");
+ if (cls == null) {
+ LOGGER.debug("No AsyncLoggerConfig.ExceptionHandler specified");
+ return null;
+ }
+ try {
+ @SuppressWarnings("unchecked")
+ Class extends ExceptionHandler> klass = (Class extends ExceptionHandler>) Class
+ .forName(cls);
+ ExceptionHandler result = klass.newInstance();
+ LOGGER.debug("AsyncLoggerConfig.ExceptionHandler=" + result);
+ return result;
+ } catch (Exception ignored) {
+ LOGGER.debug(
+ "AsyncLoggerConfig.ExceptionHandler not set: error creating "
+ + cls + ": ", ignored);
+ return null;
+ }
+ }
+
+ @Override
+ public void stopFilter() {
+ // only stop disruptor if shutting down logging subsystem
+ if (LogManager.getContext() instanceof LoggerContext) {
+ if (((LoggerContext) LogManager.getContext()).getStatus() != Status.STOPPING) {
+ return;
+ }
+ }
+ Disruptor temp = disruptor;
+
+ // Must guarantee that publishing to the RingBuffer has stopped
+ // before we call disruptor.shutdown()
+ disruptor = null; // client code fails with NPE if log after stop = OK
+ temp.shutdown();
+
+ // wait up to 10 seconds for the ringbuffer to drain
+ RingBuffer ringBuffer = temp.getRingBuffer();
+ for (int i = 0; i < 20; i++) {
+ if (ringBuffer.hasAvailableCapacity(ringBuffer.getBufferSize())) {
+ break;
+ }
+ try {
+ Thread.sleep(500); // give ringbuffer some time to drain...
+ } catch (InterruptedException e) {
+ }
+ }
+ executor.shutdown(); // finally, kill the processor thread
+ super.stopFilter();
+ }
+
+ /**
+ * Factory method to create a LoggerConfig.
+ *
+ * @param additivity True if additive, false otherwise.
+ * @param levelName The Level to be associated with the Logger.
+ * @param loggerName The name of the Logger.
+ * @param includeLocation "true" if location should be passed downstream
+ * @param refs An array of Appender names.
+ * @param properties Properties to pass to the Logger.
+ * @param config The Configuration.
+ * @param filter A Filter.
+ * @return A new LoggerConfig.
+ */
+ @PluginFactory
+ public static LoggerConfig createLogger(
+ @PluginAttr("additivity") final String additivity,
+ @PluginAttr("level") final String levelName,
+ @PluginAttr("name") final String loggerName,
+ @PluginAttr("includeLocation") final String includeLocation,
+ @PluginElement("appender-ref") final AppenderRef[] refs,
+ @PluginElement("properties") final Property[] properties,
+ @PluginConfiguration final Configuration config,
+ @PluginElement("filters") final Filter filter) {
+ if (loggerName == null) {
+ LOGGER.error("Loggers cannot be configured without a name");
+ return null;
+ }
+
+ final List appenderRefs = Arrays.asList(refs);
+ Level level;
+ try {
+ level = Level.toLevel(levelName, Level.ERROR);
+ } catch (final Exception ex) {
+ LOGGER.error(
+ "Invalid Log level specified: {}. Defaulting to Error",
+ levelName);
+ level = Level.ERROR;
+ }
+ final String name = loggerName.equals("root") ? "" : loggerName;
+ final boolean additive = additivity == null ? true : Boolean
+ .parseBoolean(additivity);
+
+ return new AsyncLoggerConfig(name, appenderRefs, filter, level,
+ additive, properties, config, includeLocation(includeLocation));
+ }
+
+ // Note: for asynchronous loggers, includeLocation default is FALSE
+ private static boolean includeLocation(String includeLocationConfigValue) {
+ if (includeLocationConfigValue == null) {
+ return false;
+ }
+ return Boolean.parseBoolean(includeLocationConfigValue);
+ }
+
+ /**
+ * An asynchronous root Logger.
+ */
+ @Plugin(name = "asyncRoot", type = "Core", printObject = true)
+ public static class RootLogger extends LoggerConfig {
+
+ @PluginFactory
+ public static LoggerConfig createLogger(
+ @PluginAttr("additivity") final String additivity,
+ @PluginAttr("level") final String levelName,
+ @PluginAttr("includeLocation") final String includeLocation,
+ @PluginElement("appender-ref") final AppenderRef[] refs,
+ @PluginElement("properties") final Property[] properties,
+ @PluginConfiguration final Configuration config,
+ @PluginElement("filters") final Filter filter) {
+ final List appenderRefs = Arrays.asList(refs);
+ Level level;
+ try {
+ level = Level.toLevel(levelName, Level.ERROR);
+ } catch (final Exception ex) {
+ LOGGER.error(
+ "Invalid Log level specified: {}. Defaulting to Error",
+ levelName);
+ level = Level.ERROR;
+ }
+ final boolean additive = additivity == null ? true : Boolean
+ .parseBoolean(additivity);
+
+ return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME,
+ appenderRefs, filter, level, additive, properties, config,
+ includeLocation(includeLocation));
+ }
+ }
+}
Index: log4j-async/src/site/site.xml
===================================================================
--- log4j-async/src/site/site.xml (revision 0)
+++ log4j-async/src/site/site.xml (working copy)
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
Index: log4j-async/src/site/xdoc/index.xml
===================================================================
--- log4j-async/src/site/xdoc/index.xml (revision 0)
+++ log4j-async/src/site/xdoc/index.xml (working copy)
@@ -0,0 +1,47 @@
+
+
+
+
+ Asynchronous Logging
+ Remko Popma
+
+
+
+
+ Log4j-async uses LMAX Disruptor
+ technology to significantly improve the performance of
+ asynchronous logging, especially in multi-threaded scenarios.
+
+
+
+
+
+ Log4j-async requires at least Java 6 and is dependent on the Log4j 2
+ API and implementation, as well as the LMAX disruptor library.
+
+
+
+
+
+ Include this jar, the disruptor-3.0.0 jar, the Log4j 2 API and
+ implementation jar together. Configure
+ the logging implementation as required.
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerContextSelectorTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerContextSelectorTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerContextSelectorTest.java (working copy)
@@ -0,0 +1,37 @@
+package org.apache.logging.log4j.async;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import org.apache.logging.log4j.core.LoggerContext;
+import org.junit.Test;
+
+public class AsyncLoggerContextSelectorTest {
+
+ @Test
+ public void testContextReturnsAsyncLoggerContext() {
+ AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector();
+ LoggerContext context = selector.getContext(null, null, false);
+
+ assertTrue(context instanceof AsyncLoggerContext);
+ }
+
+ @Test
+ public void testContext2ReturnsAsyncLoggerContext() {
+ AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector();
+ LoggerContext context = selector.getContext(null, null, false, null);
+
+ assertTrue(context instanceof AsyncLoggerContext);
+ }
+
+ @Test
+ public void testLoggerContextsReturnsAsyncLoggerContext() {
+ AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector();
+ List list = selector.getLoggerContexts();
+
+ assertEquals(1, list.size());
+ assertTrue(list.get(0) instanceof AsyncLoggerContext);
+ }
+
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerContextTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerContextTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerContextTest.java (working copy)
@@ -0,0 +1,21 @@
+package org.apache.logging.log4j.async;
+
+import static org.junit.Assert.*;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.junit.Test;
+
+public class AsyncLoggerContextTest {
+
+ @Test
+ public void testNewInstanceReturnsAsyncLogger() {
+ Logger logger = new AsyncLoggerContext("a").newInstance(
+ new LoggerContext("a"), "a", null);
+ assertTrue(logger instanceof AsyncLogger);
+
+ ((LifeCycle) LogManager.getContext()).stop(); // stop async thread
+ }
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerLocationTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerLocationTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerLocationTest.java (working copy)
@@ -0,0 +1,54 @@
+package org.apache.logging.log4j.async;
+
+import static org.junit.Assert.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.config.XMLConfigurationFactory;
+import org.apache.logging.log4j.core.helpers.Constants;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class AsyncLoggerLocationTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR,
+ AsyncLoggerContextSelector.class.getName());
+ System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+ "AsyncLoggerLocationTest.xml");
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, "");
+ }
+
+ @Test
+ public void testAsyncLogWritesToLog() throws Exception {
+ File f = new File("AsyncLoggerLocationTest.log");
+ // System.out.println(f.getAbsolutePath());
+ f.delete();
+ Logger log = LogManager.getLogger("com.foo.Bar");
+ String msg = "Async logger msg with location";
+ log.info(msg);
+ ((LifeCycle) LogManager.getContext()).stop(); // stop async thread
+
+ BufferedReader reader = new BufferedReader(new FileReader(f));
+ String line1 = reader.readLine();
+ reader.close();
+ f.delete();
+ assertNotNull("line1", line1);
+ assertTrue("line1 correct", line1.contains(msg));
+
+ String location = "testAsyncLogWritesToLog";
+ assertTrue("has location", line1.contains(location));
+ }
+
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerTest.java (working copy)
@@ -0,0 +1,54 @@
+package org.apache.logging.log4j.async;
+
+import static org.junit.Assert.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.config.XMLConfigurationFactory;
+import org.apache.logging.log4j.core.helpers.Constants;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class AsyncLoggerTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR,
+ AsyncLoggerContextSelector.class.getName());
+ System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+ "AsyncLoggerTest.xml");
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, "");
+ }
+
+ @Test
+ public void testAsyncLogWritesToLog() throws Exception {
+ File f = new File("AsyncLoggerTest.log");
+ // System.out.println(f.getAbsolutePath());
+ f.delete();
+ Logger log = LogManager.getLogger("com.foo.Bar");
+ String msg = "Async logger msg";
+ log.info(msg);
+ ((LifeCycle) LogManager.getContext()).stop(); // stop async thread
+
+ BufferedReader reader = new BufferedReader(new FileReader(f));
+ String line1 = reader.readLine();
+ reader.close();
+ f.delete();
+ assertNotNull("line1", line1);
+ assertTrue("line1 correct", line1.contains(msg));
+
+ String location = "testAsyncLogWritesToLog";
+ assertTrue("no location", !line1.contains(location));
+ }
+
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/CachedClockTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/CachedClockTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/CachedClockTest.java (working copy)
@@ -0,0 +1,30 @@
+package org.apache.logging.log4j.async;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class CachedClockTest {
+
+ @Test
+ public void testLessThan17Millis() {
+ long millis1 = CachedClock.instance().currentTimeMillis();
+ long sysMillis = System.currentTimeMillis();
+
+ long diff = sysMillis - millis1;
+
+ assertTrue("diff too large: " + diff, diff <= 16);
+ }
+
+ @Test
+ public void testAfterWaitStillLessThan17Millis() throws Exception {
+ Thread.sleep(100);
+ long millis1 = CachedClock.instance().currentTimeMillis();
+ long sysMillis = System.currentTimeMillis();
+
+ long diff = sysMillis - millis1;
+
+ assertTrue("diff too large: " + diff, diff <= 16);
+ }
+
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/ClockFactoryTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/ClockFactoryTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/ClockFactoryTest.java (working copy)
@@ -0,0 +1,64 @@
+package org.apache.logging.log4j.async;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class ClockFactoryTest {
+
+ @Test
+ public void testDefaultIsSystemClock() {
+ System.clearProperty(ClockFactory.PROPERTY_NAME);
+ assertEquals(SystemClock.class, ClockFactory.getClock().getClass());
+ }
+
+ @Test
+ public void testSpecifySystemClockShort() {
+ System.setProperty(ClockFactory.PROPERTY_NAME, "SystemClock");
+ assertEquals(SystemClock.class, ClockFactory.getClock().getClass());
+ }
+
+ @Test
+ public void testSpecifySystemClockLong() {
+ System.setProperty(ClockFactory.PROPERTY_NAME, SystemClock.class.getName());
+ assertEquals(SystemClock.class, ClockFactory.getClock().getClass());
+ }
+
+ @Test
+ public void testSpecifyCachedClockShort() {
+ System.setProperty(ClockFactory.PROPERTY_NAME, "CachedClock");
+ assertEquals(CachedClock.class, ClockFactory.getClock().getClass());
+ }
+
+ @Test
+ public void testSpecifyCachedClockLong() {
+ System.setProperty(ClockFactory.PROPERTY_NAME, CachedClock.class.getName());
+ assertEquals(CachedClock.class, ClockFactory.getClock().getClass());
+ }
+
+ @Test
+ public void testSpecifyCoarseCachedClockShort() {
+ System.setProperty(ClockFactory.PROPERTY_NAME, "CoarseCachedClock");
+ assertEquals(CoarseCachedClock.class, ClockFactory.getClock().getClass());
+ }
+
+ @Test
+ public void testSpecifyCoarseCachedClockLong() {
+ System.setProperty(ClockFactory.PROPERTY_NAME, CoarseCachedClock.class.getName());
+ assertEquals(CoarseCachedClock.class, ClockFactory.getClock().getClass());
+ }
+
+ static class MyClock implements Clock {
+ @Override
+ public long currentTimeMillis() {
+ return 42;
+ }
+ }
+
+ @Test
+ public void testCustomClock() {
+ System.setProperty(ClockFactory.PROPERTY_NAME, MyClock.class.getName());
+ assertEquals(MyClock.class, ClockFactory.getClock().getClass());
+ }
+
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/SystemClockTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/SystemClockTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/SystemClockTest.java (working copy)
@@ -0,0 +1,30 @@
+package org.apache.logging.log4j.async;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class SystemClockTest {
+
+ @Test
+ public void testLessThan2Millis() {
+ long millis1 = new SystemClock().currentTimeMillis();
+ long sysMillis = System.currentTimeMillis();
+
+ long diff = sysMillis - millis1;
+
+ assertTrue("diff too large: " + diff, diff <= 1);
+ }
+
+ @Test
+ public void testAfterWaitStillLessThan2Millis() throws Exception {
+ Thread.sleep(100);
+ long millis1 = new SystemClock().currentTimeMillis();
+ long sysMillis = System.currentTimeMillis();
+
+ long diff = sysMillis - millis1;
+
+ assertTrue("diff too large: " + diff, diff <= 1);
+ }
+
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastFileAppenderLocationTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastFileAppenderLocationTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastFileAppenderLocationTest.java (working copy)
@@ -0,0 +1,44 @@
+package org.apache.logging.log4j.async.appender;
+
+import static org.junit.Assert.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.config.XMLConfigurationFactory;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class FastFileAppenderLocationTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+ "FastFileAppenderLocationTest.xml");
+ }
+
+ @Test
+ public void testLocationIncluded() throws Exception {
+ File f = new File("FastFileAppenderLocationTest.log");
+ // System.out.println(f.getAbsolutePath());
+ f.delete();
+ Logger log = LogManager.getLogger("com.foo.Bar");
+ String msg = "Message with location, flushed with immediate flush=false";
+ log.info(msg);
+ ((LifeCycle) LogManager.getContext()).stop(); // stop async thread
+
+ BufferedReader reader = new BufferedReader(new FileReader(f));
+ String line1 = reader.readLine();
+ reader.close();
+ f.delete();
+ assertNotNull("line1", line1);
+ assertTrue("line1 correct", line1.contains(msg));
+
+ String location = "testLocationIncluded";
+ assertTrue("has location", line1.contains(location));
+ }
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastFileAppenderTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastFileAppenderTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastFileAppenderTest.java (working copy)
@@ -0,0 +1,44 @@
+package org.apache.logging.log4j.async.appender;
+
+import static org.junit.Assert.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.config.XMLConfigurationFactory;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class FastFileAppenderTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+ "FastFileAppenderTest.xml");
+ }
+
+ @Test
+ public void testFlushAtEndOfBatch() throws Exception {
+ File f = new File("FastFileAppenderTest.log");
+ // System.out.println(f.getAbsolutePath());
+ f.delete();
+ Logger log = LogManager.getLogger("com.foo.Bar");
+ String msg = "Message flushed with immediate flush=false";
+ log.info(msg);
+ ((LifeCycle) LogManager.getContext()).stop(); // stop async thread
+
+ BufferedReader reader = new BufferedReader(new FileReader(f));
+ String line1 = reader.readLine();
+ reader.close();
+ f.delete();
+ assertNotNull("line1", line1);
+ assertTrue("line1 correct", line1.contains(msg));
+
+ String location = "testFlushAtEndOfBatch";
+ assertTrue("no location", !line1.contains(location));
+ }
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastRollingFileAppenderLocationTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastRollingFileAppenderLocationTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastRollingFileAppenderLocationTest.java (working copy)
@@ -0,0 +1,44 @@
+package org.apache.logging.log4j.async.appender;
+
+import static org.junit.Assert.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.config.XMLConfigurationFactory;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class FastRollingFileAppenderLocationTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+ "FastRollingFileAppenderLocationTest.xml");
+ }
+
+ @Test
+ public void testLocationIncluded() throws Exception {
+ File f = new File("FastRollingFileAppenderLocationTest.log");
+ // System.out.println(f.getAbsolutePath());
+ f.delete();
+ Logger log = LogManager.getLogger("com.foo.Bar");
+ String msg = "Message with location, flushed with immediate flush=false";
+ log.info(msg);
+ ((LifeCycle) LogManager.getContext()).stop(); // stop async thread
+
+ BufferedReader reader = new BufferedReader(new FileReader(f));
+ String line1 = reader.readLine();
+ reader.close();
+ f.delete();
+ assertNotNull("line1", line1);
+ assertTrue("line1 correct", line1.contains(msg));
+
+ String location = "testLocationIncluded";
+ assertTrue("has location", line1.contains(location));
+ }
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastRollingFileAppenderTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastRollingFileAppenderTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastRollingFileAppenderTest.java (working copy)
@@ -0,0 +1,44 @@
+package org.apache.logging.log4j.async.appender;
+
+import static org.junit.Assert.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.config.XMLConfigurationFactory;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class FastRollingFileAppenderTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+ "FastRollingFileAppenderTest.xml");
+ }
+
+ @Test
+ public void testFlushAtEndOfBatch() throws Exception {
+ File f = new File("FastRollingFileAppenderTest.log");
+ // System.out.println(f.getAbsolutePath());
+ f.delete();
+ Logger log = LogManager.getLogger("com.foo.Bar");
+ String msg = "Message flushed with immediate flush=false";
+ log.info(msg);
+ ((LifeCycle) LogManager.getContext()).stop(); // stop async thread
+
+ BufferedReader reader = new BufferedReader(new FileReader(f));
+ String line1 = reader.readLine();
+ reader.close();
+ f.delete();
+ assertNotNull("line1", line1);
+ assertTrue("line1 correct", line1.contains(msg));
+
+ String location = "testFlushAtEndOfBatch";
+ assertTrue("no location", !line1.contains(location));
+ }
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/config/AsyncLoggerConfigTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/config/AsyncLoggerConfigTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/config/AsyncLoggerConfigTest.java (working copy)
@@ -0,0 +1,49 @@
+package org.apache.logging.log4j.async.config;
+
+import static org.junit.Assert.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.config.XMLConfigurationFactory;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class AsyncLoggerConfigTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+ "AsyncLoggerConfigTest.xml");
+ }
+
+ @Test
+ public void testAdditivity() throws Exception {
+ File f = new File("AsyncLoggerConfigTest.log");
+ // System.out.println(f.getAbsolutePath());
+ f.delete();
+ Logger log = LogManager.getLogger("com.foo.Bar");
+ String msg = "Additive logging: 2 for the price of 1!";
+ log.info(msg);
+ ((LifeCycle) LogManager.getContext()).stop(); // stop async thread
+
+ BufferedReader reader = new BufferedReader(new FileReader(f));
+ String line1 = reader.readLine();
+ String line2 = reader.readLine();
+ reader.close();
+ f.delete();
+ assertNotNull("line1", line1);
+ assertNotNull("line2", line2);
+ assertTrue("line1 correct", line1.contains(msg));
+ assertTrue("line2 correct", line2.contains(msg));
+
+ String location = "testAdditivity";
+ assertTrue("location",
+ line1.contains(location) || line2.contains(location));
+ }
+
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/IPerfTestRunner.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/IPerfTestRunner.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/IPerfTestRunner.java (working copy)
@@ -0,0 +1,17 @@
+package org.apache.logging.log4j.async.perftest;
+
+import com.lmax.disruptor.collections.Histogram;
+
+public interface IPerfTestRunner {
+ static final String LINE100 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!\"#$%&'()-=^~|\\@`[]{};:+*,.<>/?_123456";
+ static final String THROUGHPUT_MSG = LINE100 + LINE100 + LINE100 + LINE100
+ + LINE100;
+ static final String LATENCY_MSG = "Short msg";
+
+ void runThroughputTest(int lines, Histogram histogram);
+
+ void runLatencyTest(int samples, Histogram histogram, long nanoTimeCost,
+ int threadCount);
+ void shutdown();
+ void log(String finalMessage);
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/MTPerfTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/MTPerfTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/MTPerfTest.java (working copy)
@@ -0,0 +1,93 @@
+package org.apache.logging.log4j.async.perftest;
+
+import java.io.File;
+
+import com.lmax.disruptor.collections.Histogram;
+
+public class MTPerfTest extends PerfTest {
+
+ public static void main(String[] args) throws Exception {
+ new MTPerfTest().doMain(args);
+ }
+
+ @Override
+ public void runTestAndPrintResult(final IPerfTestRunner runner,
+ final String name, final int threadCount, String resultFile)
+ throws Exception {
+
+ // ThreadContext.put("aKey", "mdcVal");
+ PerfTest.println("Warming up the JVM...");
+ long t1 = System.nanoTime();
+
+ // warmup at least 2 rounds and at most 1 minute
+ final Histogram warmupHist = PerfTest.createHistogram();
+ final long stop = System.currentTimeMillis() + (60 * 1000);
+ Runnable run1 = new Runnable() {
+ public void run() {
+ for (int i = 0; i < 10; i++) {
+ final int LINES = PerfTest.throughput ? 50000 : 200000;
+ runTest(runner, LINES, null, warmupHist, 2);
+ if (i > 0 && System.currentTimeMillis() >= stop) {
+ return;
+ }
+ }
+ }
+ };
+ Thread thread1 = new Thread(run1);
+ Thread thread2 = new Thread(run1);
+ thread1.start();
+ thread2.start();
+ thread1.join();
+ thread2.join();
+
+ PerfTest.printf("Warmup complete in %.1f seconds%n",
+ (System.nanoTime() - t1) / (1000.0 * 1000.0 * 1000.0));
+ PerfTest.println("Waiting 10 seconds for buffers to drain warmup data...");
+ Thread.sleep(10000);
+ new File("perftest.log").delete();
+ new File("perftest.log").createNewFile();
+
+ PerfTest.println("Starting the main test...");
+ PerfTest.throughput = false;
+ multiThreadedTestRun(runner, name, threadCount, resultFile);
+
+ Thread.sleep(1000);
+ PerfTest.throughput = true;
+ multiThreadedTestRun(runner, name, threadCount, resultFile);
+ }
+
+ private void multiThreadedTestRun(final IPerfTestRunner runner,
+ final String name, final int threadCount, String resultFile)
+ throws Exception {
+
+ final Histogram[] histograms = new Histogram[threadCount];
+ for (int i = 0; i < histograms.length; i++) {
+ histograms[i] = PerfTest.createHistogram();
+ }
+ final int LINES = 256 * 1024;
+
+ Thread[] threads = new Thread[threadCount];
+ for (int i = 0; i < threads.length; i++) {
+ final Histogram histogram = histograms[i];
+ threads[i] = new Thread() {
+ public void run() {
+// int latencyCount = threadCount >= 16 ? 1000000 : 5000000;
+ int latencyCount = 5000000;
+ int count = PerfTest.throughput ? LINES / threadCount
+ : latencyCount;
+ runTest(runner, count, "end", histogram, threadCount);
+ }
+ };
+ }
+ for (Thread thread : threads) {
+ thread.start();
+ }
+ for (Thread thread : threads) {
+ thread.join();
+ }
+
+ for (Histogram histogram : histograms) {
+ PerfTest.reportResult(resultFile, name, histogram);
+ }
+ }
+}
\ No newline at end of file
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTest.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTest.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTest.java (working copy)
@@ -0,0 +1,168 @@
+package org.apache.logging.log4j.async.perftest;
+
+import java.io.FileWriter;
+import java.io.IOException;
+
+import com.lmax.disruptor.collections.Histogram;
+
+public class PerfTest {
+
+ private static final String LINE100 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!\"#$%&'()-=^~|\\@`[]{};:+*,.<>/?_123456";
+ public static final String LINE500 = LINE100 + LINE100 + LINE100 + LINE100
+ + LINE100;
+
+ static boolean verbose = false;
+ static boolean throughput;
+
+ // determine how long it takes to call System.nanoTime() (on average)
+ static long calcNanoTimeCost() {
+ final long iterations = 10000000;
+ long start = System.nanoTime();
+ long finish = start;
+
+ for (int i = 0; i < iterations; i++) {
+ finish = System.nanoTime();
+ }
+
+ if (finish <= start) {
+ throw new IllegalStateException();
+ }
+
+ finish = System.nanoTime();
+ return (finish - start) / iterations;
+ }
+
+ static Histogram createHistogram() {
+ long[] intervals = new long[31];
+ long intervalUpperBound = 1L;
+ for (int i = 0, size = intervals.length - 1; i < size; i++) {
+ intervalUpperBound *= 2;
+ intervals[i] = intervalUpperBound;
+ }
+
+ intervals[intervals.length - 1] = Long.MAX_VALUE;
+ return new Histogram(intervals);
+ }
+
+ public static void main(String[] args) throws Exception {
+ new PerfTest().doMain(args);
+ }
+
+ public void doMain(String[] args) throws Exception {
+ String runnerClass = args[0];
+ IPerfTestRunner runner = (IPerfTestRunner) Class.forName(runnerClass)
+ .newInstance();
+ String name = args[1];
+ String resultFile = args.length > 2 ? args[2] : null;
+ for (String arg : args) {
+ if ("-verbose".equalsIgnoreCase(arg)) {
+ verbose = true;
+ }
+ if ("-throughput".equalsIgnoreCase(arg)) {
+ throughput = true;
+ }
+ }
+ int threadCount = args.length > 2 ? Integer.parseInt(args[3]) : 3;
+ printf("Starting %s %s (%d)...%n", getClass().getSimpleName(), name,
+ threadCount);
+ runTestAndPrintResult(runner, name, threadCount, resultFile);
+ runner.shutdown();
+ System.exit(0);
+ }
+
+ public void runTestAndPrintResult(IPerfTestRunner runner,
+ final String name, int threadCount, String resultFile)
+ throws Exception {
+ Histogram warmupHist = createHistogram();
+
+ // ThreadContext.put("aKey", "mdcVal");
+ println("Warming up the JVM...");
+ long t1 = System.nanoTime();
+
+ // warmup at least 2 rounds and at most 1 minute
+ final long stop = System.currentTimeMillis() + (60 * 1000);
+ for (int i = 0; i < 10; i++) {
+ final int LINES = throughput ? 50000 : 200000;
+ runTest(runner, LINES, null, warmupHist, 1);
+ if (i > 0 && System.currentTimeMillis() >= stop) {
+ return;
+ }
+ }
+
+ printf("Warmup complete in %.1f seconds%n", (System.nanoTime() - t1)
+ / (1000.0 * 1000.0 * 1000.0));
+ println("Waiting 10 seconds for buffers to drain warmup data...");
+ Thread.sleep(10000);
+
+ println("Starting the main test...");
+ // test
+ throughput = false;
+ runSingleThreadedTest(runner, name, resultFile);
+
+ Thread.sleep(1000);
+
+ throughput = true;
+ runSingleThreadedTest(runner, name, resultFile);
+ }
+
+ private int runSingleThreadedTest(IPerfTestRunner runner, String name,
+ String resultFile) throws IOException {
+ Histogram latency = createHistogram();
+ final int LINES = throughput ? 50000 : 5000000;
+ runTest(runner, LINES, "end", latency, 1);
+ reportResult(resultFile, name, latency);
+ return LINES;
+ }
+
+ static void reportResult(String file, String name, Histogram histogram)
+ throws IOException {
+ String result = createSamplingReport(name, histogram);
+ println(result);
+
+ if (file != null) {
+ FileWriter writer = new FileWriter(file, true);
+ writer.write(result);
+ writer.write(System.getProperty("line.separator"));
+ writer.close();
+ }
+ }
+
+ static void printf(String msg, Object... objects) {
+ if (verbose) {
+ System.out.printf(msg, objects);
+ }
+ }
+
+ static void println(String msg) {
+ if (verbose) {
+ System.out.println(msg);
+ }
+ }
+
+ static String createSamplingReport(String name, Histogram histogram) {
+ Histogram data = histogram;
+ if (throughput) {
+ return data.getMax() + " operations/second";
+ }
+ String result = String.format(
+ "avg=%.0f 99%%=%d 99.99%%=%d sampleCount=%d", data.getMean(), //
+ data.getTwoNinesUpperBound(), //
+ data.getFourNinesUpperBound(), //
+ data.getCount() //
+ );
+ return result;
+ }
+
+ public void runTest(IPerfTestRunner runner, int lines, String finalMessage,
+ Histogram histogram, int threadCount) {
+ if (throughput) {
+ runner.runThroughputTest(lines, histogram);
+ } else {
+ long nanoTimeCost = calcNanoTimeCost();
+ runner.runLatencyTest(lines, histogram, nanoTimeCost, threadCount);
+ }
+ if (finalMessage != null) {
+ runner.log(finalMessage);
+ }
+ }
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTestDriver.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTestDriver.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTestDriver.java (working copy)
@@ -0,0 +1,435 @@
+package org.apache.logging.log4j.async.perftest;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.CharBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class PerfTestDriver {
+ static enum WaitStrategy {
+ Sleep, Yield, Block
+ };
+
+ static class Setup implements Comparable {
+ private Class> _class;
+ private String _log4jConfig;
+ private String _name;
+ private String[] _systemProperties;
+ private int _threadCount;
+ private File _temp;
+ public Stats _stats;
+ private WaitStrategy _wait;
+ private String _runner;
+
+ public Setup(Class> klass, String runner, String name,
+ String log4jConfig, int threadCount, WaitStrategy wait,
+ String... systemProperties) throws IOException {
+ _class = klass;
+ _runner = runner;
+ _name = name;
+ _log4jConfig = log4jConfig;
+ _threadCount = threadCount;
+ _systemProperties = systemProperties;
+ _wait = wait;
+ _temp = File.createTempFile("log4jperformance", ".txt");
+ }
+
+ List processArguments(String java) {
+ List args = new ArrayList();
+ args.add(java);
+ args.add("-server");
+ args.add("-Xms1g");
+ args.add("-Xmx1g");
+
+ // args.add("-XX:+UseParallelOldGC");
+ // args.add("-Xloggc:gc.log");
+ // args.add("-XX:+PrintGCTimeStamps");
+ // args.add("-XX:+PrintGCDetails");
+ // args.add("-XX:+PrintGCDateStamps");
+ // args.add("-XX:+PrintGCApplicationStoppedTime");
+ // args.add("-XX:+PrintGCApplicationConcurrentTime");
+ // args.add("-XX:+PrintSafepointStatistics");
+
+ args.add("-Dlog4j.configuration=" + _log4jConfig); // log4j1.2
+ args.add("-Dlog4j.configurationFile=" + _log4jConfig); // log4j2
+ args.add("-Dlogback.configurationFile=" + _log4jConfig);// logback
+
+ int ringBufferSize = getUserSpecifiedRingBufferSize();
+ if (ringBufferSize >= 128) {
+ args.add("-DAsyncLoggerConfig.RingBufferSize=" + ringBufferSize);
+ args.add("-DAsyncLogger.RingBufferSize=" + ringBufferSize);
+ }
+ args.add("-DAsyncLoggerConfig.WaitStrategy=" + _wait);
+ args.add("-DAsyncLogger.WaitStrategy=" + _wait);
+ if (_systemProperties != null) {
+ for (String property : _systemProperties) {
+ args.add(property);
+ }
+ }
+ args.add("-cp");
+ args.add(System.getProperty("java.class.path"));
+ args.add(_class.getName());
+ args.add(_runner);
+ args.add(_name);
+ args.add(_temp.getAbsolutePath());
+ args.add(String.valueOf(_threadCount));
+ return args;
+ }
+
+ private int getUserSpecifiedRingBufferSize() {
+ try {
+ return Integer.parseInt(System.getProperty("RingBufferSize",
+ "-1"));
+ } catch (Exception ignored) {
+ return -1;
+ }
+ }
+
+ ProcessBuilder latencyTest(String java) {
+ return new ProcessBuilder(processArguments(java));
+ }
+
+ ProcessBuilder throughputTest(String java) {
+ List args = processArguments(java);
+ args.add("-throughput");
+ return new ProcessBuilder(args);
+ }
+
+ @Override
+ public int compareTo(Setup other) {
+ // largest ops/sec first
+ return (int) Math.signum(other._stats._averageOpsPerSec
+ - _stats._averageOpsPerSec);
+ }
+
+ public String description() {
+ String detail = _class.getSimpleName();
+ if (PerfTest.class == _class) {
+ detail = "single thread";
+ } else if (MTPerfTest.class == _class) {
+ detail = _threadCount + " threads";
+ }
+ String target = _runner.substring(_runner.indexOf(".Run") + 4);
+ return target + ": " + _name + " (" + detail + ")";
+ }
+ }
+
+ static class Stats {
+ int _count;
+ long _average;
+ long _pct99;
+ long _pct99_99;
+ double _latencyRowCount;
+ int _throughputRowCount;
+ private long _averageOpsPerSec;
+
+ // example line: avg=828 99%=1118 99.99%=5028 Count=3125
+ public Stats(String raw, int repeat) {
+ String[] lines = raw.split("[\\r\\n]+");
+ long totalOps = 0;
+ for (String line : lines) {
+ if (line.startsWith("avg")) {
+ _latencyRowCount++;
+ String[] parts = line.split(" ");
+ int i = 0;
+ _average += Long.parseLong(parts[i++].split("=")[1]);
+ _pct99 += Long.parseLong(parts[i++].split("=")[1]);
+ _pct99_99 += Long.parseLong(parts[i++].split("=")[1]);
+ _count += Integer.parseInt(parts[i++].split("=")[1]);
+ } else {
+ _throughputRowCount++;
+ String number = line.substring(0, line.indexOf(' '));
+ long opsPerSec = Long.parseLong(number);
+ totalOps += opsPerSec;
+ }
+ }
+ _averageOpsPerSec = totalOps / (int) _throughputRowCount;
+ }
+
+ public String toString() {
+ String fmt = "throughput: %,d ops/sec. latency(ns): avg=%.1f 99%% < %.1f 99.99%% < %.1f (%d samples)";
+ return String.format(fmt, _averageOpsPerSec, //
+ _average / _latencyRowCount, // mean latency
+ _pct99 / _latencyRowCount, // 99% observations less than
+ _pct99_99 / _latencyRowCount,// 99.99% observs less than
+ _count);
+ }
+ }
+
+ // single-threaded performance test
+ private static Setup s(String config, String runner, String name,
+ String... systemProperties) throws IOException {
+ WaitStrategy wait = WaitStrategy.valueOf(System.getProperty(
+ "WaitStrategy", "Block"));
+ return new Setup(PerfTest.class, runner, name, config, 1, wait,
+ systemProperties);
+ }
+
+ // single-threaded performance test
+ private static Setup m(String config, String runner, String name,
+ int threadCount, String... systemProperties) throws IOException {
+ WaitStrategy wait = WaitStrategy.valueOf(System.getProperty(
+ "WaitStrategy", "Block"));
+ return new Setup(MTPerfTest.class, runner, name, config, threadCount,
+ wait, systemProperties);
+ }
+
+ public static void main(String[] args) throws Exception {
+ final String ALL_ASYNC = "-DLog4jContextSelector=org.apache.logging.log4j.async.AsyncLoggerContextSelector";
+ final String CACHEDCLOCK = "-DAsyncLogger.Clock=CachedClock";
+ final String SYSCLOCK = "";
+ final String LOG12 = "org.apache.logging.log4j.async.perftest.RunLog4j1";
+ final String LOG20 = "org.apache.logging.log4j.async.perftest.RunLog4j2";
+ final String LOGBK = "org.apache.logging.log4j.async.perftest.RunLogback";
+
+ long start = System.nanoTime();
+ List tests = new ArrayList();
+ tests.add(s("perf-logback.xml", LOGBK, "Sync"));
+ tests.add(s("perf-log4j12.xml", LOG12, "Sync"));
+ tests.add(s("perf3PlainNoLoc.xml", LOG20, "Sync"));
+ tests.add(s("perf-logback-async.xml", LOGBK, "Async Appender"));
+ tests.add(s("perf-log4j12-async.xml", LOG12, "Async Appender"));
+ tests.add(s("perf5AsyncApndNoLoc.xml", LOG20,
+ "Async Appender no location"));
+ tests.add(s("perf3PlainNoLoc.xml", LOG20, "All async no loc SysClock",
+ ALL_ASYNC, SYSCLOCK));
+ tests.add(s("perf7MixedNoLoc.xml", LOG20, "Mixed async no location"));
+
+ for (int i = 32; i <= 64; i *= 2) {
+ tests.add(m("perf-logback.xml", LOGBK, "Sync", i));
+ tests.add(m("perf-log4j12.xml", LOG12, "Sync", i));
+ tests.add(m("perf3PlainNoLoc.xml", LOG20, "Sync", i));
+ tests.add(m("perf-logback-async.xml", LOGBK, "Async Appender", i));
+ tests.add(m("perf-log4j12-async.xml", LOG12, "Async Appender", i));
+ tests.add(m("perf5AsyncApndNoLoc.xml", LOG20,
+ "Async Appender no location", i));
+ tests.add(m("perf3PlainNoLoc.xml", LOG20,
+ "All async no loc SysClock", i, ALL_ASYNC, SYSCLOCK));
+ tests.add(m("perf7MixedNoLoc.xml", LOG20,
+ "Mixed async no location", i));
+ }
+
+ // Setup[] tests = new Setup[] { //
+ // s("perf-logback.xml", LOGBK, "Sync"), //
+ // s("perf-log4j12.xml", LOG12, "Sync"), //
+ // s("perf3PlainNoLoc.xml", LOG20, "Sync"), //
+ // s("perf-logback-async.xml", LOGBK, "Async Appender"), //
+ // s("perf-log4j12-async.xml", LOG12, "Async Appender"), //
+ // s("perf5AsyncApndNoLoc.xml", LOG20,
+ // "Async Appender no location"),
+ // s("perf3PlainNoLoc.xml", LOG20, "All async no loc SysClock",
+ // ALL_ASYNC, SYSCLOCK), //
+ // s("perf7MixedNoLoc.xml", LOG20, "Mixed async no location"), //
+ //
+ // m("perf-log4j12-async.xml", LOG12, "Async Appender", 2), //
+ // m("perf-log4j12.xml", LOG12, "Sync", 2), //
+ // m("perf-logback.xml", LOGBK, "Sync", 2), //
+ // m("perf-logback-async.xml", LOGBK, "Async Appender", 2), //
+ // m("perf3PlainNoLoc.xml", LOG20, "Sync", 2), //
+ // m("perf3PlainNoLoc.xml", LOG20, "All async no loc CachedClock",
+ // 2, ALL_ASYNC, CACHEDCLOCK), //
+ // m("perf3PlainNoLoc.xml", LOG20, "All async no loc SysClock", 2,
+ // ALL_ASYNC, SYSCLOCK), //
+ //
+ // m("perf-log4j12-async.xml", LOG12, "Async Appender", 4), //
+ // m("perf-log4j12.xml", LOG12, "Sync", 4), //
+ // m("perf-logback.xml", LOGBK, "Sync", 4), //
+ // m("perf-logback-async.xml", LOGBK, "Async Appender", 4), //
+ // m("perf3PlainNoLoc.xml", LOG20, "Sync", 4), //
+ // m("perf3PlainNoLoc.xml", LOG20,
+ // "All async no loc CachedClock",
+ // 4, ALL_ASYNC, CACHEDCLOCK), //
+ // m("perf3PlainNoLoc.xml", LOG20, "All async no loc SysClock",
+ // 4,
+ // ALL_ASYNC, SYSCLOCK), //
+ //
+ // m("perf-log4j12-async.xml", LOG12, "Async Appender", 8), //
+ // m("perf-log4j12.xml", LOG12, "Sync", 8), //
+ // m("perf-logback.xml", LOGBK, "Sync", 8), //
+ // m("perf-logback-async.xml", LOGBK, "Async Appender", 8), //
+ // m("perf3PlainNoLoc.xml", LOG20, "Sync", 8), //
+ // m("perf3PlainNoLoc.xml", LOG20,
+ // "All async no loc CachedClock",
+ // 8, ALL_ASYNC, CACHEDCLOCK), //
+ // m("perf3PlainNoLoc.xml", LOG20, "All async no loc SysClock",
+ // 8,
+ // ALL_ASYNC, SYSCLOCK), //
+
+ // 2 threads
+ // m("perf5AsyncApndNoLoc.xml", LOG20,"Async Appender no location",
+ // 2), //
+ // m("perf6AsyncApndLoc.xml",
+ // LOG12,"Async Appender with location",
+ // 2), //
+ // m("perf7MixedNoLoc.xml", "Mixed async no location", 2), //
+ // m("perf8MixedLoc.xml", "Mixed async with location", 2), //
+ // m("perf4PlainLocation.xml",
+ // "All async with location SysClock",
+ // 2, ALL_ASYNC), //
+ // m("perf4PlainLocation.xml",
+ // "All async with location CachedClock", 2, ALL_ASYNC,
+ // CACHEDCLOCK), //
+ // m("perf4PlainLocation.xml", "All sync with location", 2), //
+ // m("perf1syncFile.xml", "FileAppender", 2), //
+ // m("perf1syncFastFile.xml", "FastFileAppender", 2), //
+ // m("perf2syncRollFile.xml", "RollFileAppender", 2), //
+ // m("perf2syncRollFastFile.xml", "RollFastFileAppender", 2), //
+
+ // 4 threads
+ // m("perf5AsyncApndNoLoc.xml", LOG20,"Async Appender no location",
+ // 4), //
+ // m("perf6AsyncApndLoc.xml", "Async Appender with location",
+ // 4), //
+ // m("perf7MixedNoLoc.xml", "Mixed async no location", 4), //
+ // m("perf8MixedLoc.xml", "Mixed async with location", 4), //
+ // m("perf4PlainLocation.xml",
+ // "All async with location SysClock",
+ // 4, ALL_ASYNC), //
+ // m("perf4PlainLocation.xml",
+ // "All async with location CachedClock", 4, ALL_ASYNC,
+ // CACHEDCLOCK), //
+ // m("perf4PlainLocation.xml", "All sync with location", 4), //
+ // m("perf1syncFile.xml", "FileAppender", 4), //
+ // m("perf1syncFastFile.xml", "FastFileAppender", 4), //
+ // m("perf2syncRollFile.xml", "RollFileAppender", 4), //
+ // m("perf2syncRollFastFile.xml", "RollFastFileAppender", 4), //
+
+ // 8 threads
+ // m("perf5AsyncApndNoLoc.xml", LOG20,
+ // "Async Appender no location", 8), //
+ // m("perf6AsyncApndLoc.xml", "Async Appender with location",
+ // 8), //
+ // m("perf7MixedNoLoc.xml", "Mixed async no location", 8), //
+ // m("perf8MixedLoc.xml", "Mixed async with location", 8), //
+ // m("perf4PlainLocation.xml",
+ // "All async with location SysClock",
+ // 8, ALL_ASYNC), //
+ // m("perf4PlainLocation.xml",
+ // "All async with location CachedClock", 8, ALL_ASYNC,
+ // CACHEDCLOCK), //
+ // m("perf4PlainLocation.xml", "All sync with location", 8), //
+ // m("perf1syncFile.xml", "FileAppender", 8), //
+ // m("perf1syncFastFile.xml", "FastFileAppender", 8), //
+ // m("perf2syncRollFile.xml", "RollFileAppender", 8), //
+ // m("perf2syncRollFastFile.xml", "RollFastFileAppender", 8), //
+
+ // s("perf-log4j12-async.xml", LOG12, "Async Appender"), //
+ // s("perf-log4j12.xml", LOG12, "Sync"), //
+ // s("perf-logback.xml", LOGBK, "Sync"), //
+ // s("perf-logback-async.xml", LOGBK, "Async Appender"), //
+ // s("perf3PlainNoLoc.xml", LOG20, "Sync"), //
+ // s("perf3PlainNoLoc.xml", LOG20, "All async no loc CachedClock",
+ // ALL_ASYNC, CACHEDCLOCK), //
+ // s("perf3PlainNoLoc.xml", LOG20, "All async no loc SysClock",
+ // ALL_ASYNC, SYSCLOCK), //
+ // s("perf5AsyncApndNoLoc.xml", LOG20,
+ // "Async Appender no location"),
+ //
+ // s("perf6AsyncApndLoc.xml", "Async Appender with location"), //
+ // s("perf7MixedNoLoc.xml", "Mixed async no location"), //
+ // s("perf8MixedLoc.xml", "Mixed async with location"), //
+ // s("perf4PlainLocation.xml", "All async with location SysClock",
+ // ALL_ASYNC), //
+ // s("perf4PlainLocation.xml",
+ // "All async with location CachedClock", ALL_ASYNC,
+ // CACHEDCLOCK), //
+ // s("perf4PlainLocation.xml", "All sync with location"), //
+ // s("perf1syncFile.xml", "FileAppender"), //
+ // s("perf1syncFastFile.xml", "FastFileAppender"), //
+ // s("perf2syncRollFile.xml", "RollFileAppender"), //
+ // s("perf2syncRollFastFile.xml", "RollFastFileAppender"), //
+ // };
+
+ String java = args.length > 0 ? args[0] : "java";
+ int repeat = args.length > 1 ? Integer.parseInt(args[1]) : 5;
+ int x = 0;
+ for (Setup config : tests) {
+ System.out.print(config.description());
+ ProcessBuilder pb = config.throughputTest(java);
+ pb.redirectErrorStream(true); // merge System.out and System.err
+ long t1 = System.nanoTime();
+ // int count = config._threadCount >= 16 ? 2 : repeat;
+ int count = repeat;
+ runPerfTest(count, x++, config, pb);
+ System.out.printf(" took %.1f seconds%n", (System.nanoTime() - t1)
+ / (1000.0 * 1000.0 * 1000.0));
+
+ FileReader reader = new FileReader(config._temp);
+ CharBuffer buffer = CharBuffer.allocate(256 * 1024);
+ reader.read(buffer);
+ reader.close();
+ config._temp.delete();
+ buffer.flip();
+
+ String raw = buffer.toString();
+ System.out.print(raw);
+ Stats stats = new Stats(raw, repeat);
+ System.out.println(stats);
+ System.out.println("-----");
+ config._stats = stats;
+ }
+ new File("perftest.log").delete();
+ System.out
+ .printf("Done. Total duration: %.1f minutes%n",
+ (System.nanoTime() - start)
+ / (60.0 * 1000.0 * 1000.0 * 1000.0));
+
+ printRanking((Setup[]) tests.toArray(new Setup[tests.size()]));
+ }
+
+ private static void printRanking(Setup[] tests) {
+ System.out.println();
+ System.out.println("Ranking:");
+ Arrays.sort(tests);
+ for (int i = 0; i < tests.length; i++) {
+ Setup setup = tests[i];
+ System.out.println((i + 1) + ". " + setup.description() + ": "
+ + setup._stats);
+ }
+ }
+
+ private static void runPerfTest(int repeat, int setupIndex, Setup config,
+ ProcessBuilder pb) throws IOException, InterruptedException {
+ for (int i = 0; i < repeat; i++) {
+ System.out.print(" (" + (i + 1) + "/" + repeat + ")...");
+ final Process process = pb.start();
+
+ final boolean[] stop = { false };
+ printProcessOutput(process, stop);
+ process.waitFor();
+ stop[0] = true;
+
+ File gc = new File("gc" + setupIndex + "_" + i
+ + config._log4jConfig + ".log");
+ if (gc.exists()) {
+ gc.delete();
+ }
+ new File("gc.log").renameTo(gc);
+ }
+ }
+
+ private static Thread printProcessOutput(final Process process,
+ final boolean[] stop) {
+
+ Thread t = new Thread("OutputWriter") {
+ public void run() {
+ BufferedReader in = new BufferedReader(new InputStreamReader(
+ process.getInputStream()));
+ try {
+ String line = null;
+ while (!stop[0] && (line = in.readLine()) != null) {
+ System.out.println(line);
+ }
+ } catch (Exception ignored) {
+ }
+ }
+ };
+ t.start();
+ return t;
+ }
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTestResultFormatter.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTestResultFormatter.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTestResultFormatter.java (working copy)
@@ -0,0 +1,167 @@
+package org.apache.logging.log4j.async.perftest;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Utility class that can read the "Ranking" output of the PerfTestDriver and
+ * format it for pasting into Excel.
+ */
+class PerfTestResultFormatter {
+ static final String LF = System.getProperty("line.separator");
+ static final NumberFormat NUM = new DecimalFormat("#,##0");
+
+ static class Stats {
+ long throughput;
+ double avgLatency;
+ double latency99Pct;
+ double latency99_99Pct;
+
+ Stats(String throughput, String avg, String lat99, String lat99_99)
+ throws ParseException {
+ this.throughput = NUM.parse(throughput.trim()).longValue();
+ this.avgLatency = Double.parseDouble(avg.trim());
+ this.latency99Pct = Double.parseDouble(lat99.trim());
+ this.latency99_99Pct = Double.parseDouble(lat99_99.trim());
+ }
+ }
+
+ private Map> results = new TreeMap>();
+
+ public PerfTestResultFormatter() {
+ }
+
+ public String format(String text) throws ParseException {
+ results.clear();
+ String[] lines = text.split("[\\r\\n]+");
+ for (String line : lines) {
+ process(line);
+ }
+ return latencyTable() + LF + throughputTable();
+ }
+
+ private String latencyTable() {
+ StringBuilder sb = new StringBuilder(4 * 1024);
+ Set subKeys = results.values().iterator().next().keySet();
+ char[] tabs = new char[subKeys.size()];
+ Arrays.fill(tabs, '\t');
+ String sep = new String(tabs);
+ sb.append("\tAverage latency" + sep + "99% less than" + sep
+ + "99.99% less than");
+ sb.append(LF);
+ for (int i = 0; i < 3; i++) {
+ for (String subKey : subKeys) {
+ sb.append("\t").append(subKey);
+ }
+ }
+ sb.append(LF);
+ for (String key : results.keySet()) {
+ sb.append(key);
+ for (int i = 0; i < 3; i++) {
+ Map sub = results.get(key);
+ for (String subKey : sub.keySet()) {
+ Stats stats = sub.get(subKey);
+ switch (i) {
+ case 0:
+ sb.append("\t").append((long) stats.avgLatency);
+ break;
+ case 1:
+ sb.append("\t").append((long) stats.latency99Pct);
+ break;
+ case 2:
+ sb.append("\t").append((long) stats.latency99_99Pct);
+ break;
+ }
+ }
+ }
+ sb.append(LF);
+ }
+ return sb.toString();
+ }
+
+ private String throughputTable() {
+ StringBuilder sb = new StringBuilder(4 * 1024);
+ Set subKeys = results.values().iterator().next().keySet();
+ sb.append("\tThroughput per thread (msg/sec)");
+ sb.append(LF);
+ for (String subKey : subKeys) {
+ sb.append("\t").append(subKey);
+ }
+ sb.append(LF);
+ for (String key : results.keySet()) {
+ sb.append(key);
+ Map sub = results.get(key);
+ for (String subKey : sub.keySet()) {
+ Stats stats = sub.get(subKey);
+ sb.append("\t").append((long) stats.throughput);
+ }
+ sb.append(LF);
+ }
+ return sb.toString();
+ }
+
+ private void process(String line) throws ParseException {
+ String key = line.substring(line.indexOf('.') + 1, line.indexOf('('));
+ String sub = line.substring(line.indexOf('(') + 1, line.indexOf(')'));
+ String throughput = line.substring(line.indexOf("throughput: ")
+ + "throughput: ".length(), line.indexOf(" ops"));
+ String avg = line.substring(line.indexOf("avg=") + "avg=".length(),
+ line.indexOf(" 99%"));
+ String pct99 = line.substring(
+ line.indexOf("99% < ") + "99% < ".length(),
+ line.indexOf(" 99.99%"));
+ String pct99_99 = line.substring(line.indexOf("99.99% < ")
+ + "99.99% < ".length(), line.lastIndexOf('(') - 1);
+ Stats stats = new Stats(throughput, avg, pct99, pct99_99);
+ Map map = results.get(key.trim());
+ if (map == null) {
+ map = new TreeMap(sort());
+ results.put(key.trim(), map);
+ }
+ String subKey = sub.trim();
+ if ("single thread".equals(subKey)) {
+ subKey = "1 thread";
+ }
+ map.put(subKey, stats);
+ }
+
+ private Comparator sort() {
+ return new Comparator() {
+ List expected = Arrays.asList("1 thread", "2 threads",
+ "4 threads", "8 threads", "16 threads", "32 threads",
+ "64 threads");
+
+ @Override
+ public int compare(String o1, String o2) {
+ int i1 = expected.indexOf(o1);
+ int i2 = expected.indexOf(o2);
+ if (i1 < 0 || i2 < 0) {
+ return o1.compareTo(o2);
+ }
+ return i1 - i2;
+ }
+ };
+ }
+
+ public static void main(String[] args) throws Exception {
+ PerfTestResultFormatter fmt = new PerfTestResultFormatter();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(
+ System.in));
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ fmt.process(line);
+ }
+ System.out.println(fmt.latencyTable());
+ System.out.println();
+ System.out.println(fmt.throughputTable());
+ }
+}
\ No newline at end of file
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/RunLog4j1.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/RunLog4j1.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/RunLog4j1.java (working copy)
@@ -0,0 +1,53 @@
+package org.apache.logging.log4j.async.perftest;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+import com.lmax.disruptor.collections.Histogram;
+
+public class RunLog4j1 implements IPerfTestRunner {
+
+ @Override
+ public void runThroughputTest(int lines, Histogram histogram) {
+ long s1 = System.nanoTime();
+ Logger logger = LogManager.getLogger(getClass());
+ for (int j = 0; j < lines; j++) {
+ logger.info(THROUGHPUT_MSG);
+ }
+ long s2 = System.nanoTime();
+ long opsPerSec = (1000L * 1000L * 1000L * lines) / (s2 - s1);
+ histogram.addObservation(opsPerSec);
+ }
+
+ @Override
+ public void runLatencyTest(int samples, Histogram histogram,
+ long nanoTimeCost, int threadCount) {
+ Logger logger = LogManager.getLogger(getClass());
+ for (int i = 0; i < samples; i++) {
+ long s1 = System.nanoTime();
+ logger.info(LATENCY_MSG);
+ long s2 = System.nanoTime();
+ long value = s2 - s1 - nanoTimeCost;
+ if (value > 0) {
+ histogram.addObservation(value);
+ }
+ // wait 1 microsec
+ final long PAUSE_NANOS = 10000 * threadCount;
+ long pauseStart = System.nanoTime();
+ while (PAUSE_NANOS > (System.nanoTime() - pauseStart)) {
+ // busy spin
+ }
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ LogManager.shutdown();
+ }
+
+ @Override
+ public void log(String finalMessage) {
+ Logger logger = LogManager.getLogger(getClass());
+ logger.info(finalMessage);
+ }
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/RunLog4j2.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/RunLog4j2.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/RunLog4j2.java (working copy)
@@ -0,0 +1,57 @@
+package org.apache.logging.log4j.async.perftest;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
+
+import com.lmax.disruptor.collections.Histogram;
+
+public class RunLog4j2 implements IPerfTestRunner {
+
+ @Override
+ public void runThroughputTest(int lines, Histogram histogram) {
+ long s1 = System.nanoTime();
+ Logger logger = LogManager.getLogger(getClass());
+ for (int j = 0; j < lines; j++) {
+ logger.info(THROUGHPUT_MSG);
+ }
+ long s2 = System.nanoTime();
+ long opsPerSec = (1000L * 1000L * 1000L * lines) / (s2 - s1);
+ histogram.addObservation(opsPerSec);
+ }
+
+
+ @Override
+ public void runLatencyTest(int samples, Histogram histogram,
+ long nanoTimeCost, int threadCount) {
+ Logger logger = LogManager.getLogger(getClass());
+ for (int i = 0; i < samples; i++) {
+ long s1 = System.nanoTime();
+ logger.info(LATENCY_MSG);
+ long s2 = System.nanoTime();
+ long value = s2 - s1 - nanoTimeCost;
+ if (value > 0) {
+ histogram.addObservation(value);
+ }
+ // wait 1 microsec
+ final long PAUSE_NANOS = 10000 * threadCount;
+ long pauseStart = System.nanoTime();
+ while (PAUSE_NANOS > (System.nanoTime() - pauseStart)) {
+ // busy spin
+ }
+ }
+ }
+
+
+ @Override
+ public void shutdown() {
+ ((LifeCycle) LogManager.getContext()).stop(); // stop async thread
+ }
+
+
+ @Override
+ public void log(String finalMessage) {
+ Logger logger = LogManager.getLogger(getClass());
+ logger.info(finalMessage);
+ }
+}
Index: log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/RunLogback.java
===================================================================
--- log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/RunLogback.java (revision 0)
+++ log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/RunLogback.java (working copy)
@@ -0,0 +1,55 @@
+package org.apache.logging.log4j.async.perftest;
+
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.core.spi.LifeCycle;
+
+import com.lmax.disruptor.collections.Histogram;
+
+public class RunLogback implements IPerfTestRunner {
+
+ @Override
+ public void runThroughputTest(int lines, Histogram histogram) {
+ long s1 = System.nanoTime();
+ Logger logger = (Logger) LoggerFactory.getLogger(getClass());
+ for (int j = 0; j < lines; j++) {
+ logger.info(THROUGHPUT_MSG);
+ }
+ long s2 = System.nanoTime();
+ long opsPerSec = (1000L * 1000L * 1000L * lines) / (s2 - s1);
+ histogram.addObservation(opsPerSec);
+ }
+
+ @Override
+ public void runLatencyTest(int samples, Histogram histogram,
+ long nanoTimeCost, int threadCount) {
+ Logger logger = (Logger) LoggerFactory.getLogger(getClass());
+ for (int i = 0; i < samples; i++) {
+ long s1 = System.nanoTime();
+ logger.info(LATENCY_MSG);
+ long s2 = System.nanoTime();
+ long value = s2 - s1 - nanoTimeCost;
+ if (value > 0) {
+ histogram.addObservation(value);
+ }
+ // wait 1 microsec
+ final long PAUSE_NANOS = 10000 * threadCount;
+ long pauseStart = System.nanoTime();
+ while (PAUSE_NANOS > (System.nanoTime() - pauseStart)) {
+ // busy spin
+ }
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ ((LifeCycle) LoggerFactory.getILoggerFactory()).stop();
+ }
+
+ @Override
+ public void log(String msg) {
+ Logger logger = (Logger) LoggerFactory.getLogger(getClass());
+ logger.info(msg);
+ }
+}
Index: log4j-async/src/test/resources/AsyncLoggerConfigTest.xml
===================================================================
--- log4j-async/src/test/resources/AsyncLoggerConfigTest.xml (revision 0)
+++ log4j-async/src/test/resources/AsyncLoggerConfigTest.xml (working copy)
@@ -0,0 +1,19 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/test/resources/AsyncLoggerLocationTest.xml
===================================================================
--- log4j-async/src/test/resources/AsyncLoggerLocationTest.xml (revision 0)
+++ log4j-async/src/test/resources/AsyncLoggerLocationTest.xml (working copy)
@@ -0,0 +1,17 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %location %m %ex%n
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/test/resources/AsyncLoggerTest.xml
===================================================================
--- log4j-async/src/test/resources/AsyncLoggerTest.xml (revision 0)
+++ log4j-async/src/test/resources/AsyncLoggerTest.xml (working copy)
@@ -0,0 +1,17 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %location %m %ex%n
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/test/resources/FastFileAppenderLocationTest.xml
===================================================================
--- log4j-async/src/test/resources/FastFileAppenderLocationTest.xml (revision 0)
+++ log4j-async/src/test/resources/FastFileAppenderLocationTest.xml (working copy)
@@ -0,0 +1,19 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/test/resources/FastFileAppenderTest.xml
===================================================================
--- log4j-async/src/test/resources/FastFileAppenderTest.xml (revision 0)
+++ log4j-async/src/test/resources/FastFileAppenderTest.xml (working copy)
@@ -0,0 +1,19 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/test/resources/FastRollingFileAppenderLocationTest.xml
===================================================================
--- log4j-async/src/test/resources/FastRollingFileAppenderLocationTest.xml (revision 0)
+++ log4j-async/src/test/resources/FastRollingFileAppenderLocationTest.xml (working copy)
@@ -0,0 +1,21 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/test/resources/FastRollingFileAppenderTest.xml
===================================================================
--- log4j-async/src/test/resources/FastRollingFileAppenderTest.xml (revision 0)
+++ log4j-async/src/test/resources/FastRollingFileAppenderTest.xml (working copy)
@@ -0,0 +1,21 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/test/resources/log4j.dtd
===================================================================
--- log4j-async/src/test/resources/log4j.dtd (revision 0)
+++ log4j-async/src/test/resources/log4j.dtd (working copy)
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: log4j-async/src/test/resources/perf-log4j12-async.xml
===================================================================
--- log4j-async/src/test/resources/perf-log4j12-async.xml (revision 0)
+++ log4j-async/src/test/resources/perf-log4j12-async.xml (working copy)
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/test/resources/perf-log4j12.xml
===================================================================
--- log4j-async/src/test/resources/perf-log4j12.xml (revision 0)
+++ log4j-async/src/test/resources/perf-log4j12.xml (working copy)
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/test/resources/perf-logback-async.xml
===================================================================
--- log4j-async/src/test/resources/perf-logback-async.xml (revision 0)
+++ log4j-async/src/test/resources/perf-logback-async.xml (working copy)
@@ -0,0 +1,21 @@
+
+
+
+ perftest.log
+ false
+
+ %d %p %c{1} [%t] %X{aKey} %m %ex%n
+ false
+
+
+
+ 262144
+ 0
+ false
+
+
+
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/test/resources/perf-logback.xml
===================================================================
--- log4j-async/src/test/resources/perf-logback.xml (revision 0)
+++ log4j-async/src/test/resources/perf-logback.xml (working copy)
@@ -0,0 +1,15 @@
+
+
+
+ perftest.log
+ false
+
+ %d %p %c{1} [%t] %X{aKey} %m %ex%n
+ false
+
+
+
+
+
+
+
\ No newline at end of file
Index: log4j-async/src/test/resources/perf1syncFastFile.xml
===================================================================
--- log4j-async/src/test/resources/perf1syncFastFile.xml (revision 0)
+++ log4j-async/src/test/resources/perf1syncFastFile.xml (working copy)
@@ -0,0 +1,15 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %m %ex%n
+
+
+
+
+
+
+
+
+
Index: log4j-async/src/test/resources/perf1syncFile.xml
===================================================================
--- log4j-async/src/test/resources/perf1syncFile.xml (revision 0)
+++ log4j-async/src/test/resources/perf1syncFile.xml (working copy)
@@ -0,0 +1,15 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %m %ex%n
+
+
+
+
+
+
+
+
+
Index: log4j-async/src/test/resources/perf2syncRollFastFile.xml
===================================================================
--- log4j-async/src/test/resources/perf2syncRollFastFile.xml (revision 0)
+++ log4j-async/src/test/resources/perf2syncRollFastFile.xml (working copy)
@@ -0,0 +1,20 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %m %ex%n
+
+
+
+
+
+
+
+
+
+
+
+
Index: log4j-async/src/test/resources/perf2syncRollFile.xml
===================================================================
--- log4j-async/src/test/resources/perf2syncRollFile.xml (revision 0)
+++ log4j-async/src/test/resources/perf2syncRollFile.xml (working copy)
@@ -0,0 +1,20 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %m %ex%n
+
+
+
+
+
+
+
+
+
+
+
+
Index: log4j-async/src/test/resources/perf3PlainNoLoc.xml
===================================================================
--- log4j-async/src/test/resources/perf3PlainNoLoc.xml (revision 0)
+++ log4j-async/src/test/resources/perf3PlainNoLoc.xml (working copy)
@@ -0,0 +1,15 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %m %ex%n
+
+
+
+
+
+
+
+
+
Index: log4j-async/src/test/resources/perf4PlainLocation.xml
===================================================================
--- log4j-async/src/test/resources/perf4PlainLocation.xml (revision 0)
+++ log4j-async/src/test/resources/perf4PlainLocation.xml (working copy)
@@ -0,0 +1,15 @@
+
+
+
+
+
+ %d %p %c{1.} %C %location %line [%t] %X{aKey} %m %ex%n
+
+
+
+
+
+
+
+
+
Index: log4j-async/src/test/resources/perf5AsyncApndNoLoc.xml
===================================================================
--- log4j-async/src/test/resources/perf5AsyncApndNoLoc.xml (revision 0)
+++ log4j-async/src/test/resources/perf5AsyncApndNoLoc.xml (working copy)
@@ -0,0 +1,18 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %m %ex%n
+
+
+
+
+
+
+
+
+
+
+
+
Index: log4j-async/src/test/resources/perf6AsyncApndLoc.xml
===================================================================
--- log4j-async/src/test/resources/perf6AsyncApndLoc.xml (revision 0)
+++ log4j-async/src/test/resources/perf6AsyncApndLoc.xml (working copy)
@@ -0,0 +1,18 @@
+
+
+
+
+
+ %d %p %c{1.} %C %location %line [%t] %X{aKey} %m %ex%n
+
+
+
+
+
+
+
+
+
+
+
+
Index: log4j-async/src/test/resources/perf7MixedNoLoc.xml
===================================================================
--- log4j-async/src/test/resources/perf7MixedNoLoc.xml (revision 0)
+++ log4j-async/src/test/resources/perf7MixedNoLoc.xml (working copy)
@@ -0,0 +1,15 @@
+
+
+
+
+
+ %d %p %c{1.} [%t] %X{aKey} %m %ex%n
+
+
+
+
+
+
+
+
+
Index: log4j-async/src/test/resources/perf8MixedLoc.xml
===================================================================
--- log4j-async/src/test/resources/perf8MixedLoc.xml (revision 0)
+++ log4j-async/src/test/resources/perf8MixedLoc.xml (working copy)
@@ -0,0 +1,15 @@
+
+
+
+
+
+ %d %p %c{1.} %C %location %line [%t] %X{aKey} %m %ex%n
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: src/site/site.xml
===================================================================
--- src/site/site.xml (revision 1462911)
+++ src/site/site.xml (working copy)
@@ -101,6 +101,17 @@
+ -
+
+
+
+
+
+
+
+
+
+
@@ -121,6 +132,7 @@
+
+ There are a few system properties
+ you can use to control
+ aspects of the asynchronous logging subsystem.
+ Some of these can be used to
+ tune logging performance.
+
+
+
+ | System Property |
+ Default Value |
+ Description |
+
+
+ | AsyncLoggerConfig.ExceptionHandler |
+
+ null
+ |
+
+ Fully qualified name of a class that implements the
+ com.lmax.disruptor.ExceptionHandler
+ interface. The class needs to have a public
+ zero-argument
+ constructor. If specified, this class
+ will be notified when an
+ exception occurs while
+ logging the messages.
+ |
+
+
+ | AsyncLoggerConfig.RingBufferSize |
+ 256 * 1024
+ |
+
+ Size (number of slots) in the RingBuffer used by the
+ asynchronous logging subsystem.
+ Make this value large enough to
+ deal with bursts
+ of activity. The minimum size is 128.
+ The RingBuffer will
+ be pre-allocated at first use and will never grow
+ or shrink during the life of the system.
+ |
+
+
+ | AsyncLoggerConfig.WaitStrategy |
+
+ Sleep
+ |
+
+ Valid values: Block, Sleep, Yield.
+
+ Block
+ is a strategy that uses a lock and
+ condition
+ variable for the I/O thread waiting for log events.
+ Block can be used when
+ throughput and low-latency
+ are not as important as CPU resource.
+ Recommended for resource constrained/virtualised environments.
+
+ Sleep
+ is a strategy that initially spins, then
+ uses a Thread.yield(), and
+ eventually parks for the
+ minimum number of nanos the OS and JVM will allow
+ while the I/O thread is waiting for log events.
+ Sleep is a good compromise between performance
+ and CPU resource.
+ This strategy has very low impact on the
+ application thread, in exchange for some additional
+ latency for actually getting the message logged.
+
+ Yield
+ is a strategy that uses a Thread.yield() for
+ waiting for log events after an initially spinning.
+ Yield is a good compromise between performance
+ and CPU resource, but may use more CPU than Sleep
+ in order to get the message logged to disk sooner.
+ |
+
+ System Properties to configure mixed
+ asynchronous and normal loggers
+
+
+
+
+
+
+ If one of the layouts is
+ configured with a location-related attribute like %line,
+ %location, %class or %method, Log4j will take a snapshot of the
+ stack, and walk the stack trace to find the location information.
+ This is an expensive operation: 1.3 - 5 times slower for
+ synchronous loggers. Synchronous loggers wait as
+ long as possible before they take this stack snapshot. If no
+ location is required, the snapshot will never be taken. However,
+ asynchronous loggers need to make this decision before passing the
+ log message to another thread; the location information will be
+ lost after that point.
+
+ The performance impact of taking a stack trace snapshot is even
+ higher for asynchronous loggers: logging with location is
+ 4 - 20 times slower than without location.
+ For this reason, asynchronous loggers and asynchronous
+ appenders do not include location information by default.
+
+ You can override the default behaviour in your logger
+ or asynchronous appender configuration
+ by specifying
+ includeLocation="true".
+
+
+
+
+
+
+
+ The FastFileAppender is similar to the standard
+ FileAppender
+ except it is always buffered (this cannot be switched off)
+ and internally it uses a
+ ByteBuffer + RandomAccessFile
+ instead of a
+ BufferedOutputStream.
+ We saw a 10-30% performance improvement compared to
+ FileAppender with "bufferedIO=true" in our
+ measurements.
+ Similar to the FileAppender,
+ FastFileAppender uses a FastFileManager to actually perform the
+ file I/O. While FastFileAppender
+ from different Configurations
+ cannot be shared, the FastFileManagers can be if the Manager is
+ accessible. For example, two webapps in a
+ servlet container can have
+ their own configuration and safely
+ write to the same file if Log4j
+ is in a ClassLoader that is common to
+ both of them.
+
+
+
+ | Parameter Name |
+ Type |
+ Description |
+
+
+ | append |
+ boolean |
+ When true - the default, records will be appended to the end
+ of the file. When set to false,
+ the file will be cleared before
+ new records are written.
+ |
+
+
+ | fileName |
+ String |
+ The name of the file to write to. If the file, or any of its
+ parent directories, do not exist,
+ they will be created.
+ |
+
+
+ | filters |
+ Filter |
+ A Filter to determine if the event should be handled by this
+ Appender. More than one Filter
+ may be used by using a CompositeFilter.
+ |
+
+
+ | immediateFlush |
+ boolean |
+ When set to true, each write will be followed by a flush.
+ This will guarantee the data is written
+ to disk but could impact performance.
+ This option is only necessary when using this
+ appender with synchronous loggers. Asynchronous loggers will
+ automatically flush at the end of a batch, which also guarantees
+ the data is written to disk but is more efficient.
+ |
+
+
+ | layout |
+ Layout |
+ The Layout to use to format the LogEvent |
+
+
+ | name |
+ String |
+ The name of the Appender. |
+
+
+ | suppressExceptions |
+ boolean |
+ The default is true, causing exceptions to be internally
+ logged and then ignored. When set to
+ false exceptions will be
+ percolated to the caller.
+ |
+
+ FastFileAppender Parameters
+
+
+ Here is a sample FastFile configuration:
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+ The FastRollingFileAppender is similar to the standard
+ RollingFileAppender
+ except it is always buffered (this cannot be switched off)
+ and
+ internally it uses a
+ ByteBuffer + RandomAccessFile
+ instead of a
+ BufferedOutputStream.
+
+ The FastRollingFileAppender writes
+ to the File named in the
+ fileName parameter
+ and rolls the file over according the
+ TriggeringPolicy
+ and the RolloverPolicy.
+
+ Similar to the RollingFileAppender,
+ FastRollingFileAppender uses a FastRollingFileManager
+ to actually perform the
+ file I/O and perform the rollover. While FastRollingFileAppender
+ from different Configurations cannot be
+ shared, the FastRollingFileManagers can be
+ if the Manager is accessible.
+ For example, two webapps in a servlet
+ container can have their own configuration and safely write to the
+ same file if Log4j is in a ClassLoader that is common to both of them.
+
+
+ A FastRollingFileAppender requires a
+ TriggeringPolicy
+ and a
+ RolloverStrategy.
+ The triggering policy determines if a rollover should
+ be performed
+ while the RolloverStrategy defines how the rollover
+ should be done.
+ If no RolloverStrategy
+ is configured, FastRollingFileAppender will
+ use the
+ DefaultRolloverStrategy.
+
+
+ File locking is not supported by the FastRollingFileAppender.
+
+
+
+ | Parameter Name |
+ Type |
+ Description |
+
+
+ | append |
+ boolean |
+ When true - the default, records will be appended to the end
+ of the file. When set to false,
+ the file will be cleared before
+ new records are written.
+ |
+
+
+ | filter |
+ Filter |
+ A Filter to determine if the event should be handled by this
+ Appender. More than one Filter
+ may be used by using a
+ CompositeFilter.
+ |
+
+
+ | fileName |
+ String |
+ The name of the file to write to. If the file, or any of its
+ parent directories, do not exist,
+ they will be created.
+ |
+
+
+ | filePattern |
+ String |
+
+ The pattern of the file name of the archived log file. The format
+ of the pattern should is
+ dependent on the RolloverPolicy that is
+ used. The DefaultRolloverPolicy
+ will accept both
+ a date/time
+ pattern compatible with
+
+ SimpleDateFormat
+
+ and/or a %i which represents an integer counter. The pattern
+ also supports interpolation at
+ runtime so any of the Lookups (such
+ as the
+ DateLookup
+ can
+ be included in the pattern.
+ |
+
+
+ | immediateFlush |
+ boolean |
+ When set to true, each write will be followed by a flush. This
+ will guarantee the data is written
+ to disk but could impact
+ performance.
+ This option is only necessary when using this
+ appender with
+ synchronous loggers. Asynchronous loggers will
+ automatically
+ flush at the end of a batch, which also guarantees
+ the data
+ is written to disk but is more efficient.
+ |
+
+
+ | layout |
+ Layout |
+ The Layout to use to format the LogEvent |
+
+
+
+ | name |
+ String |
+ The name of the Appender. |
+
+
+ | policy |
+ TriggeringPolicy |
+ The policy to use to determine if a rollover should occur.
+ |
+
+
+ | strategy |
+ RolloverStrategy |
+ The strategy to use to determine the name and location of the
+ archive file.
+ |
+
+
+ | suppressExceptions |
+ boolean |
+ The default is true, causing exceptions to be internally
+ logged and then ignored. When set to
+ false exceptions will be
+ percolated to the caller.
+ |
+
+ FastRollingFileAppender Parameters
+
+
+ Triggering Policies
+
+ See
+ RollingFileAppender Triggering Policies.
+
+
+ Rollover Strategies
+
+ See
+ RollingFileAppender Rollover Strategies.
+
+
+
+ Below is a sample configuration that uses a FastRollingFileAppender
+ with both the time and size based
+ triggering policies, will create
+ up to 7 archives on the same day (1-7) that
+ are stored in a
+ directory
+ based on the current year and month, and will compress
+ each
+ archive using gzip:
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+ This second example shows a rollover strategy that will keep up to
+ 20 files before removing them.
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+ Below is a sample configuration that uses a FastRollingFileAppender
+ with both the time and size based
+ triggering policies, will create
+ up to 7 archives on the same day (1-7) that
+ are stored in a
+ directory
+ based on the current year and month, and will compress
+ each
+ archive using gzip and will roll every 6 hours when the hour is
+ divisible
+ by 6:
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+ This section shows the results of our performance tests.
+ The
+ methodology used was the same for all tests:
+
+
+ - First, warm up the JVM by logging 200,000 log messages of 500
+ characters.
+ - Repeat the warm-up 10 times, then wait 10 seconds for the I/O thread to catch up and buffers to drain.
+ - Latency test: at less than saturation, measure how
+ long a call to Logger.log takes.
+ Pause for 10 microseconds * threadCount between measurements.
+ Repeat this 5 million times, and measure average latency,
+ latency of 99% of observations and 99.99% of observations.
+ - Throughput test: measure how long it takes to log 256 *
+ 1024 / threadCount messages of 500 characters.
+
+ Logging Throughput
+ On Windows 7 (64bit) with JDK1.7.0_11, 2-core Intel i5-3317u CPU
+ @1.70Ghz with hyperthreading switched on (4 virtual cores):
+
+
+ | Logger |
+ 1 thread |
+ 2 threads |
+ 4 threads |
+ 8 threads |
+ 16 threads |
+ 32 threads |
+
+
+ | Log4j2: Loggers all asynchronous |
+ 1,655,952 |
+ 928,951 |
+ 1,045,265 |
+ 1,509,109 |
+ 1,708,989 |
+ 773,565 |
+
+
+ | Log4j2: Loggers mixed sync/async |
+ 534,592 |
+ 1,204,774 |
+ 1,632,204 |
+ 1,368,041 |
+ 462,093 |
+ 908,529 |
+
+
+ | Log4j2: Async Appender |
+ 1,091,563 |
+ 1,006,287 |
+ 511,571 |
+ 302,230 |
+ 160,094 |
+ 60,152 |
+
+
+ | Log4j1: Async Appender |
+ 1,336,667 |
+ 911,657 |
+ 636,899 |
+ 406,405 |
+ 202,777 |
+ 162,964 |
+
+
+ | Logback: Async Appender |
+ 2,231,044 |
+ 783,722 |
+ 582,935 |
+ 289,905 |
+ 172,463 |
+ 133,435 |
+
+
+ | Log4j2: Synchronous |
+ 281,250 |
+ 225,731 |
+ 129,015 |
+ 66,590 |
+ 34,401 |
+ 17,347 |
+
+
+ | Log4j1: Synchronous |
+ 147,824 |
+ 72,383 |
+ 32,865 |
+ 18,025 |
+ 8,937 |
+ 4,440 |
+
+
+ | Logback: Synchronous |
+ 149,811 |
+ 66,301 |
+ 32,341 |
+ 16,962 |
+ 8,431 |
+ 3,610 |
+
+ Throughput per thread in messages/second
+
+
+
+ On Solaris 10 (64bit) with JDK1.7.0_06, 4-core Xeon X5570 dual CPU
+ @2.93Ghz with hyperthreading switched on (16 virtual cores):
+
+
+ | Logger |
+ 1 thread |
+ 2 threads |
+ 4 threads |
+ 8 threads |
+ 16 threads |
+ 32 threads |
+ 64 threads |
+
+
+ | Log4j2: Loggers all asynchronous |
+ 2,652,412 |
+ 909,119 |
+ 776,993 |
+ 516,365 |
+ 239,246 |
+ 253,791 |
+ 288,997 |
+
+
+ | Log4j2: Loggers mixed sync/async |
+ 2,454,358 |
+ 839,394 |
+ 854,578 |
+ 597,913 |
+ 261,003 |
+ 216,863 |
+ 218,937 |
+
+
+ | Log4j2: Async Appender |
+ 1,713,429 |
+ 603,019 |
+ 331,506 |
+ 149,408 |
+ 86,107 |
+ 45,529 |
+ 23,980 |
+
+
+ | Log4j1: Async Appender |
+ 2,239,664 |
+ 494,470 |
+ 221,402 |
+ 109,314 |
+ 60,580 |
+ 31,706 |
+ 14,072 |
+
+
+ | Logback: Async Appender |
+ 2,206,907 |
+ 624,082 |
+ 307,500 |
+ 160,096 |
+ 85,701 |
+ 43,422 |
+ 21,303 |
+
+
+ | Log4j2: Synchronous |
+ 273,536 |
+ 136,523 |
+ 67,609 |
+ 34,404 |
+ 15,373 |
+ 7,903 |
+ 4,253 |
+
+
+ | Log4j1: Synchronous |
+ 326,894 |
+ 105,591 |
+ 57,036 |
+ 30,511 |
+ 13,900 |
+ 7,094 |
+ 3,509 |
+
+
+ | Logback: Synchronous |
+ 178,063 |
+ 65,000 |
+ 34,372 |
+ 16,903 |
+ 8,334 |
+ 3,985 |
+ 1,967 |
+
+ Throughput per thread in messages/second
+
+
+
+ In the above two environments, with the
+ default settings (SystemClock and SleepingWaitStrategy),
+ asynchronous logging
+ processes 6 - 68 times the number of log events in the same time
+ as synchronous logging. These numbers may also give you an
+ idea of the performance trade-off when choosing between
+ All Async and the more flexible
+ Mixed Async logging.
+
+ Note that the numbers above are throughput per thread.
+ The graph below shows the total throughput of all threads together.
+ 
+
+ Throughput of Logging With Location (includeLocation="true")
+ On Solaris 10 (64bit) with JDK1.7.0_06, 4-core Xeon X5570 dual CPU
+ @2.93Ghz with hyperthreading switched off (8 virtual cores):
+
+
+ | Logger |
+ 1 thread |
+ 2 threads |
+ 4 threads |
+ 8 threads |
+
+
+ | Loggers all asynchronous |
+ 75,862 |
+ 88,775 |
+ 80,240 |
+ 68,077 |
+
+
+ | Loggers mixed sync/async |
+ 61,993 |
+ 66,164 |
+ 55,735 |
+ 52,843 |
+
+
+ | Async Appender |
+ 47,033 |
+ 52,426 |
+ 50,882 |
+ 36,905 |
+
+
+ | Synchronous |
+ 31,054 |
+ 33,175 |
+ 29,791 |
+ 23,628 |
+
+ Throughput in log messages/second
+ per thread
+
+ As expected, logging location information
+ has a large performance impact. Asynchronous loggers are 4 - 20
+ times slower, while synchronous loggers are 1.3 - 5 times slower.
+ However, if you do need location information,
+ asynchronous logging will still be faster than synchronous logging.
+
+
+ Latency
+ The latency comparison below is done by logging at
+ less than saturation, measuring how long a call to Logger.log
+ takes to return. After each call to Logger.log, the test waits
+ for 10 microseconds * threadCount before continuing.
+ Each thread logs 5 million messages.
+
+
+
+
+
+ The scale of the latency comparison graphs is logarithmic, not linear.
+ That makes it a bit difficult to see, but the average latency of
+ asynchronous loggers is half to one third of the latency of
+ ArrayBlockingQueue-based asynchronous appenders in scenarios
+ up to 8 threads. With more threads, the average latency of
+ asynchronous appenders is orders of magnitude larger than
+ asynchronous loggers.
+
+
+ Applications interested in low latency often care not only about
+ average latency, but also about worst-case latency.
+ The graph below shows that asynchronous loggers also do
+ better when comparing the maximum latency of 99.99% of
+ observations with other logging methods.
+ The worst case latency of asynchronous loggers remains more or
+ less the same (around 10-20 microseconds) in multi-threaded scenarios where
+ ArrayBlockingQueue-based asynchronous appenders have more outliers
+ with latencies of over 100 milliseconds per call to Logger.log.
+
+
+
+
+
+
+ FileAppender vs. FastFileAppender
+ On Windows 7 (64bit) with JDK1.7.0_11, 2-core Intel i5-3317u CPU
+ @1.70Ghz with hyperthreading switched on (4 virtual cores):
+
+
+ | Appender |
+ 1 thread |
+ 2 threads |
+ 4 threads |
+ 8 threads |
+
+
+ | FastFileAppender |
+ 250,438 |
+ 169,939 |
+ 109,074 |
+ 58,845 |
+
+
+ | FileAppender |
+ 186,695 |
+ 118,587 |
+ 57,012 |
+ 28,846 |
+
+
+ | RollingFastFileAppender |
+ 104,254 |
+ 39,345 |
+ 16,971 |
+ 9,465 |
+
+
+ | RollingFileAppender |
+ 182,518 |
+ 114,690 |
+ 55,147 |
+ 28,153 |
+
+ Throughput per thread in operations/second
+
+
+ On Solaris 10 (64bit) with JDK1.7.0_06, 4-core Xeon X5570 CPU
+ @2.93GHz with hyperthreading switched on (8 virtual cores):
+
+
+ | Appender |
+ 1 thread |
+ 2 threads |
+ 4 threads |
+ 8 threads |
+
+
+ | FastFileAppender |
+ 240,760 |
+ 128,713 |
+ 66,555 |
+ 30,544 |
+
+
+ | FileAppender |
+ 172,517 |
+ 106,587 |
+ 55,885 |
+ 25,675 |
+
+
+ | RollingFastFileAppender |
+ 78,715 |
+ 31,426 |
+ 17,881 |
+ 8,727 |
+
+
+ | RollingFileAppender |
+ 186,422 |
+ 97,737 |
+ 55,766 |
+ 25,097 |
+
+ Throughput per thread in operations/second
+
+
+
+
+
+
+
+ Asynchronous Loggers are implemented using the
+ LMAX Disruptor
+ inter-thread messaging library. From the LMAX web site:
+
+
+ ... using queues to pass
+ data between stages of the system was
+ introducing latency, so we
+ focused on optimising this area.
+ The Disruptor is the result of our research and
+ testing. We found that cache misses at the
+ CPU-level, and locks requiring kernel
+ arbitration are both extremely costly, so we created a
+ framework which has "mechanical sympathy" for
+ the hardware it's running on, and that's lock-free.
+
+
+
+
+ LMAX Disruptor internal performance comparisons with
+ java.util.concurrent.ArrayBlockingQueue
+ can be found
+
+ here.
+
+
+
+
+
\ No newline at end of file