You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rp...@apache.org on 2016/08/12 12:11:58 UTC

[12/50] logging-log4j2 git commit: [LOG4J2-1501] FileAppender should be able to create files lazily.

[LOG4J2-1501] FileAppender should be able to create files lazily.

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/61f706fc
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/61f706fc
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/61f706fc

Branch: refs/heads/LOG4J2-1010&LOG4J2-1447-injectable-contextdata&better-datastructure
Commit: 61f706fc0a811c3184e941f2e8d1e012b5cbdb39
Parents: 887e028
Author: Gary Gregory <gg...@apache.org>
Authored: Mon Aug 8 08:50:11 2016 -0700
Committer: Gary Gregory <gg...@apache.org>
Committed: Mon Aug 8 08:50:11 2016 -0700

----------------------------------------------------------------------
 .../log4j/core/appender/FileAppender.java       |  79 +++++-
 .../log4j/core/appender/FileManager.java        |  79 ++++--
 .../core/appender/OutputStreamManager.java      |  59 +++-
 .../log4j/core/appender/FileAppenderTest.java   | 270 +++++++++++--------
 .../log4j/test/appender/InMemoryAppender.java   |   7 +-
 src/changes/changes.xml                         |   3 +
 6 files changed, 346 insertions(+), 151 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/61f706fc/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java
index 90c088e..a69758b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java
@@ -39,7 +39,7 @@ import org.apache.logging.log4j.core.util.Integers;
 @Plugin(name = "File", category = "Core", elementType = "appender", printObject = true)
 public final class FileAppender extends AbstractOutputStreamAppender<FileManager> {
 
-    private static final int DEFAULT_BUFFER_SIZE = 8192;
+    static final int DEFAULT_BUFFER_SIZE = 8192;
     private final String fileName;
     private final Advertiser advertiser;
     private Object advertisement;
@@ -96,8 +96,9 @@ public final class FileAppender extends AbstractOutputStreamAppender<FileManager
      * @param advertiseUri The advertised URI which can be used to retrieve the file contents.
      * @param config The Configuration
      * @return The FileAppender.
+     * @deprecated Use {@link #createAppender(String, boolean, boolean, String, String, String, boolean, String, Layout<? extends Serializable>, Filter, String, String, boolean, Configuration)}
      */
-    @PluginFactory
+    @Deprecated
     public static FileAppender createAppender(
             // @formatter:off
             @PluginAttribute("fileName") final String fileName,
@@ -144,8 +145,8 @@ public final class FileAppender extends AbstractOutputStreamAppender<FileManager
             layout = PatternLayout.createDefaultLayout();
         }
 
-        final FileManager manager = FileManager.getFileManager(fileName, isAppend, isLocking, isBuffered, advertiseUri,
-                layout, bufferSize, isFlush);
+        final FileManager manager = FileManager.getFileManager(fileName, isAppend, isLocking, isBuffered, false,
+                advertiseUri, layout, bufferSize, isFlush);
         if (manager == null) {
             return null;
         }
@@ -153,4 +154,74 @@ public final class FileAppender extends AbstractOutputStreamAppender<FileManager
         return new FileAppender(name, layout, filter, manager, fileName, ignoreExceptions, !isBuffered || isFlush,
                 isAdvertise ? config.getAdvertiser() : null);
     }
+
+    /**
+     * 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 locking "True" if the file should be locked. The default is "false".
+     * @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 ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
+     *               they are propagated to the caller.
+     * @param bufferedIo "true" if I/O should be buffered, "false" otherwise. The default is "true".
+     * @param bufferSize buffer size for buffered IO (default is 8192).
+     * @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 lazyCreate true if you want to lazy-create the file (a.k.a. on-demand.)
+     * @param config The Configuration
+     * @return The FileAppender.
+     * @since 2.7
+     */
+    @PluginFactory
+    public static FileAppender createAppender(
+            // @formatter:off
+            @PluginAttribute("fileName") final String fileName,
+            @PluginAttribute(value = "append", defaultBoolean = true) final boolean append,
+            @PluginAttribute("locking") final boolean locking,
+            @PluginAttribute("name") final String name,
+            @PluginAttribute(value = "immediateFlush", defaultBoolean = true) final boolean immediateFlush,
+            @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final boolean ignoreExceptions,
+            @PluginAttribute(value = "bufferedIo", defaultBoolean = true) boolean bufferedIo,
+            @PluginAttribute(value = "bufferSize", defaultInt = DEFAULT_BUFFER_SIZE) final int bufferSize,
+            @PluginElement("Layout") Layout<? extends Serializable> layout,
+            @PluginElement("Filter") final Filter filter,
+            @PluginAttribute("advertise") final boolean advertise,
+            @PluginAttribute("advertiseUri") final String advertiseUri,
+            @PluginAttribute("lazyCreate") final boolean lazyCreate,
+            @PluginConfiguration final Configuration config) {
+             // @formatter:on
+        if (locking && bufferedIo) {
+            LOGGER.warn("Locking and buffering are mutually exclusive. No buffering will occur for {}", fileName);
+            bufferedIo = false;
+        }
+        if (!bufferedIo && bufferSize > 0) {
+            LOGGER.warn("The bufferSize is set to {} but bufferedIo is not true: {}", bufferSize, bufferedIo);
+        }
+        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;
+        }
+        if (layout == null) {
+            layout = PatternLayout.createDefaultLayout();
+        }
+
+        final FileManager manager = FileManager.getFileManager(fileName, append, locking, bufferedIo, lazyCreate,
+                advertiseUri, layout, bufferSize, immediateFlush);
+        if (manager == null) {
+            return null;
+        }
+
+        return new FileAppender(name, layout, filter, manager, fileName, ignoreExceptions, !bufferedIo || immediateFlush,
+                advertise ? config.getAdvertiser() : null);
+    }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/61f706fc/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java
index af10d25..71d152b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java
@@ -51,7 +51,11 @@ public class FileManager extends OutputStreamManager {
         this(fileName, os, append, locking, advertiseURI, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize]));
     }
 
