You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rg...@apache.org on 2013/04/18 01:35:47 UTC
svn commit: r1469103 [1/3] - in /logging/log4j/log4j2/trunk/core/src:
main/java/org/apache/logging/log4j/core/appender/
main/java/org/apache/logging/log4j/core/appender/rolling/
main/java/org/apache/logging/log4j/core/async/ test/java/org/apache/loggin...
Author: rgoers
Date: Wed Apr 17 23:35:45 2013
New Revision: 1469103
URL: http://svn.apache.org/r1469103
Log:
LOG4J2-208 - Merge async support into core
Added:
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastFileAppender.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastFileManager.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastRollingFileAppender.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FastRollingFileManager.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigHelper.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelector.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/CachedClock.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/Clock.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/ClockFactory.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/CoarseCachedClock.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventHandler.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/SystemClock.java
logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/package-info.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/FastFileAppenderLocationTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/FastFileAppenderTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/FastRollingFileAppenderLocationTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/FastRollingFileAppenderRolloverTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/FastRollingFileAppenderTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerLocationTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/CachedClockTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/ClockFactoryTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/SystemClockTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/perftest/
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/perftest/IPerfTestRunner.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/perftest/MTPerfTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java
logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java
logging/log4j/log4j2/trunk/core/src/test/resources/AsyncLoggerConfigTest.xml
logging/log4j/log4j2/trunk/core/src/test/resources/AsyncLoggerLocationTest.xml
logging/log4j/log4j2/trunk/core/src/test/resources/AsyncLoggerTest.xml
logging/log4j/log4j2/trunk/core/src/test/resources/FastFileAppenderLocationTest.xml
logging/log4j/log4j2/trunk/core/src/test/resources/FastFileAppenderTest.xml
logging/log4j/log4j2/trunk/core/src/test/resources/FastRollingFileAppenderLocationTest.xml
logging/log4j/log4j2/trunk/core/src/test/resources/FastRollingFileAppenderTest.xml
logging/log4j/log4j2/trunk/core/src/test/resources/log4j.dtd
logging/log4j/log4j2/trunk/core/src/test/resources/perf-log4j12-async.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf-log4j12.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf-logback-async.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf-logback.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf1syncFastFile.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf1syncFile.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf2syncRollFastFile.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf2syncRollFile.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf3PlainNoLoc.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf4PlainLocation.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf5AsyncApndNoLoc.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf6AsyncApndLoc.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf7MixedNoLoc.xml
logging/log4j/log4j2/trunk/core/src/test/resources/perf8MixedLoc.xml
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastFileAppender.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastFileAppender.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastFileAppender.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastFileAppender.java Wed Apr 17 23:35:45 2013
@@ -0,0 +1,162 @@
+/*
+ * 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 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.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<String, String> configuration = new HashMap<String, String>(
+ 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.
+ ((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);
+ }
+}
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastFileManager.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastFileManager.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastFileManager.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastFileManager.java Wed Apr 17 23:35:45 2013
@@ -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.core.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;
+
+/**
+ * Extends OutputStreamManager but instead of using a buffered output stream,
+ * this class uses a {@code ByteBuffer} and a {@code RandomAccessFile} to do the
+ * I/O.
+ */
+public class FastFileManager extends OutputStreamManager {
+ private static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
+
+ 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<Boolean> isEndOfBatch = new ThreadLocal<Boolean>();
+
+ 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);
+
+ // TODO make buffer size configurable?
+ buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * 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();
+ }
+
+ @Override
+ public void close() {
+ flush();
+ try {
+ randomAccessFile.close();
+ } catch (final IOException ex) {
+ LOGGER.error("Unable to close RandomAccessFile " + getName() + ". "
+ + ex);
+ }
+ }
+
+ /**
+ * Returns the name of the File being managed.
+ *
+ * @return The name of the File being managed.
+ */
+ public String getFileName() {
+ return getName();
+ }
+
+ /** {@code OutputStream} subclass that does not write anything. */
+ 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:
+ * <p/>
+ * Key: "fileURI" Value: provided "advertiseURI" param.
+ *
+ * @return Map of content format keys supporting FileManager
+ */
+ public Map<String, String> getContentFormat() {
+ Map<String, String> result = new HashMap<String, String>(
+ 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<FastFileManager, FactoryData> {
+
+ /**
+ * 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;
+ }
+ }
+
+}
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastRollingFileAppender.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastRollingFileAppender.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastRollingFileAppender.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/FastRollingFileAppender.java Wed Apr 17 23:35:45 2013
@@ -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.core.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.rolling.DefaultRolloverStrategy;
+import org.apache.logging.log4j.core.appender.rolling.FastRollingFileManager;
+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<String, String> configuration = new HashMap<String, String>(
+ 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) {
+ FastRollingFileManager manager = (FastRollingFileManager) getManager();
+ manager.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.
+ manager.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);
+ }
+}
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FastRollingFileManager.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FastRollingFileManager.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FastRollingFileManager.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FastRollingFileManager.java Wed Apr 17 23:35:45 2013
@@ -0,0 +1,201 @@
+/*
+ * 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.rolling;
+
+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;
+
+/**
+ * Extends RollingFileManager but instead of using a buffered output stream,
+ * this class uses a {@code ByteBuffer} and a {@code RandomAccessFile} to do the
+ * I/O.
+ */
+public class FastRollingFileManager extends RollingFileManager {
+ private static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
+
+ private static final FastRollingFileManagerFactory FACTORY = new FastRollingFileManagerFactory();
+
+ private final boolean isImmediateFlush;
+ private RandomAccessFile randomAccessFile;
+ private final ByteBuffer buffer;
+ private ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<Boolean>();
+
+ 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);
+
+ // TODO make buffer size configurable?
+ buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
+ }
+
+ 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
+ protected void createFileAfterRollover() throws IOException {
+ this.randomAccessFile = new RandomAccessFile(getFileName(), "rw");
+ if (isAppend()) {
+ randomAccessFile.seek(randomAccessFile.length());
+ }
+ }
+
+ @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();
+ }
+
+ @Override
+ public void close() {
+ flush();
+ try {
+ randomAccessFile.close();
+ } catch (final IOException ex) {
+ LOGGER.error("Unable to close RandomAccessFile " + getName() + ". "
+ + ex);
+ }
+ }
+
+ /**
+ * Factory to create a FastRollingFileManager.
+ */
+ private static class FastRollingFileManagerFactory implements
+ ManagerFactory<FastRollingFileManager, FactoryData> {
+
+ /**
+ * 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;
+ }
+ }
+
+ /** {@code OutputStream} subclass that does not write anything. */
+ 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;
+ }
+ }
+
+}
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java Wed Apr 17 23:35:45 2013
@@ -0,0 +1,264 @@
+/*
+ * 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.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. (<a
+ * href="http://lmax-exchange.github.com/disruptor/"
+ * >http://lmax-exchange.github.com/disruptor/</a>)
+ * <p>
+ * To use AsyncLogger, specify the System property
+ * {@code -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector}
+ * before you obtain a Logger, and all Loggers returned by LogManager.getLogger
+ * will be AsyncLoggers.
+ * <p>
+ * Note that for performance reasons, this logger does not include source
+ * location by default. You need to specify {@code includeLocation="true"} in
+ * the configuration or any %class, %location or %line conversion patterns in
+ * your log4j.xml configuration will produce either a "?" character or no output
+ * at all.
+ * <p>
+ * 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 {
+ private static final int HALF_A_SECOND = 500;
+ private static final int MAX_DRAIN_ATTEMPTS_BEFORE_SHUTDOWN = 20;
+ private static final int RINGBUFFER_MIN_SIZE = 128;
+ private static final int RINGBUFFER_DEFAULT_SIZE = 256 * 1024;
+ private static final StatusLogger LOGGER = StatusLogger.getLogger();
+
+ private static volatile Disruptor<RingBufferLogEvent> disruptor;
+ private static Clock clock = ClockFactory.getClock();
+
+ private static ExecutorService executor = Executors
+ .newSingleThreadExecutor();
+ private ThreadLocal<Info> threadlocalInfo = new ThreadLocal<Info>();
+
+ static {
+ int ringBufferSize = calculateRingBufferSize();
+
+ WaitStrategy waitStrategy = createWaitStrategy();
+ disruptor = new Disruptor<RingBufferLogEvent>(
+ RingBufferLogEvent.FACTORY, ringBufferSize, executor,
+ ProducerType.MULTI, waitStrategy);
+ EventHandler<RingBufferLogEvent>[] 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() {
+ int ringBufferSize = RINGBUFFER_DEFAULT_SIZE;
+ String userPreferredRBSize = System.getProperty(
+ "AsyncLogger.RingBufferSize", String.valueOf(ringBufferSize));
+ try {
+ int size = Integer.parseInt(userPreferredRBSize);
+ if (size < RINGBUFFER_MIN_SIZE) {
+ size = RINGBUFFER_MIN_SIZE;
+ LOGGER.warn(
+ "Invalid RingBufferSize {}, using minimum size {}.",
+ userPreferredRBSize, RINGBUFFER_MIN_SIZE);
+ }
+ ringBufferSize = size;
+ } catch (Exception ex) {
+ LOGGER.warn("Invalid RingBufferSize {}, using default size {}.",
+ userPreferredRBSize, ringBufferSize);
+ }
+ 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;
+ }
+ }
+
+ /**
+ * Constructs an {@code AsyncLogger} with the specified context, name and
+ * message factory.
+ *
+ * @param context context of this logger
+ * @param name name of this logger
+ * @param messageFactory message factory of this logger
+ */
+ public AsyncLogger(LoggerContext context, String name,
+ MessageFactory messageFactory) {
+ super(context, name, messageFactory);
+ }
+
+ /**
+ * Tuple with the event translator and thread name for a thread.
+ */
+ private static class Info {
+ private RingBufferLogEventTranslator translator;
+ private String cachedThreadName;
+ }
+
+ @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<Property, Boolean> properties = config.loggerConfig.getProperties();
+ event.mergePropertiesIntoContextMap(properties,
+ config.config.getSubst());
+ config.logEvent(event);
+ }
+
+ public static void stop() {
+ Disruptor<RingBufferLogEvent> 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<RingBufferLogEvent> ringBuffer = temp.getRingBuffer();
+ for (int i = 0; i < MAX_DRAIN_ATTEMPTS_BEFORE_SHUTDOWN; i++) {
+ if (ringBuffer.hasAvailableCapacity(ringBuffer.getBufferSize())) {
+ break;
+ }
+ try {
+ // give ringbuffer some time to drain...
+ Thread.sleep(HALF_A_SECOND);
+ } catch (InterruptedException e) {
+ // ignored
+ }
+ }
+ executor.shutdown(); // finally, kill the processor thread
+ }
+}
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java Wed Apr 17 23:35:45 2013
@@ -0,0 +1,228 @@
+/*
+ * 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.async;
+
+import java.util.Arrays;
+import java.util.List;
+
+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;
+
+/**
+ * Asynchronous Logger object that is created via configuration and can be
+ * combined with synchronous loggers.
+ * <p>
+ * AsyncLoggerConfig 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. (<a
+ * href="http://lmax-exchange.github.com/disruptor/"
+ * >http://lmax-exchange.github.com/disruptor/</a>)
+ * <p>
+ * To use AsyncLoggerConfig, specify {@code <asyncLogger>} or
+ * {@code <asyncRoot>} in configuration.
+ * <p>
+ * Note that for performance reasons, this logger does not include source
+ * location by default. You need to specify {@code includeLocation="true"} in
+ * the configuration or any %class, %location or %line conversion patterns in
+ * your log4j.xml configuration will produce either a "?" character or no output
+ * at all.
+ * <p>
+ * For best performance, use AsyncLoggerConfig 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.
+ */
+@Plugin(name = "asyncLogger", type = "Core", printObject = true)
+public class AsyncLoggerConfig extends LoggerConfig {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private AsyncLoggerConfigHelper helper;
+
+ /**
+ * 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<AppenderRef> 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
+ helper.callAppendersFromAnotherThread(event);
+ }
+
+ /** Called by AsyncLoggerConfigHelper.RingBufferLog4jEventHandler. */
+ void asyncCallAppenders(LogEvent event) {
+ super.callAppenders(event);
+ }
+
+ @Override
+ public void startFilter() {
+ if (helper == null) {
+ helper = new AsyncLoggerConfigHelper(this);
+ }
+ super.startFilter();
+ }
+
+ @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;
+ }
+ }
+ helper.shutdown();
+ 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<AppenderRef> 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<AppenderRef> 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));
+ }
+ }
+}
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigHelper.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigHelper.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigHelper.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigHelper.java Wed Apr 17 23:35:45 2013
@@ -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.core.async;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LogEvent;
+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.Sequence;
+import com.lmax.disruptor.SequenceReportingEventHandler;
+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;
+
+/**
+ * Helper class decoupling the {@code AsyncLoggerConfig} class from the LMAX
+ * Disruptor library.
+ * <p>
+ * {@code AsyncLoggerConfig} is a plugin, and will be loaded even if users do
+ * not configure any {@code <asyncLogger>} or {@code <asyncRoot>} elements in
+ * the configuration. If {@code AsyncLoggerConfig} has inner classes that extend
+ * or implement classes from the Disruptor library, a
+ * {@code NoClassDefFoundError} is thrown if the Disruptor jar is not in the
+ * classpath when the PluginManager loads the {@code AsyncLoggerConfig} plugin
+ * from the pre-defined plugins definition file.
+ * <p>
+ * This class serves to make the dependency on the Disruptor optional, so that
+ * these classes are only loaded when the {@code AsyncLoggerConfig} is actually
+ * used.
+ */
+class AsyncLoggerConfigHelper {
+
+ private static final int MAX_DRAIN_ATTEMPTS_BEFORE_SHUTDOWN = 20;
+ private static final int HALF_A_SECOND = 500;
+ private static final int RINGBUFFER_MIN_SIZE = 128;
+ private static final int RINGBUFFER_DEFAULT_SIZE = 256 * 1024;
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private static volatile Disruptor<RingBufferLog4jEvent> disruptor;
+ private static ExecutorService executor = Executors
+ .newSingleThreadExecutor();
+
+ /**
+ * 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<RingBufferLog4jEvent> FACTORY = new EventFactory<RingBufferLog4jEvent>() {
+ @Override
+ public RingBufferLog4jEvent newInstance() {
+ return new RingBufferLog4jEvent();
+ }
+ };
+
+ /**
+ * Object responsible for passing on data to a specific RingBuffer event.
+ */
+ private final EventTranslator<RingBufferLog4jEvent> translator = new EventTranslator<RingBufferLog4jEvent>() {
+ @Override
+ public void translateTo(RingBufferLog4jEvent event, long sequence) {
+ event.event = currentLogEvent.get();
+ event.loggerConfig = asyncLoggerConfig;
+ }
+ };
+
+ private ThreadLocal<LogEvent> currentLogEvent = new ThreadLocal<LogEvent>();
+ private AsyncLoggerConfig asyncLoggerConfig;
+
+ public AsyncLoggerConfigHelper(AsyncLoggerConfig asyncLoggerConfig) {
+ this.asyncLoggerConfig = asyncLoggerConfig;
+ initDisruptor();
+ }
+
+ private static synchronized void initDisruptor() {
+ if (disruptor != null) {
+ return;
+ }
+ int ringBufferSize = calculateRingBufferSize();
+ WaitStrategy waitStrategy = createWaitStrategy();
+ disruptor = new Disruptor<RingBufferLog4jEvent>(FACTORY,
+ ringBufferSize, executor, ProducerType.MULTI, waitStrategy);
+ EventHandler<RingBufferLog4jEvent>[] handlers = new RingBufferLog4jEventHandler[] {//
+ new RingBufferLog4jEventHandler() };
+ disruptor.handleExceptionsWith(getExceptionHandler());
+ disruptor.handleEventsWith(handlers);
+
+ LOGGER.debug(
+ "Starting AsyncLoggerConfig disruptor with ringbuffer size {}...",
+ disruptor.getRingBuffer().getBufferSize());
+ disruptor.start();
+ }
+
+ private static 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() {
+ int ringBufferSize = RINGBUFFER_DEFAULT_SIZE;
+ String userPreferredRBSize = System.getProperty(
+ "AsyncLoggerConfig.RingBufferSize",
+ String.valueOf(ringBufferSize));
+ try {
+ int size = Integer.parseInt(userPreferredRBSize);
+ if (size < RINGBUFFER_MIN_SIZE) {
+ size = RINGBUFFER_MIN_SIZE;
+ LOGGER.warn(
+ "Invalid RingBufferSize {}, using minimum size {}.",
+ userPreferredRBSize, RINGBUFFER_MIN_SIZE);
+ }
+ ringBufferSize = size;
+ } catch (Exception ex) {
+ LOGGER.warn("Invalid RingBufferSize {}, using default size {}.",
+ userPreferredRBSize, ringBufferSize);
+ }
+ 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;
+ }
+ }
+
+ /**
+ * RingBuffer events contain all information necessary to perform the work
+ * in a separate thread.
+ */
+ private static class RingBufferLog4jEvent {
+ private AsyncLoggerConfig loggerConfig;
+ private LogEvent event;
+ }
+
+ /**
+ * EventHandler performs the work in a separate thread.
+ */
+ private static class RingBufferLog4jEventHandler implements
+ SequenceReportingEventHandler<RingBufferLog4jEvent> {
+ private static final int NOTIFY_PROGRESS_THRESHOLD = 50;
+ private Sequence sequenceCallback;
+ private int counter;
+
+ @Override
+ public void setSequenceCallback(Sequence sequenceCallback) {
+ this.sequenceCallback = sequenceCallback;
+ }
+
+ @Override
+ public void onEvent(RingBufferLog4jEvent event, long sequence,
+ boolean endOfBatch) throws Exception {
+ event.event.setEndOfBatch(endOfBatch);
+ event.loggerConfig.asyncCallAppenders(event.event);
+
+ // notify the BatchEventProcessor that the sequence has progressed.
+ // Without this callback the sequence would not be progressed
+ // until the batch has completely finished.
+ if (++counter > NOTIFY_PROGRESS_THRESHOLD) {
+ sequenceCallback.set(sequence);
+ counter = 0;
+ }
+ }
+ }
+
+ public void shutdown() {
+ Disruptor<RingBufferLog4jEvent> 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<RingBufferLog4jEvent> ringBuffer = temp.getRingBuffer();
+ for (int i = 0; i < MAX_DRAIN_ATTEMPTS_BEFORE_SHUTDOWN; i++) {
+ if (ringBuffer.hasAvailableCapacity(ringBuffer.getBufferSize())) {
+ break;
+ }
+ try {
+ // give ringbuffer some time to drain...
+ Thread.sleep(HALF_A_SECOND);
+ } catch (InterruptedException e) {
+ // ignored
+ }
+ }
+ executor.shutdown(); // finally, kill the processor thread
+ }
+
+ public void callAppendersFromAnotherThread(LogEvent event) {
+ currentLogEvent.set(event);
+ disruptor.publishEvent(translator);
+ }
+
+}
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java Wed Apr 17 23:35:45 2013
@@ -0,0 +1,59 @@
+/*
+ * 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.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;
+
+/**
+ * {@code LoggerContext} that creates {@code AsyncLogger} objects.
+ */
+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();
+ }
+}
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelector.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelector.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelector.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelector.java Wed Apr 17 23:35:45 2013
@@ -0,0 +1,51 @@
+/*
+ * 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.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;
+
+/**
+ * {@code ContextSelector} that returns the singleton {@code AsyncLoggerContext}.
+ */
+public class AsyncLoggerContextSelector implements ContextSelector {
+
+ private static final AsyncLoggerContext CONTEXT = new AsyncLoggerContext(
+ "AsyncLoggerContext");
+
+ public LoggerContext getContext(String fqcn, ClassLoader loader,
+ boolean currentContext) {
+ return CONTEXT;
+ }
+
+ public List<LoggerContext> getLoggerContexts() {
+ List<LoggerContext> list = new ArrayList<LoggerContext>();
+ list.add(CONTEXT);
+ return Collections.unmodifiableList(list);
+ }
+
+ public LoggerContext getContext(String fqcn, ClassLoader loader,
+ boolean currentContext, URI configLocation) {
+ return CONTEXT;
+ }
+
+}
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/CachedClock.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/CachedClock.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/CachedClock.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/CachedClock.java Wed Apr 17 23:35:45 2013
@@ -0,0 +1,72 @@
+/*
+ * 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.async;
+
+import com.lmax.disruptor.util.Util;
+
+/**
+ * Implementation of the {@code Clock} interface that tracks the time in a
+ * private long field that is updated by a background thread once every
+ * millisecond. Timers on most platforms do not have millisecond granularity, so
+ * the returned value may "jump" every 10 or 16 milliseconds. To reduce this
+ * problem, this class also updates the internal time value every 1024 calls to
+ * {@code currentTimeMillis()}.
+ */
+public final class CachedClock implements Clock {
+ private static final int UPDATE_THRESHOLD = 0x3FF;
+ 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)
+ }
+ }
+ };
+
+ private CachedClock() {
+ updater.setDaemon(true);
+ updater.start();
+ }
+
+ public static CachedClock instance() {
+ return instance;
+ }
+
+ /**
+ * Returns the value of a private long field that is updated by a background
+ * thread once every millisecond. Timers on most platforms do not
+ * have millisecond granularity, the returned value may "jump" every 10 or
+ * 16 milliseconds. To reduce this problem, this method also updates the
+ * internal time value every 1024 calls.
+ * @return the cached time
+ */
+ @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 & UPDATE_THRESHOLD) == UPDATE_THRESHOLD) {
+ millis = System.currentTimeMillis();
+ }
+ return millis;
+ }
+}
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/Clock.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/Clock.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/Clock.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/Clock.java Wed Apr 17 23:35:45 2013
@@ -0,0 +1,29 @@
+/*
+ * 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.async;
+
+/**
+ * Interface for classes that can provide the time stamp used in log events.
+ */
+public interface Clock {
+ /**
+ * Returns the time in milliseconds since the epoch.
+ *
+ * @return the time in milliseconds since the epoch
+ */
+ long currentTimeMillis();
+}
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/ClockFactory.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/ClockFactory.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/ClockFactory.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/ClockFactory.java Wed Apr 17 23:35:45 2013
@@ -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.async;
+
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Factory for {@code Clock} objects.
+ */
+public final class ClockFactory {
+
+ /**
+ * Name of the system property that can be used to specify a {@code Clock}
+ * implementation class.
+ */
+ public static final String PROPERTY_NAME = "AsyncLogger.Clock";
+ private static final StatusLogger LOGGER = StatusLogger.getLogger();
+
+ // private static final Clock clock = createClock();
+
+ private ClockFactory() {
+ }
+
+ /**
+ * Returns a {@code Clock} instance depending on the value of system
+ * property {@code "AsyncLogger.Clock"}.
+ * <p>
+ * If system property {@code AsyncLogger.Clock=CachedClock} is specified,
+ * this method returns an instance of {@link CachedClock}. If system
+ * property {@code AsyncLogger.Clock=CoarseCachedClock} is specified, this
+ * method returns an instance of {@link CoarseCachedClock}.
+ * <p>
+ * If another value is specified, this value is taken as the fully qualified
+ * class name of a class that implements the {@code Clock} interface. An
+ * object of this class is instantiated and returned.
+ * <p>
+ * If no value is specified, or if the specified value could not correctly
+ * be instantiated or did not implement the {@code Clock} interface, then an
+ * instance of {@link SystemClock} is returned.
+ *
+ * @return a {@code Clock} instance
+ */
+ public static Clock getClock() {
+ return createClock();
+ }
+
+ private static Clock createClock() {
+ String userRequest = System.getProperty(PROPERTY_NAME);
+ if (userRequest == null || "SystemClock".equals(userRequest)) {
+ LOGGER.debug("Using default SystemClock for timestamps");
+ return new SystemClock();
+ }
+ if (CachedClock.class.getName().equals(userRequest) //
+ || "CachedClock".equals(userRequest)) {
+ LOGGER.debug("Using specified CachedClock for timestamps");
+ return CachedClock.instance();
+ }
+ if (CoarseCachedClock.class.getName().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();
+ }
+ }
+}
Added: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/CoarseCachedClock.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/CoarseCachedClock.java?rev=1469103&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/CoarseCachedClock.java (added)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/async/CoarseCachedClock.java Wed Apr 17 23:35:45 2013
@@ -0,0 +1,65 @@
+/*
+ * 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.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 final 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)
+ }
+ }
+ };
+
+ private CoarseCachedClock() {
+ updater.setDaemon(true);
+ updater.start();
+ }
+
+ /**
+ * Returns the singleton instance.
+ *
+ * @return the singleton instance
+ */
+ public static CoarseCachedClock instance() {
+ return instance;
+ }
+
+ /**
+ * Returns the value of a private long field that is updated by a background
+ * thread once every millisecond. Because timers on most platforms do not
+ * have millisecond granularity, the returned value may "jump" every 10 or
+ * 16 milliseconds.
+ * @return the cached time
+ */
+ @Override
+ public long currentTimeMillis() {
+ return millis;
+ }
+}