Deprecate org.apache.logging.log4j.core.util.NullOutputStream.NULL_OUTPUT_STREAM in favor of org.apache.logging.log4j.core.util.NullOutputStream.getInstance().
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/f73f89b1 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/f73f89b1 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/f73f89b1 Branch: refs/heads/LOG4J2-1349-gcfree-threadcontext Commit: f73f89b14731a0bf8e8d89053509056f1cc43075 Parents: d85a058 Author: Gary Gregory <ggreg...@apache.org> Authored: Wed Sep 7 20:54:19 2016 -0700 Committer: Gary Gregory <ggreg...@apache.org> Committed: Wed Sep 7 20:54:19 2016 -0700 ---------------------------------------------------------------------- .../core/appender/MemoryMappedFileManager.java | 718 +++++++++---------- .../core/appender/RandomAccessFileManager.java | 432 +++++------ .../rolling/RollingRandomAccessFileManager.java | 530 +++++++------- .../log4j/core/net/TcpSocketManager.java | 654 ++++++++--------- .../log4j/core/util/NullOutputStream.java | 9 +- .../appender/RandomAccessFileManagerTest.java | 8 +- .../RollingRandomAccessFileManagerTest.java | 6 +- 7 files changed, 1182 insertions(+), 1175 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f73f89b1/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManager.java index 332e8e4..13c5294 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManager.java @@ -1,359 +1,359 @@ -/* - * 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.io.Serializable; -import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.util.Closer; -import org.apache.logging.log4j.core.util.NullOutputStream; - -//Lines too long... -//CHECKSTYLE:OFF -/** - * Extends OutputStreamManager but instead of using a buffered output stream, this class maps a region of a file into - * memory and writes to this memory region. - * <p> - * - * @see <a href="http://www.codeproject.com/Tips/683614/Things-to-Know-about-Memory-Mapped-File-in-Java"> - * http://www.codeproject.com/Tips/683614/Things-to-Know-about-Memory-Mapped-File-in-Java</a> - * @see <a href="http://bugs.java.com/view_bug.do?bug_id=6893654">http://bugs.java.com/view_bug.do?bug_id=6893654</a> - * @see <a href="http://bugs.java.com/view_bug.do?bug_id=4724038">http://bugs.java.com/view_bug.do?bug_id=4724038</a> - * @see <a - * href="http://stackoverflow.com/questions/9261316/memory-mapped-mappedbytebuffer-or-direct-bytebuffer-for-db-implementation"> - * http://stackoverflow.com/questions/9261316/memory-mapped-mappedbytebuffer-or-direct-bytebuffer-for-db-implementation</a> - * - * @since 2.1 - */ -//CHECKSTYLE:ON -public class MemoryMappedFileManager extends OutputStreamManager { - /** - * Default length of region to map. - */ - static final int DEFAULT_REGION_LENGTH = 32 * 1024 * 1024; - private static final int MAX_REMAP_COUNT = 10; - private static final MemoryMappedFileManagerFactory FACTORY = new MemoryMappedFileManagerFactory(); - private static final double NANOS_PER_MILLISEC = 1000.0 * 1000.0; - - private final boolean isForce; - private final int regionLength; - private final String advertiseURI; - private final RandomAccessFile randomAccessFile; - private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>(); - private MappedByteBuffer mappedBuffer; - private long mappingOffset; - - protected MemoryMappedFileManager(final RandomAccessFile file, final String fileName, final OutputStream os, - final boolean force, final long position, final int regionLength, final String advertiseURI, - final Layout<? extends Serializable> layout, final boolean writeHeader) throws IOException { - super(os, fileName, layout, writeHeader, ByteBuffer.wrap(new byte[0])); - this.isForce = force; - this.randomAccessFile = Objects.requireNonNull(file, "RandomAccessFile"); - this.regionLength = regionLength; - this.advertiseURI = advertiseURI; - this.isEndOfBatch.set(Boolean.FALSE); - this.mappedBuffer = mmap(randomAccessFile.getChannel(), getFileName(), position, regionLength); - this.byteBuffer = mappedBuffer; - this.mappingOffset = position; - } - - /** - * Returns the MemoryMappedFileManager. - * - * @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 isForce true if the contents should be flushed to disk on every write - * @param regionLength The mapped region length. - * @param advertiseURI the URI to use when advertising the file - * @param layout The layout. - * @return A MemoryMappedFileManager for the File. - */ - public static MemoryMappedFileManager getFileManager(final String fileName, final boolean append, - final boolean isForce, final int regionLength, final String advertiseURI, - final Layout<? extends Serializable> layout) { - return (MemoryMappedFileManager) getManager(fileName, new FactoryData(append, isForce, regionLength, - advertiseURI, layout), FACTORY); - } - - public Boolean isEndOfBatch() { - return isEndOfBatch.get(); - } - - public void setEndOfBatch(final boolean endOfBatch) { - this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); - } - - @Override - protected synchronized void write(final byte[] bytes, int offset, int length, final boolean immediateFlush) { - while (length > mappedBuffer.remaining()) { - final int chunk = mappedBuffer.remaining(); - mappedBuffer.put(bytes, offset, chunk); - offset += chunk; - length -= chunk; - remap(); - } - mappedBuffer.put(bytes, offset, length); - - // no need to call flush() if force is true, - // already done in AbstractOutputStreamAppender.append - } - - private synchronized void remap() { - final long offset = this.mappingOffset + mappedBuffer.position(); - final int length = mappedBuffer.remaining() + regionLength; - try { - unsafeUnmap(mappedBuffer); - final long fileLength = randomAccessFile.length() + regionLength; - LOGGER.debug("{} {} extending {} by {} bytes to {}", getClass().getSimpleName(), getName(), getFileName(), - regionLength, fileLength); - - final long startNanos = System.nanoTime(); - randomAccessFile.setLength(fileLength); - final float millis = (float) ((System.nanoTime() - startNanos) / NANOS_PER_MILLISEC); - LOGGER.debug("{} {} extended {} OK in {} millis", getClass().getSimpleName(), getName(), getFileName(), - millis); - - mappedBuffer = mmap(randomAccessFile.getChannel(), getFileName(), offset, length); - this.byteBuffer = mappedBuffer; - mappingOffset = offset; - } catch (final Exception ex) { - logError("Unable to remap", ex); - } - } - - @Override - public synchronized void flush() { - mappedBuffer.force(); - } - - @Override - public synchronized boolean closeOutputStream() { - final long position = mappedBuffer.position(); - final long length = mappingOffset + position; - try { - unsafeUnmap(mappedBuffer); - } catch (final Exception ex) { - logError("Unable to unmap MappedBuffer", ex); - } - try { - LOGGER.debug("MMapAppender closing. Setting {} length to {} (offset {} + position {})", getFileName(), - length, mappingOffset, position); - randomAccessFile.setLength(length); - randomAccessFile.close(); - return true; - } catch (final IOException ex) { - logError("Unable to close MemoryMappedFile", ex); - return false; - } - } - - public static MappedByteBuffer mmap(final FileChannel fileChannel, final String fileName, final long start, - final int size) throws IOException { - for (int i = 1;; i++) { - try { - LOGGER.debug("MMapAppender remapping {} start={}, size={}", fileName, start, size); - - final long startNanos = System.nanoTime(); - final MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, start, size); - map.order(ByteOrder.nativeOrder()); - - final float millis = (float) ((System.nanoTime() - startNanos) / NANOS_PER_MILLISEC); - LOGGER.debug("MMapAppender remapped {} OK in {} millis", fileName, millis); - - return map; - } catch (final IOException e) { - if (e.getMessage() == null || !e.getMessage().endsWith("user-mapped section open")) { - throw e; - } - LOGGER.debug("Remap attempt {}/{} failed. Retrying...", i, MAX_REMAP_COUNT, e); - if (i < MAX_REMAP_COUNT) { - Thread.yield(); - } else { - try { - Thread.sleep(1); - } catch (final InterruptedException ignored) { - Thread.currentThread().interrupt(); - throw e; - } - } - } - } - } - - private static void unsafeUnmap(final MappedByteBuffer mbb) throws PrivilegedActionException { - LOGGER.debug("MMapAppender unmapping old buffer..."); - final long startNanos = System.nanoTime(); - AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { - @Override - public Object run() throws Exception { - final Method getCleanerMethod = mbb.getClass().getMethod("cleaner"); - getCleanerMethod.setAccessible(true); - final Object cleaner = getCleanerMethod.invoke(mbb); // sun.misc.Cleaner instance - final Method cleanMethod = cleaner.getClass().getMethod("clean"); - cleanMethod.invoke(cleaner); - return null; - } - }); - final float millis = (float) ((System.nanoTime() - startNanos) / NANOS_PER_MILLISEC); - LOGGER.debug("MMapAppender unmapped buffer OK in {} millis", millis); - } - - /** - * Returns the name of the File being managed. - * - * @return The name of the File being managed. - */ - public String getFileName() { - return getName(); - } - - /** - * Returns the length of the memory mapped region. - * - * @return the length of the mapped region - */ - public int getRegionLength() { - return regionLength; - } - - /** - * Returns {@code true} if the content of the buffer should be forced to the storage device on every write, - * {@code false} otherwise. - * - * @return whether each write should be force-sync'ed - */ - public boolean isImmediateFlush() { - return isForce; - } - - /** - * Gets this FileManager's content format specified by: - * <p> - * Key: "fileURI" Value: provided "advertiseURI" param. - * </p> - * - * @return Map of content format keys supporting FileManager - */ - @Override - public Map<String, String> getContentFormat() { - final Map<String, String> result = new HashMap<>(super.getContentFormat()); - result.put("fileURI", advertiseURI); - return result; - } - - @Override - protected void flushBuffer(final ByteBuffer buffer) { - // do nothing (do not call drain() to avoid spurious remapping) - } - - @Override - public ByteBuffer getByteBuffer() { - return mappedBuffer; - } - - @Override - public ByteBuffer drain(final ByteBuffer buf) { - remap(); - return mappedBuffer; - } - - /** - * Factory Data. - */ - private static class FactoryData { - private final boolean append; - private final boolean force; - private final int regionLength; - private final String advertiseURI; - private final Layout<? extends Serializable> layout; - - /** - * Constructor. - * - * @param append Append to existing file or truncate. - * @param force forces the memory content to be written to the storage device on every event - * @param regionLength length of the mapped region - * @param advertiseURI the URI to use when advertising the file - * @param layout The layout. - */ - public FactoryData(final boolean append, final boolean force, final int regionLength, - final String advertiseURI, final Layout<? extends Serializable> layout) { - this.append = append; - this.force = force; - this.regionLength = regionLength; - this.advertiseURI = advertiseURI; - this.layout = layout; - } - } - - /** - * Factory to create a MemoryMappedFileManager. - */ - private static class MemoryMappedFileManagerFactory - implements ManagerFactory<MemoryMappedFileManager, FactoryData> { - - /** - * Create a MemoryMappedFileManager. - * - * @param name The name of the File. - * @param data The FactoryData - * @return The MemoryMappedFileManager for the File. - */ - @SuppressWarnings("resource") - @Override - public MemoryMappedFileManager createManager(final String name, final FactoryData data) { - final File file = new File(name); - final File parent = file.getParentFile(); - if (null != parent && !parent.exists()) { - parent.mkdirs(); - } - if (!data.append) { - file.delete(); - } - - final boolean writeHeader = !data.append || !file.exists(); - final OutputStream os = NullOutputStream.NULL_OUTPUT_STREAM; - RandomAccessFile raf = null; - try { - raf = new RandomAccessFile(name, "rw"); - final long position = (data.append) ? raf.length() : 0; - raf.setLength(position + data.regionLength); - return new MemoryMappedFileManager(raf, name, os, data.force, position, data.regionLength, - data.advertiseURI, data.layout, writeHeader); - } catch (final Exception ex) { - LOGGER.error("MemoryMappedFileManager (" + name + ") " + ex, ex); - Closer.closeSilently(raf); - } - return null; - } - } -} +/* + * 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.io.Serializable; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.util.Closer; +import org.apache.logging.log4j.core.util.NullOutputStream; + +//Lines too long... +//CHECKSTYLE:OFF +/** + * Extends OutputStreamManager but instead of using a buffered output stream, this class maps a region of a file into + * memory and writes to this memory region. + * <p> + * + * @see <a href="http://www.codeproject.com/Tips/683614/Things-to-Know-about-Memory-Mapped-File-in-Java"> + * http://www.codeproject.com/Tips/683614/Things-to-Know-about-Memory-Mapped-File-in-Java</a> + * @see <a href="http://bugs.java.com/view_bug.do?bug_id=6893654">http://bugs.java.com/view_bug.do?bug_id=6893654</a> + * @see <a href="http://bugs.java.com/view_bug.do?bug_id=4724038">http://bugs.java.com/view_bug.do?bug_id=4724038</a> + * @see <a + * href="http://stackoverflow.com/questions/9261316/memory-mapped-mappedbytebuffer-or-direct-bytebuffer-for-db-implementation"> + * http://stackoverflow.com/questions/9261316/memory-mapped-mappedbytebuffer-or-direct-bytebuffer-for-db-implementation</a> + * + * @since 2.1 + */ +//CHECKSTYLE:ON +public class MemoryMappedFileManager extends OutputStreamManager { + /** + * Default length of region to map. + */ + static final int DEFAULT_REGION_LENGTH = 32 * 1024 * 1024; + private static final int MAX_REMAP_COUNT = 10; + private static final MemoryMappedFileManagerFactory FACTORY = new MemoryMappedFileManagerFactory(); + private static final double NANOS_PER_MILLISEC = 1000.0 * 1000.0; + + private final boolean isForce; + private final int regionLength; + private final String advertiseURI; + private final RandomAccessFile randomAccessFile; + private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>(); + private MappedByteBuffer mappedBuffer; + private long mappingOffset; + + protected MemoryMappedFileManager(final RandomAccessFile file, final String fileName, final OutputStream os, + final boolean force, final long position, final int regionLength, final String advertiseURI, + final Layout<? extends Serializable> layout, final boolean writeHeader) throws IOException { + super(os, fileName, layout, writeHeader, ByteBuffer.wrap(new byte[0])); + this.isForce = force; + this.randomAccessFile = Objects.requireNonNull(file, "RandomAccessFile"); + this.regionLength = regionLength; + this.advertiseURI = advertiseURI; + this.isEndOfBatch.set(Boolean.FALSE); + this.mappedBuffer = mmap(randomAccessFile.getChannel(), getFileName(), position, regionLength); + this.byteBuffer = mappedBuffer; + this.mappingOffset = position; + } + + /** + * Returns the MemoryMappedFileManager. + * + * @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 isForce true if the contents should be flushed to disk on every write + * @param regionLength The mapped region length. + * @param advertiseURI the URI to use when advertising the file + * @param layout The layout. + * @return A MemoryMappedFileManager for the File. + */ + public static MemoryMappedFileManager getFileManager(final String fileName, final boolean append, + final boolean isForce, final int regionLength, final String advertiseURI, + final Layout<? extends Serializable> layout) { + return (MemoryMappedFileManager) getManager(fileName, new FactoryData(append, isForce, regionLength, + advertiseURI, layout), FACTORY); + } + + public Boolean isEndOfBatch() { + return isEndOfBatch.get(); + } + + public void setEndOfBatch(final boolean endOfBatch) { + this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); + } + + @Override + protected synchronized void write(final byte[] bytes, int offset, int length, final boolean immediateFlush) { + while (length > mappedBuffer.remaining()) { + final int chunk = mappedBuffer.remaining(); + mappedBuffer.put(bytes, offset, chunk); + offset += chunk; + length -= chunk; + remap(); + } + mappedBuffer.put(bytes, offset, length); + + // no need to call flush() if force is true, + // already done in AbstractOutputStreamAppender.append + } + + private synchronized void remap() { + final long offset = this.mappingOffset + mappedBuffer.position(); + final int length = mappedBuffer.remaining() + regionLength; + try { + unsafeUnmap(mappedBuffer); + final long fileLength = randomAccessFile.length() + regionLength; + LOGGER.debug("{} {} extending {} by {} bytes to {}", getClass().getSimpleName(), getName(), getFileName(), + regionLength, fileLength); + + final long startNanos = System.nanoTime(); + randomAccessFile.setLength(fileLength); + final float millis = (float) ((System.nanoTime() - startNanos) / NANOS_PER_MILLISEC); + LOGGER.debug("{} {} extended {} OK in {} millis", getClass().getSimpleName(), getName(), getFileName(), + millis); + + mappedBuffer = mmap(randomAccessFile.getChannel(), getFileName(), offset, length); + this.byteBuffer = mappedBuffer; + mappingOffset = offset; + } catch (final Exception ex) { + logError("Unable to remap", ex); + } + } + + @Override + public synchronized void flush() { + mappedBuffer.force(); + } + + @Override + public synchronized boolean closeOutputStream() { + final long position = mappedBuffer.position(); + final long length = mappingOffset + position; + try { + unsafeUnmap(mappedBuffer); + } catch (final Exception ex) { + logError("Unable to unmap MappedBuffer", ex); + } + try { + LOGGER.debug("MMapAppender closing. Setting {} length to {} (offset {} + position {})", getFileName(), + length, mappingOffset, position); + randomAccessFile.setLength(length); + randomAccessFile.close(); + return true; + } catch (final IOException ex) { + logError("Unable to close MemoryMappedFile", ex); + return false; + } + } + + public static MappedByteBuffer mmap(final FileChannel fileChannel, final String fileName, final long start, + final int size) throws IOException { + for (int i = 1;; i++) { + try { + LOGGER.debug("MMapAppender remapping {} start={}, size={}", fileName, start, size); + + final long startNanos = System.nanoTime(); + final MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, start, size); + map.order(ByteOrder.nativeOrder()); + + final float millis = (float) ((System.nanoTime() - startNanos) / NANOS_PER_MILLISEC); + LOGGER.debug("MMapAppender remapped {} OK in {} millis", fileName, millis); + + return map; + } catch (final IOException e) { + if (e.getMessage() == null || !e.getMessage().endsWith("user-mapped section open")) { + throw e; + } + LOGGER.debug("Remap attempt {}/{} failed. Retrying...", i, MAX_REMAP_COUNT, e); + if (i < MAX_REMAP_COUNT) { + Thread.yield(); + } else { + try { + Thread.sleep(1); + } catch (final InterruptedException ignored) { + Thread.currentThread().interrupt(); + throw e; + } + } + } + } + } + + private static void unsafeUnmap(final MappedByteBuffer mbb) throws PrivilegedActionException { + LOGGER.debug("MMapAppender unmapping old buffer..."); + final long startNanos = System.nanoTime(); + AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + @Override + public Object run() throws Exception { + final Method getCleanerMethod = mbb.getClass().getMethod("cleaner"); + getCleanerMethod.setAccessible(true); + final Object cleaner = getCleanerMethod.invoke(mbb); // sun.misc.Cleaner instance + final Method cleanMethod = cleaner.getClass().getMethod("clean"); + cleanMethod.invoke(cleaner); + return null; + } + }); + final float millis = (float) ((System.nanoTime() - startNanos) / NANOS_PER_MILLISEC); + LOGGER.debug("MMapAppender unmapped buffer OK in {} millis", millis); + } + + /** + * Returns the name of the File being managed. + * + * @return The name of the File being managed. + */ + public String getFileName() { + return getName(); + } + + /** + * Returns the length of the memory mapped region. + * + * @return the length of the mapped region + */ + public int getRegionLength() { + return regionLength; + } + + /** + * Returns {@code true} if the content of the buffer should be forced to the storage device on every write, + * {@code false} otherwise. + * + * @return whether each write should be force-sync'ed + */ + public boolean isImmediateFlush() { + return isForce; + } + + /** + * Gets this FileManager's content format specified by: + * <p> + * Key: "fileURI" Value: provided "advertiseURI" param. + * </p> + * + * @return Map of content format keys supporting FileManager + */ + @Override + public Map<String, String> getContentFormat() { + final Map<String, String> result = new HashMap<>(super.getContentFormat()); + result.put("fileURI", advertiseURI); + return result; + } + + @Override + protected void flushBuffer(final ByteBuffer buffer) { + // do nothing (do not call drain() to avoid spurious remapping) + } + + @Override + public ByteBuffer getByteBuffer() { + return mappedBuffer; + } + + @Override + public ByteBuffer drain(final ByteBuffer buf) { + remap(); + return mappedBuffer; + } + + /** + * Factory Data. + */ + private static class FactoryData { + private final boolean append; + private final boolean force; + private final int regionLength; + private final String advertiseURI; + private final Layout<? extends Serializable> layout; + + /** + * Constructor. + * + * @param append Append to existing file or truncate. + * @param force forces the memory content to be written to the storage device on every event + * @param regionLength length of the mapped region + * @param advertiseURI the URI to use when advertising the file + * @param layout The layout. + */ + public FactoryData(final boolean append, final boolean force, final int regionLength, + final String advertiseURI, final Layout<? extends Serializable> layout) { + this.append = append; + this.force = force; + this.regionLength = regionLength; + this.advertiseURI = advertiseURI; + this.layout = layout; + } + } + + /** + * Factory to create a MemoryMappedFileManager. + */ + private static class MemoryMappedFileManagerFactory + implements ManagerFactory<MemoryMappedFileManager, FactoryData> { + + /** + * Create a MemoryMappedFileManager. + * + * @param name The name of the File. + * @param data The FactoryData + * @return The MemoryMappedFileManager for the File. + */ + @SuppressWarnings("resource") + @Override + public MemoryMappedFileManager createManager(final String name, final FactoryData data) { + final File file = new File(name); + final File parent = file.getParentFile(); + if (null != parent && !parent.exists()) { + parent.mkdirs(); + } + if (!data.append) { + file.delete(); + } + + final boolean writeHeader = !data.append || !file.exists(); + final OutputStream os = NullOutputStream.getInstance(); + RandomAccessFile raf = null; + try { + raf = new RandomAccessFile(name, "rw"); + final long position = (data.append) ? raf.length() : 0; + raf.setLength(position + data.regionLength); + return new MemoryMappedFileManager(raf, name, os, data.force, position, data.regionLength, + data.advertiseURI, data.layout, writeHeader); + } catch (final Exception ex) { + LOGGER.error("MemoryMappedFileManager (" + name + ") " + ex, ex); + Closer.closeSilently(raf); + } + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f73f89b1/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileManager.java index 4cd5459..45740f1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileManager.java @@ -1,216 +1,216 @@ -/* - * 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.io.Serializable; -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; - -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.util.NullOutputStream; - -/** - * 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 RandomAccessFileManager extends OutputStreamManager { - static final int DEFAULT_BUFFER_SIZE = 256 * 1024; - - private static final RandomAccessFileManagerFactory FACTORY = new RandomAccessFileManagerFactory(); - - private final String advertiseURI; - private final RandomAccessFile randomAccessFile; - private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>(); - - protected RandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile file, final String fileName, - final OutputStream os, final int bufferSize, final String advertiseURI, - final Layout<? extends Serializable> layout, final boolean writeHeader) { - super(loggerContext, os, fileName, false, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize])); - this.randomAccessFile = file; - this.advertiseURI = advertiseURI; - this.isEndOfBatch.set(Boolean.FALSE); - } - - /** - * Returns the RandomAccessFileManager. - * - * @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 bufferSize The buffer size. - * @param advertiseURI the URI to use when advertising the file - * @param layout The layout. - * @param configuration The configuration. - * @return A RandomAccessFileManager for the File. - */ - public static RandomAccessFileManager getFileManager(final String fileName, final boolean append, - final boolean isFlush, final int bufferSize, final String advertiseURI, - final Layout<? extends Serializable> layout, final Configuration configuration) { - return (RandomAccessFileManager) getManager(fileName, new FactoryData(append, - isFlush, bufferSize, advertiseURI, layout, configuration), FACTORY); - } - - public Boolean isEndOfBatch() { - return isEndOfBatch.get(); - } - - public void setEndOfBatch(final boolean endOfBatch) { - this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); - } - - @Override - protected void writeToDestination(final byte[] bytes, final int offset, final int length) { - try { - randomAccessFile.write(bytes, offset, length); - } catch (final IOException ex) { - final String msg = "Error writing to RandomAccessFile " + getName(); - throw new AppenderLoggingException(msg, ex); - } - } - - @Override - public synchronized void flush() { - flushBuffer(byteBuffer); - } - - @Override - public synchronized boolean closeOutputStream() { - flush(); - try { - randomAccessFile.close(); - return true; - } catch (final IOException ex) { - logError("Unable to close RandomAccessFile", ex); - return false; - } - } - - /** - * Returns the name of the File being managed. - * - * @return The name of the File being managed. - */ - public String getFileName() { - return getName(); - } - - /** - * Returns the buffer capacity. - * @return the buffer size - */ - public int getBufferSize() { - return byteBuffer.capacity(); - } - - /** - * Gets this FileManager's content format specified by: - * <p> - * Key: "fileURI" Value: provided "advertiseURI" param. - * </p> - * - * @return Map of content format keys supporting FileManager - */ - @Override - public Map<String, String> getContentFormat() { - final Map<String, String> result = new HashMap<>( - super.getContentFormat()); - result.put("fileURI", advertiseURI); - return result; - } - - /** - * Factory Data. - */ - private static class FactoryData extends ConfigurationFactoryData { - private final boolean append; - private final boolean immediateFlush; - private final int bufferSize; - private final String advertiseURI; - private final Layout<? extends Serializable> layout; - - /** - * Constructor. - * - * @param append Append status. - * @param bufferSize size of the buffer - * @param configuration The configuration. - */ - public FactoryData(final boolean append, final boolean immediateFlush, final int bufferSize, - final String advertiseURI, final Layout<? extends Serializable> layout, final Configuration configuration) { - super(configuration); - this.append = append; - this.immediateFlush = immediateFlush; - this.bufferSize = bufferSize; - this.advertiseURI = advertiseURI; - this.layout = layout; - } - } - - /** - * Factory to create a RandomAccessFileManager. - */ - private static class RandomAccessFileManagerFactory implements - ManagerFactory<RandomAccessFileManager, FactoryData> { - - /** - * Create a RandomAccessFileManager. - * - * @param name The name of the File. - * @param data The FactoryData - * @return The RandomAccessFileManager for the File. - */ - @Override - public RandomAccessFileManager createManager(final String name, final FactoryData data) { - final File file = new File(name); - final File parent = file.getParentFile(); - if (null != parent && !parent.exists()) { - parent.mkdirs(); - } - if (!data.append) { - file.delete(); - } - - final boolean writeHeader = !data.append || !file.exists(); - final OutputStream os = NullOutputStream.NULL_OUTPUT_STREAM; - RandomAccessFile raf; - try { - raf = new RandomAccessFile(name, "rw"); - if (data.append) { - raf.seek(raf.length()); - } else { - raf.setLength(0); - } - return new RandomAccessFileManager(data.getLoggerContext(), raf, name, - os, data.bufferSize, data.advertiseURI, data.layout, writeHeader); - } catch (final Exception ex) { - LOGGER.error("RandomAccessFileManager (" + name + ") " + ex, ex); - } - return null; - } - } - -} +/* + * 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.io.Serializable; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.util.NullOutputStream; + +/** + * 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 RandomAccessFileManager extends OutputStreamManager { + static final int DEFAULT_BUFFER_SIZE = 256 * 1024; + + private static final RandomAccessFileManagerFactory FACTORY = new RandomAccessFileManagerFactory(); + + private final String advertiseURI; + private final RandomAccessFile randomAccessFile; + private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>(); + + protected RandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile file, final String fileName, + final OutputStream os, final int bufferSize, final String advertiseURI, + final Layout<? extends Serializable> layout, final boolean writeHeader) { + super(loggerContext, os, fileName, false, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize])); + this.randomAccessFile = file; + this.advertiseURI = advertiseURI; + this.isEndOfBatch.set(Boolean.FALSE); + } + + /** + * Returns the RandomAccessFileManager. + * + * @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 bufferSize The buffer size. + * @param advertiseURI the URI to use when advertising the file + * @param layout The layout. + * @param configuration The configuration. + * @return A RandomAccessFileManager for the File. + */ + public static RandomAccessFileManager getFileManager(final String fileName, final boolean append, + final boolean isFlush, final int bufferSize, final String advertiseURI, + final Layout<? extends Serializable> layout, final Configuration configuration) { + return (RandomAccessFileManager) getManager(fileName, new FactoryData(append, + isFlush, bufferSize, advertiseURI, layout, configuration), FACTORY); + } + + public Boolean isEndOfBatch() { + return isEndOfBatch.get(); + } + + public void setEndOfBatch(final boolean endOfBatch) { + this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); + } + + @Override + protected void writeToDestination(final byte[] bytes, final int offset, final int length) { + try { + randomAccessFile.write(bytes, offset, length); + } catch (final IOException ex) { + final String msg = "Error writing to RandomAccessFile " + getName(); + throw new AppenderLoggingException(msg, ex); + } + } + + @Override + public synchronized void flush() { + flushBuffer(byteBuffer); + } + + @Override + public synchronized boolean closeOutputStream() { + flush(); + try { + randomAccessFile.close(); + return true; + } catch (final IOException ex) { + logError("Unable to close RandomAccessFile", ex); + return false; + } + } + + /** + * Returns the name of the File being managed. + * + * @return The name of the File being managed. + */ + public String getFileName() { + return getName(); + } + + /** + * Returns the buffer capacity. + * @return the buffer size + */ + public int getBufferSize() { + return byteBuffer.capacity(); + } + + /** + * Gets this FileManager's content format specified by: + * <p> + * Key: "fileURI" Value: provided "advertiseURI" param. + * </p> + * + * @return Map of content format keys supporting FileManager + */ + @Override + public Map<String, String> getContentFormat() { + final Map<String, String> result = new HashMap<>( + super.getContentFormat()); + result.put("fileURI", advertiseURI); + return result; + } + + /** + * Factory Data. + */ + private static class FactoryData extends ConfigurationFactoryData { + private final boolean append; + private final boolean immediateFlush; + private final int bufferSize; + private final String advertiseURI; + private final Layout<? extends Serializable> layout; + + /** + * Constructor. + * + * @param append Append status. + * @param bufferSize size of the buffer + * @param configuration The configuration. + */ + public FactoryData(final boolean append, final boolean immediateFlush, final int bufferSize, + final String advertiseURI, final Layout<? extends Serializable> layout, final Configuration configuration) { + super(configuration); + this.append = append; + this.immediateFlush = immediateFlush; + this.bufferSize = bufferSize; + this.advertiseURI = advertiseURI; + this.layout = layout; + } + } + + /** + * Factory to create a RandomAccessFileManager. + */ + private static class RandomAccessFileManagerFactory implements + ManagerFactory<RandomAccessFileManager, FactoryData> { + + /** + * Create a RandomAccessFileManager. + * + * @param name The name of the File. + * @param data The FactoryData + * @return The RandomAccessFileManager for the File. + */ + @Override + public RandomAccessFileManager createManager(final String name, final FactoryData data) { + final File file = new File(name); + final File parent = file.getParentFile(); + if (null != parent && !parent.exists()) { + parent.mkdirs(); + } + if (!data.append) { + file.delete(); + } + + final boolean writeHeader = !data.append || !file.exists(); + final OutputStream os = NullOutputStream.getInstance(); + RandomAccessFile raf; + try { + raf = new RandomAccessFile(name, "rw"); + if (data.append) { + raf.seek(raf.length()); + } else { + raf.setLength(0); + } + return new RandomAccessFileManager(data.getLoggerContext(), raf, name, + os, data.bufferSize, data.advertiseURI, data.layout, writeHeader); + } catch (final Exception ex) { + LOGGER.error("RandomAccessFileManager (" + name + ") " + ex, ex); + } + return null; + } + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f73f89b1/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java index 9be448b..6481972 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java @@ -1,265 +1,265 @@ -/* - * 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.IOException; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.io.Serializable; -import java.nio.ByteBuffer; - -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.appender.AppenderLoggingException; -import org.apache.logging.log4j.core.appender.ConfigurationFactoryData; -import org.apache.logging.log4j.core.appender.ManagerFactory; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.util.NullOutputStream; - -/** - * 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 RollingRandomAccessFileManager extends RollingFileManager { - /** - * The default buffer size. - */ - public static final int DEFAULT_BUFFER_SIZE = 256 * 1024; - - private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory(); - - private RandomAccessFile randomAccessFile; - private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>(); - - public RollingRandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile raf, - final String fileName, final String pattern, final OutputStream os, final boolean append, - final boolean immediateFlush, final int bufferSize, final long size, final long time, - final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, - final Layout<? extends Serializable> layout, final boolean writeHeader) { - super(loggerContext, fileName, pattern, os, append, false, size, time, policy, strategy, advertiseURI, layout, - writeHeader, ByteBuffer.wrap(new byte[bufferSize])); - this.randomAccessFile = raf; - isEndOfBatch.set(Boolean.FALSE); - writeHeader(); - } - - /** - * Writes the layout's header to the file if it exists. - */ - private void writeHeader() { - if (layout == null) { - return; - } - final byte[] header = layout.getHeader(); - if (header == null) { - return; - } - try { - if (randomAccessFile.length() == 0) { - // write to the file, not to the buffer: the buffer may not be empty - randomAccessFile.write(header, 0, header.length); - } - } catch (final IOException e) { - logError("Unable to write header", e); - } - } - - public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName, - final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize, - final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, - final Layout<? extends Serializable> layout, final Configuration configuration) { - return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend, - immediateFlush, bufferSize, policy, strategy, advertiseURI, layout, configuration), FACTORY); - } - - public Boolean isEndOfBatch() { - return isEndOfBatch.get(); - } - - public void setEndOfBatch(final boolean endOfBatch) { - this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); - } - - // override to make visible for unit tests - @Override - protected synchronized void write(final byte[] bytes, final int offset, final int length, - final boolean immediateFlush) { - super.write(bytes, offset, length, immediateFlush); - } - - @Override - protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) { - try { - randomAccessFile.write(bytes, offset, length); - size += length; - } catch (final IOException ex) { - final String msg = "Error writing to RandomAccessFile " + getName(); - throw new AppenderLoggingException(msg, ex); - } - } - - @Override - protected void createFileAfterRollover() throws IOException { - this.randomAccessFile = new RandomAccessFile(getFileName(), "rw"); - if (isAppend()) { - randomAccessFile.seek(randomAccessFile.length()); - } - writeHeader(); - } - - @Override - public synchronized void flush() { - flushBuffer(byteBuffer); - } - - @Override - public synchronized boolean closeOutputStream() { - flush(); - try { - randomAccessFile.close(); - return true; - } catch (final IOException e) { - logError("Unable to close RandomAccessFile", e); - return false; - } - } - - /** - * Returns the buffer capacity. - * - * @return the buffer size - */ - @Override - public int getBufferSize() { - return byteBuffer.capacity(); - } - - /** - * Factory to create a RollingRandomAccessFileManager. - */ - private static class RollingRandomAccessFileManagerFactory implements - ManagerFactory<RollingRandomAccessFileManager, FactoryData> { - - /** - * Create the RollingRandomAccessFileManager. - * - * @param name The name of the entity to manage. - * @param data The data required to create the entity. - * @return a RollingFileManager. - */ - @Override - public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) { - final File file = new File(name); - final File parent = file.getParentFile(); - if (null != parent && !parent.exists()) { - parent.mkdirs(); - } - - if (!data.append) { - file.delete(); - } - final long size = data.append ? file.length() : 0; - final long time = file.exists() ? file.lastModified() : System.currentTimeMillis(); - - final boolean writeHeader = !data.append || !file.exists(); - RandomAccessFile raf = null; - try { - raf = new RandomAccessFile(name, "rw"); - if (data.append) { - final long length = raf.length(); - LOGGER.trace("RandomAccessFile {} seek to {}", name, length); - raf.seek(length); - } else { - LOGGER.trace("RandomAccessFile {} set length to 0", name); - raf.setLength(0); - } - return new RollingRandomAccessFileManager(data.getLoggerContext(), raf, name, data.pattern, - NullOutputStream.NULL_OUTPUT_STREAM, data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, - data.strategy, data.advertiseURI, data.layout, writeHeader); - } catch (final IOException ex) { - LOGGER.error("Cannot access RandomAccessFile " + ex, ex); - if (raf != null) { - try { - raf.close(); - } catch (final IOException e) { - LOGGER.error("Cannot close RandomAccessFile {}", name, e); - } - } - } - return null; - } - } - - /** - * Factory data. - */ - private static class FactoryData extends ConfigurationFactoryData { - private final String pattern; - private final boolean append; - private final boolean immediateFlush; - private final int bufferSize; - private final TriggeringPolicy policy; - private final RolloverStrategy strategy; - private final String advertiseURI; - private final Layout<? extends Serializable> layout; - - /** - * Create the data for the factory. - * - * @param pattern The pattern. - * @param append The append flag. - * @param immediateFlush - * @param bufferSize - * @param policy - * @param strategy - * @param advertiseURI - * @param layout - * @param configuration - */ - public FactoryData(final String pattern, final boolean append, final boolean immediateFlush, - final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy, - final String advertiseURI, final Layout<? extends Serializable> layout, final Configuration configuration) { - super(configuration); - this.pattern = pattern; - this.append = append; - this.immediateFlush = immediateFlush; - this.bufferSize = bufferSize; - this.policy = policy; - this.strategy = strategy; - this.advertiseURI = advertiseURI; - this.layout = layout; - } - - public TriggeringPolicy getTriggeringPolicy() - { - return this.policy; - } - - public RolloverStrategy getRolloverStrategy() - { - return this.strategy; - } - } - - @Override - public void updateData(final Object data) { - final FactoryData factoryData = (FactoryData) data; - setRolloverStrategy(factoryData.getRolloverStrategy()); - setTriggeringPolicy(factoryData.getTriggeringPolicy()); - } -} +/* + * 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.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.io.Serializable; +import java.nio.ByteBuffer; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AppenderLoggingException; +import org.apache.logging.log4j.core.appender.ConfigurationFactoryData; +import org.apache.logging.log4j.core.appender.ManagerFactory; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.util.NullOutputStream; + +/** + * 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 RollingRandomAccessFileManager extends RollingFileManager { + /** + * The default buffer size. + */ + public static final int DEFAULT_BUFFER_SIZE = 256 * 1024; + + private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory(); + + private RandomAccessFile randomAccessFile; + private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>(); + + public RollingRandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile raf, + final String fileName, final String pattern, final OutputStream os, final boolean append, + final boolean immediateFlush, final int bufferSize, final long size, final long time, + final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, + final Layout<? extends Serializable> layout, final boolean writeHeader) { + super(loggerContext, fileName, pattern, os, append, false, size, time, policy, strategy, advertiseURI, layout, + writeHeader, ByteBuffer.wrap(new byte[bufferSize])); + this.randomAccessFile = raf; + isEndOfBatch.set(Boolean.FALSE); + writeHeader(); + } + + /** + * Writes the layout's header to the file if it exists. + */ + private void writeHeader() { + if (layout == null) { + return; + } + final byte[] header = layout.getHeader(); + if (header == null) { + return; + } + try { + if (randomAccessFile.length() == 0) { + // write to the file, not to the buffer: the buffer may not be empty + randomAccessFile.write(header, 0, header.length); + } + } catch (final IOException e) { + logError("Unable to write header", e); + } + } + + public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName, + final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize, + final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, + final Layout<? extends Serializable> layout, final Configuration configuration) { + return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend, + immediateFlush, bufferSize, policy, strategy, advertiseURI, layout, configuration), FACTORY); + } + + public Boolean isEndOfBatch() { + return isEndOfBatch.get(); + } + + public void setEndOfBatch(final boolean endOfBatch) { + this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); + } + + // override to make visible for unit tests + @Override + protected synchronized void write(final byte[] bytes, final int offset, final int length, + final boolean immediateFlush) { + super.write(bytes, offset, length, immediateFlush); + } + + @Override + protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) { + try { + randomAccessFile.write(bytes, offset, length); + size += length; + } catch (final IOException ex) { + final String msg = "Error writing to RandomAccessFile " + getName(); + throw new AppenderLoggingException(msg, ex); + } + } + + @Override + protected void createFileAfterRollover() throws IOException { + this.randomAccessFile = new RandomAccessFile(getFileName(), "rw"); + if (isAppend()) { + randomAccessFile.seek(randomAccessFile.length()); + } + writeHeader(); + } + + @Override + public synchronized void flush() { + flushBuffer(byteBuffer); + } + + @Override + public synchronized boolean closeOutputStream() { + flush(); + try { + randomAccessFile.close(); + return true; + } catch (final IOException e) { + logError("Unable to close RandomAccessFile", e); + return false; + } + } + + /** + * Returns the buffer capacity. + * + * @return the buffer size + */ + @Override + public int getBufferSize() { + return byteBuffer.capacity(); + } + + /** + * Factory to create a RollingRandomAccessFileManager. + */ + private static class RollingRandomAccessFileManagerFactory implements + ManagerFactory<RollingRandomAccessFileManager, FactoryData> { + + /** + * Create the RollingRandomAccessFileManager. + * + * @param name The name of the entity to manage. + * @param data The data required to create the entity. + * @return a RollingFileManager. + */ + @Override + public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) { + final File file = new File(name); + final File parent = file.getParentFile(); + if (null != parent && !parent.exists()) { + parent.mkdirs(); + } + + if (!data.append) { + file.delete(); + } + final long size = data.append ? file.length() : 0; + final long time = file.exists() ? file.lastModified() : System.currentTimeMillis(); + + final boolean writeHeader = !data.append || !file.exists(); + RandomAccessFile raf = null; + try { + raf = new RandomAccessFile(name, "rw"); + if (data.append) { + final long length = raf.length(); + LOGGER.trace("RandomAccessFile {} seek to {}", name, length); + raf.seek(length); + } else { + LOGGER.trace("RandomAccessFile {} set length to 0", name); + raf.setLength(0); + } + return new RollingRandomAccessFileManager(data.getLoggerContext(), raf, name, data.pattern, + NullOutputStream.getInstance(), data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, + data.strategy, data.advertiseURI, data.layout, writeHeader); + } catch (final IOException ex) { + LOGGER.error("Cannot access RandomAccessFile " + ex, ex); + if (raf != null) { + try { + raf.close(); + } catch (final IOException e) { + LOGGER.error("Cannot close RandomAccessFile {}", name, e); + } + } + } + return null; + } + } + + /** + * Factory data. + */ + private static class FactoryData extends ConfigurationFactoryData { + private final String pattern; + private final boolean append; + private final boolean immediateFlush; + private final int bufferSize; + private final TriggeringPolicy policy; + private final RolloverStrategy strategy; + private final String advertiseURI; + private final Layout<? extends Serializable> layout; + + /** + * Create the data for the factory. + * + * @param pattern The pattern. + * @param append The append flag. + * @param immediateFlush + * @param bufferSize + * @param policy + * @param strategy + * @param advertiseURI + * @param layout + * @param configuration + */ + public FactoryData(final String pattern, final boolean append, final boolean immediateFlush, + final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy, + final String advertiseURI, final Layout<? extends Serializable> layout, final Configuration configuration) { + super(configuration); + this.pattern = pattern; + this.append = append; + this.immediateFlush = immediateFlush; + this.bufferSize = bufferSize; + this.policy = policy; + this.strategy = strategy; + this.advertiseURI = advertiseURI; + this.layout = layout; + } + + public TriggeringPolicy getTriggeringPolicy() + { + return this.policy; + } + + public RolloverStrategy getRolloverStrategy() + { + return this.strategy; + } + } + + @Override + public void updateData(final Object data) { + final FactoryData factoryData = (FactoryData) data; + setRolloverStrategy(factoryData.getRolloverStrategy()); + setTriggeringPolicy(factoryData.getTriggeringPolicy()); + } +}