-    /** @since 2.6 */
+    /**
+     * @deprecated 
+     * @since 2.6 
+     */
+    @Deprecated
     protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking,
             final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader,
             final ByteBuffer buffer) {
@@ -62,12 +66,27 @@ public class FileManager extends OutputStreamManager {
         this.bufferSize = buffer.capacity();
     }
 
+    /** 
+     * @throws IOException 
+     * @since 2.7 
+     */
+    protected FileManager(final String fileName, final boolean append, final boolean locking, final boolean lazyCreate,
+            final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader,
+            final ByteBuffer buffer) throws IOException {
+        super(fileName, lazyCreate, layout, writeHeader, buffer);
+        this.isAppend = append;
+        this.isLocking = locking;
+        this.advertiseURI = advertiseURI;
+        this.bufferSize = buffer.capacity();
+    }
+
     /**
      * Returns the FileManager.
      * @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 locking true if the file should be locked while writing, false otherwise.
      * @param bufferedIo true if the contents should be buffered as they are written.
+     * @param lazyCreate true if you want to lazy-create the file (a.k.a. on-demand.)
      * @param advertiseUri the URI to use when advertising the file
      * @param layout The layout
      * @param bufferSize buffer size for buffered IO
@@ -75,31 +94,40 @@ public class FileManager extends OutputStreamManager {
      * @return A FileManager for the File.
      */
     public static FileManager getFileManager(final String fileName, final boolean append, boolean locking,
-            final boolean bufferedIo, final String advertiseUri, final Layout<? extends Serializable> layout,
-            final int bufferSize, final boolean immediateFlush) {
+            final boolean bufferedIo, boolean lazyCreate, final String advertiseUri,
+            final Layout<? extends Serializable> layout, final int bufferSize, final boolean immediateFlush) {
 
         if (locking && bufferedIo) {
             locking = false;
         }
-        return (FileManager) getManager(fileName, new FactoryData(append, locking, bufferedIo, bufferSize,
-                immediateFlush, advertiseUri, layout), FACTORY);
+        return (FileManager) getManager(fileName,
+                new FactoryData(append, locking, bufferedIo, bufferSize, immediateFlush, lazyCreate, advertiseUri, layout),
+                FACTORY);
     }
 
     @Override
-    protected synchronized void write(final byte[] bytes, final int offset, final int length, final boolean immediateFlush)  {
-
+    protected OutputStream createOutputStream() throws FileNotFoundException {
+        return new FileOutputStream(getFileName(), isAppend);
+    }
+    
+    @Override
+    protected synchronized void write(final byte[] bytes, final int offset, final int length,
+            final boolean immediateFlush) {
         if (isLocking) {
-            final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel();
-            /*
-             * Lock the whole file. This could be optimized to only lock from the current file position. Note that
-             * locking may be advisory on some systems and mandatory on others, so locking just from the current
-             * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an exception
-             * if the region of the file is already locked by another FileChannel in the same JVM. Hopefully, that will
-             * be avoided since every file should have a single file manager - unless two different files strings are
-             * configured that somehow map to the same file.
-             */
-            try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) {
-                super.write(bytes, offset, length, immediateFlush);
+            try {
+                @SuppressWarnings("resource")
+                final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel();
+                /*
+                 * Lock the whole file. This could be optimized to only lock from the current file position. Note that
+                 * locking may be advisory on some systems and mandatory on others, so locking just from the current
+                 * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an
+                 * exception if the region of the file is already locked by another FileChannel in the same JVM.
+                 * Hopefully, that will be avoided since every file should have a single file manager - unless two
+                 * different files strings are configured that somehow map to the same file.
+                 */
+                try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) {
+                    super.write(bytes, offset, length, immediateFlush);
+                }
             } catch (final IOException ex) {
                 throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex);
             }
@@ -162,6 +190,7 @@ public class FileManager extends OutputStreamManager {
         private final boolean bufferedIO;
         private final int bufferSize;
         private final boolean immediateFlush;
+        private final boolean lazyCreate;
         private final String advertiseURI;
         private final Layout<? extends Serializable> layout;
 
@@ -172,15 +201,19 @@ public class FileManager extends OutputStreamManager {
          * @param bufferedIO Buffering flag.
          * @param bufferSize Buffer size.
          * @param immediateFlush flush on every write or not
+         * @param lazyCreate if you want to lazy-create the file (a.k.a. on-demand.)
          * @param advertiseURI the URI to use when advertising the file
+         * @param layout The layout
          */
         public FactoryData(final boolean append, final boolean locking, final boolean bufferedIO, final int bufferSize,
-                final boolean immediateFlush, final String advertiseURI, final Layout<? extends Serializable> layout) {
+                final boolean immediateFlush, final boolean lazyCreate, final String advertiseURI,
+                final Layout<? extends Serializable> layout) {
             this.append = append;
             this.locking = locking;
             this.bufferedIO = bufferedIO;
             this.bufferSize = bufferSize;
             this.immediateFlush = immediateFlush;
+            this.lazyCreate = lazyCreate;
             this.advertiseURI = advertiseURI;
             this.layout = layout;
         }
@@ -192,12 +225,11 @@ public class FileManager extends OutputStreamManager {
     private static class FileManagerFactory implements ManagerFactory<FileManager, FactoryData> {
 
         /**
-         * Create a FileManager.
+         * Creates a FileManager.
          * @param name The name of the File.
          * @param data The FactoryData
          * @return The FileManager for the File.
          */
-        @SuppressWarnings("resource")
         @Override
         public FileManager createManager(final String name, final FactoryData data) {
             final File file = new File(name);
@@ -208,12 +240,11 @@ public class FileManager extends OutputStreamManager {
 
             final boolean writeHeader = !data.append || !file.exists();
             try {
-                final FileOutputStream fos = new FileOutputStream(name, data.append);
                 final int actualSize = data.bufferedIO ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
                 final ByteBuffer buffer = ByteBuffer.wrap(new byte[actualSize]);
-                return new FileManager(name, fos, data.append, data.locking, data.advertiseURI, data.layout,
+                return new FileManager(name, data.append, data.locking, data.lazyCreate, data.advertiseURI, data.layout,
                         writeHeader, buffer);
-            } catch (final FileNotFoundException ex) {
+            } catch (final IOException ex) {
                 LOGGER.error("FileManager (" + name + ") " + ex, ex);
             }
             return null;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/61f706fc/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java
index 794610c..e707bea 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.core.appender;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.Serializable;
 import java.nio.ByteBuffer;
 import java.util.Objects;
 
@@ -37,6 +38,7 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe
 
     protected OutputStreamManager(final OutputStream os, final String streamName, final Layout<?> layout,
             final boolean writeHeader) {
+        // Can't use new ctor because it throws an exception
         this(os, streamName, layout, writeHeader, ByteBuffer.wrap(new byte[Constants.ENCODER_BYTE_BUFFER_SIZE]));
     }
 
@@ -48,7 +50,9 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe
      * @param writeHeader
      * @param byteBuffer
      * @since 2.6
+     * @deprecated
      */
+    @Deprecated
     protected OutputStreamManager(final OutputStream os, final String streamName, final Layout<?> layout,
             final boolean writeHeader, final ByteBuffer byteBuffer) {
         super(streamName);
@@ -58,7 +62,7 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe
             final byte[] header = layout.getHeader();
             if (header != null) {
                 try {
-                    this.os.write(header, 0, header.length);
+                    getOutputStream().write(header, 0, header.length);
                 } catch (final IOException e) {
                     logError("Unable to write header", e);
                 }
@@ -68,6 +72,30 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe
     }
 
     /**
+     * @param byteBuffer
+     * @throws IOException 
+     * @since 2.7
+     */
+    protected OutputStreamManager(final String streamName, final boolean lazyCreate, final Layout<? extends Serializable> layout,
+            final boolean writeHeader, final ByteBuffer byteBuffer)
+            throws IOException {
+        super(streamName);
+        this.layout = layout;
+        this.byteBuffer = Objects.requireNonNull(byteBuffer, "byteBuffer");
+        this.os = lazyCreate ? null : createOutputStream();
+        if (writeHeader && layout != null) {
+            final byte[] header = layout.getHeader();
+            if (header != null) {
+                try {
+                    getOutputStream().write(header, 0, header.length);
+                } catch (final IOException e) {
+                    logError("Unable to write header for " + streamName, e);
+                }
+            }
+        }
+    }
+
+    /**
      * Creates a Manager.
      *
      * @param name The name of the stream to manage.
@@ -81,6 +109,11 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe
         return AbstractManager.getManager(name, factory, data);
     }
 
+    @SuppressWarnings("unused")
+    protected OutputStream createOutputStream() throws IOException {
+        throw new IllegalStateException(getClass().getCanonicalName() + " must implement createOutputStream()");
+    }
+    
     /**
      * Indicate whether the footer should be skipped or not.
      * @param skipFooter true if the footer should be skipped.
@@ -119,7 +152,10 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe
         return getCount() > 0;
     }
 
-    protected OutputStream getOutputStream() {
+    protected OutputStream getOutputStream() throws IOException {
+        if (os == null) {
+            os = createOutputStream();
+        }
         return os;
     }
 
@@ -208,10 +244,9 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe
      */
     protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
         try {
-            os.write(bytes, offset, length);
+            getOutputStream().write(bytes, offset, length);
         } catch (final IOException ex) {
-            final String msg = "Error writing to stream " + getName();
-            throw new AppenderLoggingException(msg, ex);
+            throw new AppenderLoggingException("Error writing to stream " + getName(), ex);
         }
     }
 
@@ -220,11 +255,13 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe
      * @since 2.6
      */
     protected synchronized void flushDestination() {
-        try {
-            os.flush();
-        } catch (final IOException ex) {
-            final String msg = "Error flushing stream " + getName();
-            throw new AppenderLoggingException(msg, ex);
+        final OutputStream stream = os; // access volatile field only once per method
+        if (stream != null) {
+            try {
+                stream.flush();
+            } catch (final IOException ex) {
+                throw new AppenderLoggingException("Error flushing stream " + getName(), ex);
+            }
         }
     }
 
@@ -255,7 +292,7 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe
     protected synchronized void close() {
         flush();
         final OutputStream stream = os; // access volatile field only once per method
-        if (stream == System.out || stream == System.err) {
+        if (stream == null || stream == System.out || stream == System.err) {
             return;
         }
         try {

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/61f706fc/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java
index 9934d10..760715d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java
@@ -24,6 +24,7 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.InputStreamReader;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -44,117 +45,151 @@ import org.junit.Assert;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 /**
- *
+ * Tests {@link FileAppender}.
  */
+@RunWith(Parameterized.class)
 public class FileAppenderTest {
 
-    private static final String FILENAME = "target/fileAppenderTest.log";
+    @Parameters(name = "lazyCreate = {0}")
+    public static Boolean[] getParameters() {
+        return new Boolean[] { false, true };
+    }
+
+    private static final String FILE_NAME = "target/fileAppenderTest.log";
+    private static final Path PATH = Paths.get(FILE_NAME);
     private static final int THREADS = 2;
 
+    public FileAppenderTest(boolean lazyCreate) {
+        super();
+        this.lazyCreate = lazyCreate;
+    }
+
+    private final boolean lazyCreate;
+    private final int threadCount = THREADS;
+
     @Rule
-    public CleanFiles files = new CleanFiles(FILENAME);
+    public CleanFiles files = new CleanFiles(PATH);
 
     @AfterClass
     public static void cleanupClass() {
-        assertTrue("Manager for " + FILENAME + " not removed", !AbstractManager.hasManager(FILENAME));
+        assertTrue("Manager for " + FILE_NAME + " not removed", !AbstractManager.hasManager(FILE_NAME));
     }
 
     @Test
     public void testAppender() throws Exception {
-        writer(false, 1, "test");
-        verifyFile(1);
+        final int logEventCount = 1;
+        writer(false, logEventCount, "test", lazyCreate, false);
+        verifyFile(logEventCount);
+    }
+
+    @Test
+    public void testLazyStart() throws Exception {
+        final Layout<String> layout = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
+                .build();
+        final FileAppender appender = FileAppender.createAppender(FILE_NAME, true, false, "test", false, false, false,
+                1, layout, null, false, null, lazyCreate, null);
+        try {
+            Assert.assertNotEquals(lazyCreate, Files.exists(PATH));
+            appender.start();
+            Assert.assertNotEquals(lazyCreate, Files.exists(PATH));
+        } finally {
+            appender.stop();
+        }
+        Assert.assertNotEquals(lazyCreate, Files.exists(PATH));
     }
 
     @Test
     public void testSmallestBufferSize() throws Exception {
-        final Layout<String> layout = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN).build();
-        final String bufferSizeStr = "1";
-        final FileAppender appender = FileAppender.createAppender(FILENAME, "true", "false", "test", "false", "false",
-                "false", bufferSizeStr, layout, null, "false", null, null);
-        appender.start();
-        final File file = new File(FILENAME);
-        assertTrue("Appender did not start", appender.isStarted());
-        long curLen = file.length();
-        long prevLen = curLen;
-        assertTrue("File length: " + curLen, curLen == 0);
-        for (int i = 0; i < 100; ++i) {
-            final LogEvent event = Log4jLogEvent.newBuilder().setLoggerName("TestLogger") //
-                    .setLoggerFqcn(FileAppenderTest.class.getName()).setLevel(Level.INFO) //
-                    .setMessage(new SimpleMessage("Test")).setThreadName(this.getClass().getSimpleName()) //
-                    .setTimeMillis(System.currentTimeMillis()).build();
-            try {
-                appender.append(event);
-                curLen = file.length();
-                assertTrue("File length: " + curLen, curLen > prevLen);
-                Thread.sleep(25); // Give up control long enough for another thread/process to occasionally do
-                                  // something.
-            } catch (final Exception ex) {
-                throw ex;
+        final Layout<String> layout = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
+                .build();
+        final FileAppender appender = FileAppender.createAppender(FILE_NAME, true, false, "test", false, false, false,
+                1, layout, null, false, null, lazyCreate, null);
+        try {
+            appender.start();
+            final File file = new File(FILE_NAME);
+            assertTrue("Appender did not start", appender.isStarted());
+            Assert.assertNotEquals(lazyCreate, Files.exists(PATH));
+            long curLen = file.length();
+            long prevLen = curLen;
+            assertTrue("File length: " + curLen, curLen == 0);
+            for (int i = 0; i < 100; ++i) {
+                final LogEvent event = Log4jLogEvent.newBuilder().setLoggerName("TestLogger") //
+                        .setLoggerFqcn(FileAppenderTest.class.getName()).setLevel(Level.INFO) //
+                        .setMessage(new SimpleMessage("Test")).setThreadName(this.getClass().getSimpleName()) //
+                        .setTimeMillis(System.currentTimeMillis()).build();
+                try {
+                    appender.append(event);
+                    curLen = file.length();
+                    assertTrue("File length: " + curLen, curLen > prevLen);
+                    // Give up control long enough for another thread/process to occasionally do something.
+                    Thread.sleep(25);
+                } catch (final Exception ex) {
+                    throw ex;
+                }
+                prevLen = curLen;
             }
-            prevLen = curLen;
+        } finally {
+            appender.stop();
         }
-        appender.stop();
         assertFalse("Appender did not stop", appender.isStarted());
     }
 
     @Test
     public void testLockingAppender() throws Exception {
-        writer(true, 1, "test");
-        verifyFile(1);
+        final int logEventCount = 1;
+        writer(true, logEventCount, "test", lazyCreate, false);
+        verifyFile(logEventCount);
     }
 
     @Test
-    public void testMultipleAppenders() throws Exception {
-        final ExecutorService pool = Executors.newFixedThreadPool(THREADS);
-        final Exception[] error = new Exception[1];
-        final int count = 100;
-        final Runnable runnable = new FileWriterRunnable(false, count, error);
-        for (int i = 0; i < THREADS; ++i) {
-            pool.execute(runnable);
+    public void testMultipleAppenderThreads() throws Exception {
+        testMultipleLockingAppenderThreads(false, threadCount);
+    }
+
+    private void testMultipleLockingAppenderThreads(final boolean lock, int threadCount)
+            throws InterruptedException, Exception {
+        final ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
+        final Exception[] exceptionRef = new Exception[1];
+        final int logEventCount = 100;
+        final Runnable runnable = new FileWriterRunnable(lock, logEventCount, exceptionRef);
+        for (int i = 0; i < threadCount; ++i) {
+            threadPool.execute(runnable);
         }
-        pool.shutdown();
-        pool.awaitTermination(10, TimeUnit.SECONDS);
-        if (error[0] != null) {
-            throw error[0];
+        threadPool.shutdown();
+        Assert.assertTrue("The thread pool has not shutdown: " + threadPool,
+                threadPool.awaitTermination(10, TimeUnit.SECONDS));
+        if (exceptionRef[0] != null) {
+            throw exceptionRef[0];
         }
-        verifyFile(THREADS * count);
+        verifyFile(threadCount * logEventCount);
     }
 
     @Test
-    public void testMultipleLockedAppenders() throws Exception {
-        final ExecutorService pool = Executors.newFixedThreadPool(THREADS);
-        final Exception[] error = new Exception[1];
-        final int count = 100;
-        final Runnable runnable = new FileWriterRunnable(true, count, error);
-        for (int i = 0; i < THREADS; ++i) {
-            pool.execute(runnable);
-        }
-        pool.shutdown();
-        pool.awaitTermination(10, TimeUnit.SECONDS);
-        if (error[0] != null) {
-            throw error[0];
-        }
-        verifyFile(THREADS * count);
+    public void testMultipleLockingAppenders() throws Exception {
+        testMultipleLockingAppenderThreads(true, threadCount);
     }
 
     @Test
     @Ignore
     public void testMultipleVMs() throws Exception {
         final String classPath = System.getProperty("java.class.path");
-        final Integer count = 10;
-        final int processeCount = 3;
-        final Process[] processes = new Process[processeCount];
-        final ProcessBuilder[] builders = new ProcessBuilder[processeCount];
-        for (int index = 0; index < processeCount; ++index) {
+        final Integer logEventCount = 10;
+        final int processCount = 3;
+        final Process[] processes = new Process[processCount];
+        final ProcessBuilder[] builders = new ProcessBuilder[processCount];
+        for (int index = 0; index < processCount; ++index) {
             builders[index] = new ProcessBuilder("java", "-cp", classPath, ProcessTest.class.getName(),
-                    "Process " + index, count.toString(), "true");
+                    "Process " + index, logEventCount.toString(), "true", Boolean.toString(lazyCreate));
         }
-        for (int index = 0; index < processeCount; ++index) {
+        for (int index = 0; index < processCount; ++index) {
             processes[index] = builders[index].start();
         }
-        for (int index = 0; index < processeCount; ++index) {
+        for (int index = 0; index < processCount; ++index) {
             final Process process = processes[index];
             // System.out.println("Process " + index + " exited with " + p.waitFor());
             try (final BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
@@ -165,63 +200,75 @@ public class FileAppenderTest {
             }
             process.destroy();
         }
-        verifyFile(count * processeCount);
+        verifyFile(logEventCount * processCount);
     }
 
-    private static void writer(final boolean lock, final int count, final String name) throws Exception {
-        final Layout<String> layout = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN).build();
-        final FileAppender app = FileAppender.createAppender(FILENAME, "true", Boolean.toString(lock), "test", "false",
-                "false", "false", null, layout, null, "false", null, null);
-        app.start();
-        assertTrue("Appender did not start", app.isStarted());
-        Assert.assertTrue(Files.exists(Paths.get(FILENAME)));
-        for (int i = 0; i < count; ++i) {
-            final LogEvent event = Log4jLogEvent.newBuilder().setLoggerName("TestLogger")
-                    .setLoggerFqcn(FileAppenderTest.class.getName()).setLevel(Level.INFO)
-                    .setMessage(new SimpleMessage("Test")).setThreadName(name).setTimeMillis(System.currentTimeMillis())
-                    .build();
-            try {
-                app.append(event);
-                Thread.sleep(25); // Give up control long enough for another thread/process to occasionally do
-                                  // something.
-            } catch (final Exception ex) {
-                throw ex;
+    private static void writer(final boolean lock, final int logEventCount, final String name, boolean lazyCreate,
+            boolean concurrent) throws Exception {
+        final Layout<String> layout = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
+                .build();
+        final FileAppender appender = FileAppender.createAppender(FILE_NAME, true, lock, "test", false, false, false,
+                FileAppender.DEFAULT_BUFFER_SIZE, layout, null, false, null, lazyCreate, null);
+        try {
+            appender.start();
+            assertTrue("Appender did not start", appender.isStarted());
+            final boolean exists = Files.exists(PATH);
+            String msg = String.format("concurrent = %s, lazyCreate = %s, file exists = %s", concurrent, lazyCreate,
+                    exists);
+            // If concurrent the file might have been created (or not.)
+            // Can't really test lazyCreate && concurrent.
+            final boolean expectFileCreated = !lazyCreate;
+            if (concurrent && expectFileCreated) {
+                Assert.assertTrue(msg, exists);
+            } else if (expectFileCreated) {
+                Assert.assertNotEquals(msg, lazyCreate, exists);
             }
+            for (int i = 0; i < logEventCount; ++i) {
+                final LogEvent logEvent = Log4jLogEvent.newBuilder().setLoggerName("TestLogger")
+                        .setLoggerFqcn(FileAppenderTest.class.getName()).setLevel(Level.INFO)
+                        .setMessage(new SimpleMessage("Test")).setThreadName(name)
+                        .setTimeMillis(System.currentTimeMillis()).build();
+                try {
+                    appender.append(logEvent);
+                    Thread.sleep(25); // Give up control long enough for another thread/process to occasionally do
+                                      // something.
+                } catch (final Exception ex) {
+                    throw ex;
+                }
+            }
+        } finally {
+            appender.stop();
         }
-        app.stop();
-        assertFalse("Appender did not stop", app.isStarted());
+        assertFalse("Appender did not stop", appender.isStarted());
     }
 
     private void verifyFile(final int count) throws Exception {
         // String expected = "[\\w]* \\[\\s*\\] INFO TestLogger - Test$";
         final String expected = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3} \\[[^\\]]*\\] INFO TestLogger - Test";
         final Pattern pattern = Pattern.compile(expected);
-        final FileInputStream fis = new FileInputStream(FILENAME);
-        final BufferedReader is = new BufferedReader(new InputStreamReader(fis));
-        int counter = 0;
-        String str = Strings.EMPTY;
-        while (is.ready()) {
-            str = is.readLine();
-            // System.out.println(str);
-            ++counter;
-            final Matcher matcher = pattern.matcher(str);
-            assertTrue("Bad data: " + str, matcher.matches());
+        int lines = 0;
+        try (final BufferedReader is = new BufferedReader(new InputStreamReader(new FileInputStream(FILE_NAME)))) {
+            String str = Strings.EMPTY;
+            while (is.ready()) {
+                str = is.readLine();
+                // System.out.println(str);
+                ++lines;
+                final Matcher matcher = pattern.matcher(str);
+                assertTrue("Unexpected data: " + str, matcher.matches());
+            }
         }
-        fis.close();
-        assertTrue("Incorrect count: was " + counter + " should be " + count, count == counter);
-        fis.close();
-
+        Assert.assertEquals(count, lines);
     }
 
     public class FileWriterRunnable implements Runnable {
         private final boolean lock;
-        private final int count;
-        private final Exception[] error;
+        private final int logEventCount;
+        private final Exception[] exceptionRef;
 
-        public FileWriterRunnable(final boolean lock, final int count, final Exception[] error) {
+        public FileWriterRunnable(final boolean lock, final int logEventCount, final Exception[] exceptionRef) {
             this.lock = lock;
-            this.count = count;
-            this.error = error;
+            this.logEventCount = logEventCount;
+            this.exceptionRef = exceptionRef;
         }
 
         @Override
@@ -229,10 +276,9 @@ public class FileAppenderTest {
             final Thread thread = Thread.currentThread();
 
             try {
-                writer(lock, count, thread.getName());
-
+                writer(lock, logEventCount, thread.getName(), lazyCreate, true);
             } catch (final Exception ex) {
-                error[0] = ex;
+                exceptionRef[0] = ex;
                 throw new RuntimeException(ex);
             }
         }
@@ -256,10 +302,12 @@ public class FileAppenderTest {
             }
             final boolean lock = Boolean.parseBoolean(args[2]);
 
+            final boolean lazyCreate = Boolean.parseBoolean(args[2]);
+
             // System.out.println("Got arguments " + id + ", " + count + ", " + lock);
 
             try {
-                writer(lock, count, id);
+                writer(lock, count, id, lazyCreate, true);
                 // thread.sleep(50);
 
             } catch (final Exception ex) {

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/61f706fc/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/InMemoryAppender.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/InMemoryAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/InMemoryAppender.java
index 2d7ccaa..da8b71a 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/InMemoryAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/InMemoryAppender.java
@@ -17,6 +17,7 @@
 package org.apache.logging.log4j.test.appender;
 
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.Serializable;
 
 import org.apache.logging.log4j.core.Layout;
@@ -48,7 +49,11 @@ public class InMemoryAppender extends AbstractOutputStreamAppender<InMemoryAppen
 
         @Override
         public String toString() {
-            return getOutputStream().toString();
+            try {
+                return getOutputStream().toString();
+            } catch (IOException e) {
+                throw new IllegalStateException(e);
+            }
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/61f706fc/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 1f1fa85..922aef0 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -60,6 +60,9 @@
       <action issue="LOG4J2-1313" dev="rpopma" type="fix" due-to="Philipp Knobel">
         Properties declared in configuration can now have their value either in the element body or in an attribute named "value".
       </action>
+      <action issue="LOG4J2-1501" dev="ggregory" type="add" due-to="Gary Gregory">
+        [LOG4J2-1501] FileAppender should be able to create files lazily.        
+      </action>
       <action issue="LOG4J2-1471" dev="ggregory" type="add" due-to="Gary Gregory">
         [PatternLayout] Add an ANSI option to %xThrowable.
       </action>