You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2017/06/20 21:39:52 UTC

logging-log4j2 git commit: [LOG4J2-1699] Configurable Log File Permissions with PosixFilePermission. Patch applied with some minor tweaks.

Repository: logging-log4j2
Updated Branches:
  refs/heads/master 457f2262e -> 8318a3856


[LOG4J2-1699] Configurable Log File Permissions with
PosixFilePermission. Patch applied with some minor tweaks.

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

Branch: refs/heads/master
Commit: 8318a3856ac6fedb2374fa894b48f5e54ff9d7d9
Parents: 457f226
Author: Pierrick HYMBERT <pi...@gmail.com>
Authored: Tue Jun 20 14:39:47 2017 -0700
Committer: Gary Gregory <gg...@apache.org>
Committed: Tue Jun 20 14:39:47 2017 -0700

----------------------------------------------------------------------
 .../log4j/core/appender/FileAppender.java       |  38 ++-
 .../log4j/core/appender/FileManager.java        | 105 ++++++-
 .../core/appender/RollingFileAppender.java      |  40 ++-
 .../RollingRandomAccessFileAppender.java        |  29 +-
 .../appender/rolling/RollingFileManager.java    |  81 ++++--
 .../rolling/RollingRandomAccessFileManager.java |  37 ++-
 .../core/appender/FilePermissionsTest.java      | 133 +++++++++
 .../rolling/CronTriggeringPolicyTest.java       | 278 +++++++++----------
 .../rolling/OnStartupTriggeringPolicyTest.java  |   2 +-
 .../RollingRandomAccessFileManagerTest.java     |  33 ++-
 pom.xml                                         |   2 +-
 src/changes/changes.xml                         |   3 +
 12 files changed, 591 insertions(+), 190 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8318a385/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 030126b..8e5736f 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
@@ -70,6 +70,15 @@ public final class FileAppender extends AbstractOutputStreamAppender<FileManager
         @PluginBuilderAttribute
         private boolean createOnDemand;
 
+        @PluginBuilderAttribute
+        private String filePermissions;
+
+        @PluginBuilderAttribute
+        private String fileOwner;
+
+        @PluginBuilderAttribute
+        private String fileGroup;
+
         @Override
         public FileAppender build() {
             boolean bufferedIo = isBufferedIo();
@@ -84,7 +93,7 @@ public final class FileAppender extends AbstractOutputStreamAppender<FileManager
             final Layout<? extends Serializable> layout = getOrCreateLayout();
 
             final FileManager manager = FileManager.getFileManager(fileName, append, locking, bufferedIo, createOnDemand,
-                    advertiseUri, layout, bufferSize, getConfiguration());
+                    advertiseUri, layout, bufferSize, filePermissions, fileOwner, fileGroup, getConfiguration());
             if (manager == null) {
                 return null;
             }
@@ -117,6 +126,18 @@ public final class FileAppender extends AbstractOutputStreamAppender<FileManager
             return locking;
         }
 
+        public String getFilePermissions() {
+            return filePermissions;
+        }
+
+        public String getFileOwner() {
+            return fileOwner;
+        }
+
+        public String getFileGroup() {
+            return fileGroup;
+        }
+
         public B withAdvertise(final boolean advertise) {
             this.advertise = advertise;
             return asBuilder();
@@ -147,6 +168,21 @@ public final class FileAppender extends AbstractOutputStreamAppender<FileManager
             return asBuilder();
         }
 
+        public B withFilePermissions(final String filePermissions) {
+            this.filePermissions = filePermissions;
+            return asBuilder();
+        }
+
+        public B withFileOwner(final String fileOwner) {
+            this.fileOwner = fileOwner;
+            return asBuilder();
+        }
+
+        public B withFileGroup(final String fileGroup) {
+            this.fileGroup = fileGroup;
+            return asBuilder();
+        }
+
     }
     
     private static final int DEFAULT_BUFFER_SIZE = 8192;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8318a385/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 c0b8adb..c07afc2 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
@@ -17,7 +17,6 @@
 package org.apache.logging.log4j.core.appender;
 
 import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -25,9 +24,20 @@ import java.io.Serializable;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileLock;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.logging.log4j.core.Layout;
 import org.apache.logging.log4j.core.LoggerContext;
@@ -48,6 +58,9 @@ public class FileManager extends OutputStreamManager {
     private final boolean isLocking;
     private final String advertiseURI;
     private final int bufferSize;
+    private final Set<PosixFilePermission> filePermissions;
+    private final String fileOwner;
+    private final String fileGroup;
 
     /**
      * @deprecated
@@ -73,11 +86,16 @@ public class FileManager extends OutputStreamManager {
         this.isLocking = locking;
         this.advertiseURI = advertiseURI;
         this.bufferSize = buffer.capacity();
+        this.filePermissions = null;
+        this.fileOwner = null;
+        this.fileGroup = null;
     }
 
     /**
+     * @deprecated
      * @since 2.7
      */
+    @Deprecated
     protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking,
             final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
             final boolean writeHeader, final ByteBuffer buffer) {
@@ -87,6 +105,30 @@ public class FileManager extends OutputStreamManager {
         this.isLocking = locking;
         this.advertiseURI = advertiseURI;
         this.bufferSize = buffer.capacity();
+        this.filePermissions = null;
+        this.fileOwner = null;
+        this.fileGroup = null;
+    }
+
+    /**
+     * @since 2.8.3
+     */
+    protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking,
+            final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
+            final String filePermissions, final String fileOwner, final String fileGroup, final boolean writeHeader,
+            final ByteBuffer buffer) {
+        super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer);
+        this.isAppend = append;
+        this.createOnDemand = createOnDemand;
+        this.isLocking = locking;
+        this.advertiseURI = advertiseURI;
+        this.bufferSize = buffer.capacity();
+
+        final Set<String> views = FileSystems.getDefault().supportedFileAttributeViews();
+        this.filePermissions = filePermissions != null && views.contains("posix")
+                                ? PosixFilePermissions.fromString(filePermissions) : null;
+        this.fileOwner = views.contains("owner") ? fileOwner : null;
+        this.fileGroup = views.contains("group") ? fileGroup : null;
     }
 
     /**
@@ -99,25 +141,62 @@ public class FileManager extends OutputStreamManager {
      * @param advertiseUri the URI to use when advertising the file
      * @param layout The layout
      * @param bufferSize buffer size for buffered IO
+     * @param filePermissions File permissions
+     * @param fileOwner File owner
+     * @param fileOwner File group
      * @param configuration The configuration.
      * @return A FileManager for the File.
      */
     public static FileManager getFileManager(final String fileName, final boolean append, boolean locking,
             final boolean bufferedIo, final boolean createOnDemand, final String advertiseUri,
-            final Layout<? extends Serializable> layout, final int bufferSize, final Configuration configuration) {
+            final Layout<? extends Serializable> layout,
+            final int bufferSize, final String filePermissions, final String fileOwner, final String fileGroup,
+            final Configuration configuration) {
 
         if (locking && bufferedIo) {
             locking = false;
         }
         return (FileManager) getManager(fileName, new FactoryData(append, locking, bufferedIo, bufferSize,
-                createOnDemand, advertiseUri, layout, configuration), FACTORY);
+                createOnDemand, advertiseUri, layout, filePermissions, fileOwner, fileGroup, configuration), FACTORY);
     }
 
     @Override
-    protected OutputStream createOutputStream() throws FileNotFoundException {
-        String filename = getFileName();
+    protected OutputStream createOutputStream() throws IOException {
+        final String filename = getFileName();
         LOGGER.debug("Now writing to {} at {}", filename, new Date());
-        return new FileOutputStream(filename, isAppend);
+        final FileOutputStream fos = new FileOutputStream(filename, isAppend);
+        definePathAttributeView(Paths.get(filename));
+        return fos;
+    }
+
+    protected void definePathAttributeView(final Path path) throws IOException {
+      if (filePermissions != null || fileOwner != null || fileGroup != null) {
+        // FileOutputStream may not create new file on all jvm
+        path.toFile().createNewFile();
+
+        final PosixFileAttributeView view = Files.getFileAttributeView(path,
+            PosixFileAttributeView.class);
+        if (view != null) {
+          final UserPrincipalLookupService lookupService = FileSystems.getDefault()
+              .getUserPrincipalLookupService();
+          if (fileOwner != null) {
+            final UserPrincipal userPrincipal = lookupService.lookupPrincipalByName(fileOwner);
+            if (userPrincipal != null) {
+              view.setOwner(userPrincipal);
+            }
+          }
+          if (fileGroup != null) {
+            final GroupPrincipal groupPrincipal = lookupService
+                .lookupPrincipalByGroupName(fileGroup);
+            if (groupPrincipal != null) {
+              view.setGroup(groupPrincipal);
+            }
+          }
+          if (filePermissions != null) {
+            view.setPermissions(filePermissions);
+          }
+        }
+      }
     }
 
     @Override
@@ -154,6 +233,7 @@ public class FileManager extends OutputStreamManager {
      * @param length how many bytes to write
      * @since 2.8
      */
+    @Override
     protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
         if (isLocking) {
             try {
@@ -242,6 +322,9 @@ public class FileManager extends OutputStreamManager {
         private final boolean createOnDemand;
         private final String advertiseURI;
         private final Layout<? extends Serializable> layout;
+        private final String filePermissions;
+        private final String fileOwner;
+        private final String fileGroup;
 
         /**
          * Constructor.
@@ -252,10 +335,14 @@ public class FileManager extends OutputStreamManager {
          * @param createOnDemand 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 filePermissions File permissions
+         * @param fileOwner File owner
+         * @param fileGroup File group
          * @param configuration the configuration
          */
         public FactoryData(final boolean append, final boolean locking, final boolean bufferedIo, final int bufferSize,
                 final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
+                final String filePermissions, final String fileOwner, final String fileGroup,
                 final Configuration configuration) {
             super(configuration);
             this.append = append;
@@ -265,6 +352,9 @@ public class FileManager extends OutputStreamManager {
             this.createOnDemand = createOnDemand;
             this.advertiseURI = advertiseURI;
             this.layout = layout;
+            this.filePermissions = filePermissions;
+            this.fileOwner = fileOwner;
+            this.fileGroup = fileGroup;
         }
     }
 
@@ -289,7 +379,8 @@ public class FileManager extends OutputStreamManager {
                 final ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[actualSize]);
                 final FileOutputStream fos = data.createOnDemand ? null : new FileOutputStream(file, data.append);
                 return new FileManager(data.getLoggerContext(), name, fos, data.append, data.locking,
-                        data.createOnDemand, data.advertiseURI, data.layout, writeHeader, byteBuffer);
+                        data.createOnDemand, data.advertiseURI, data.layout,
+                        data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, byteBuffer);
             } catch (final IOException ex) {
                 LOGGER.error("FileManager (" + name + ") " + ex, ex);
             }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8318a385/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java
index c3e092a..1072259 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java
@@ -28,11 +28,11 @@ import org.apache.logging.log4j.core.Filter;
 import org.apache.logging.log4j.core.Layout;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
+import org.apache.logging.log4j.core.appender.rolling.DirectFileRolloverStrategy;
 import org.apache.logging.log4j.core.appender.rolling.DirectWriteRolloverStrategy;
 import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
 import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
 import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
-import org.apache.logging.log4j.core.appender.rolling.DirectFileRolloverStrategy;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.plugins.Plugin;
 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
@@ -89,6 +89,15 @@ public final class RollingFileAppender extends AbstractOutputStreamAppender<Roll
         @PluginBuilderAttribute
         private boolean createOnDemand;
 
+        @PluginBuilderAttribute
+        private String filePermissions;
+
+        @PluginBuilderAttribute
+        private String fileOwner;
+
+        @PluginBuilderAttribute
+        private String fileGroup;
+
         @Override
         public RollingFileAppender build() {
             // Even though some variables may be annotated with @Required, we must still perform validation here for
@@ -130,7 +139,7 @@ public final class RollingFileAppender extends AbstractOutputStreamAppender<Roll
             final Layout<? extends Serializable> layout = getOrCreateLayout();
             final RollingFileManager manager = RollingFileManager.getFileManager(fileName, filePattern, append,
                     isBufferedIo, policy, strategy, advertiseUri, layout, bufferSize, isImmediateFlush(),
-                    createOnDemand, getConfiguration());
+                    createOnDemand, filePermissions, fileOwner, fileGroup, getConfiguration());
             if (manager == null) {
                 return null;
             }
@@ -165,6 +174,18 @@ public final class RollingFileAppender extends AbstractOutputStreamAppender<Roll
             return locking;
         }
 
+        public String getFilePermissions() {
+            return filePermissions;
+        }
+
+        public String getFileOwner() {
+            return fileOwner;
+        }
+
+        public String getFileGroup() {
+            return fileGroup;
+        }
+
         public B withAdvertise(final boolean advertise) {
             this.advertise = advertise;
             return asBuilder();
@@ -222,6 +243,21 @@ public final class RollingFileAppender extends AbstractOutputStreamAppender<Roll
             return asBuilder();
         }
 
+        public B withFilePermissions(final String filePermissions) {
+            this.filePermissions = filePermissions;
+            return asBuilder();
+        }
+
+        public B withFileOwner(final String fileOwner) {
+            this.fileOwner = fileOwner;
+            return asBuilder();
+        }
+
+        public B withFileGroup(final String fileGroup) {
+            this.fileGroup = fileGroup;
+            return asBuilder();
+        }
+
     }
     
     private static final int DEFAULT_BUFFER_SIZE = 8192;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8318a385/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java
index f0416ce..89c2008 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java
@@ -33,11 +33,9 @@ import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
 import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
 import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.layout.PatternLayout;
 import org.apache.logging.log4j.core.net.Advertiser;
 import org.apache.logging.log4j.core.util.Booleans;
 import org.apache.logging.log4j.core.util.Integers;
@@ -80,6 +78,15 @@ public final class RollingRandomAccessFileAppender extends AbstractOutputStreamA
         @PluginBuilderAttribute("advertiseURI")
         private String advertiseURI;
 
+        @PluginBuilderAttribute
+        private String filePermissions;
+
+        @PluginBuilderAttribute
+        private String fileOwner;
+
+        @PluginBuilderAttribute
+        private String fileGroup;
+
         @Override
         public RollingRandomAccessFileAppender build() {
             final String name = getName();
@@ -114,7 +121,8 @@ public final class RollingRandomAccessFileAppender extends AbstractOutputStreamA
             final int bufferSize = getBufferSize();
             final RollingRandomAccessFileManager manager = RollingRandomAccessFileManager
                     .getRollingRandomAccessFileManager(fileName, filePattern, append, immediateFlush, bufferSize, policy,
-                            strategy, advertiseURI, layout, getConfiguration());
+                            strategy, advertiseURI, layout,
+                            filePermissions, fileOwner, fileGroup, getConfiguration());
             if (manager == null) {
                 return null;
             }
@@ -160,6 +168,21 @@ public final class RollingRandomAccessFileAppender extends AbstractOutputStreamA
             return asBuilder();
         }
 
+        public B withFilePermissions(final String filePermissions) {
+            this.filePermissions = filePermissions;
+            return asBuilder();
+        }
+
+        public B withFileOwner(final String fileOwner) {
+            this.fileOwner = fileOwner;
+            return asBuilder();
+        }
+
+        public B withFileGroup(final String fileGroup) {
+            this.fileGroup = fileGroup;
+            return asBuilder();
+        }
+
     }
     
     private final String fileName;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8318a385/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java
index 6e83abb..e0d0360 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java
@@ -64,10 +64,11 @@ public class RollingFileManager extends FileManager {
     private volatile boolean renameEmptyFiles = false;
     private volatile boolean initialized = false;
     private volatile String fileName;
-    private FileExtension fileExtension;
+    private final FileExtension fileExtension;
+
     /* This executor pool will create a new Thread for every work async action to be performed. Using it allows
        us to make sure all the Threads are completed when the Manager is stopped. */
-    private ExecutorService asyncExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0, TimeUnit.MILLISECONDS,
+    private final ExecutorService asyncExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0, TimeUnit.MILLISECONDS,
             new EmptyQueue(), threadFactory);
 
     private static final AtomicReferenceFieldUpdater<RollingFileManager, TriggeringPolicy> triggeringPolicyUpdater =
@@ -101,9 +102,7 @@ public class RollingFileManager extends FileManager {
         this.fileExtension = FileExtension.lookupForFile(pattern);
     }
 
-    /**
-     * @since 2.7
-     */
+    @Deprecated
     protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os,
             final boolean append, final boolean createOnDemand, final long size, final long time,
             final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy,
@@ -119,6 +118,27 @@ public class RollingFileManager extends FileManager {
         this.fileExtension = FileExtension.lookupForFile(pattern);
     }
 
+    /**
+     * @since 2.8.3
+     */
+    protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os,
+            final boolean append, final boolean createOnDemand, final long size, final long time,
+            final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy,
+            final String advertiseURI, final Layout<? extends Serializable> layout,
+            final String filePermissions, final String fileOwner, final String fileGroup,
+            final boolean writeHeader, final ByteBuffer buffer) {
+        super(loggerContext, fileName, os, append, false, createOnDemand, advertiseURI, layout,
+              filePermissions, fileOwner, fileGroup, writeHeader, buffer);
+        this.size = size;
+        this.initialTime = time;
+        this.triggeringPolicy = triggeringPolicy;
+        this.rolloverStrategy = rolloverStrategy;
+        this.patternProcessor = new PatternProcessor(pattern);
+        this.patternProcessor.setPrevFileTime(time);
+        this.fileName = fileName;
+        this.fileExtension = FileExtension.lookupForFile(pattern);
+    }
+
     public void initialize() {
 
         if (!initialized) {
@@ -144,16 +164,22 @@ public class RollingFileManager extends FileManager {
      * @param bufferSize buffer size to use if bufferedIO is true
      * @param immediateFlush flush on every write or not
      * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.)
+     * @param filePermissions File permissions
+     * @param fileOwner File owner
+     * @param fileGroup File group
      * @param configuration The configuration.
      * @return A RollingFileManager.
      */
     public static RollingFileManager getFileManager(final String fileName, final String pattern, final boolean append,
             final boolean bufferedIO, final TriggeringPolicy policy, final RolloverStrategy strategy,
             final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize,
-            final boolean immediateFlush, final boolean createOnDemand, final Configuration configuration) {
-        String name = fileName == null ? pattern : fileName;
+            final boolean immediateFlush, final boolean createOnDemand,
+            final String filePermissions, final String fileOwner, final String fileGroup,
+            final Configuration configuration) {
+        final String name = fileName == null ? pattern : fileName;
         return (RollingFileManager) getManager(name, new FactoryData(fileName, pattern, append,
-            bufferedIO, policy, strategy, advertiseURI, layout, bufferSize, immediateFlush, createOnDemand, configuration), factory);
+            bufferedIO, policy, strategy, advertiseURI, layout, bufferSize, immediateFlush, createOnDemand,
+            filePermissions, fileOwner, fileGroup, configuration), factory);
     }
 
     /**
@@ -229,12 +255,12 @@ public class RollingFileManager extends FileManager {
             ((LifeCycle) triggeringPolicy).stop();
             stopped &= true;
         }
-        boolean status = super.releaseSub(timeout, timeUnit) && stopped;
+        final boolean status = super.releaseSub(timeout, timeUnit) && stopped;
         asyncExecutor.shutdown();
         try {
             // Allow at least the minimum interval to pass so async actions can complete.
-            long millis = timeUnit.toMillis(timeout);
-            long waitInterval = MIN_DURATION < millis ? millis : MIN_DURATION;
+            final long millis = timeUnit.toMillis(timeout);
+            final long waitInterval = MIN_DURATION < millis ? millis : MIN_DURATION;
 
             for (int count = 1; count <= MAX_TRIES && !asyncExecutor.isTerminated(); ++count) {
                 asyncExecutor.awaitTermination(waitInterval * count, TimeUnit.MILLISECONDS);
@@ -300,7 +326,7 @@ public class RollingFileManager extends FileManager {
 
     public void setTriggeringPolicy(final TriggeringPolicy triggeringPolicy) {
         triggeringPolicy.initialize(this);
-        TriggeringPolicy policy = this.triggeringPolicy;
+        final TriggeringPolicy policy = this.triggeringPolicy;
         int count = 0;
         boolean policyUpdated = false;
         do {
@@ -474,6 +500,9 @@ public class RollingFileManager extends FileManager {
         private final RolloverStrategy strategy;
         private final String advertiseURI;
         private final Layout<? extends Serializable> layout;
+        private final String filePermissions;
+        private final String fileOwner;
+        private final String fileGroup;
 
         /**
          * Creates the data for the factory.
@@ -485,12 +514,16 @@ public class RollingFileManager extends FileManager {
          * @param bufferSize the buffer size
          * @param immediateFlush flush on every write or not
          * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.)
+         * @param filePermissions File permissions
+         * @param fileOwner File owner
+         * @param fileGroup File group
          * @param configuration The configuration
          */
         public FactoryData(final String fileName, final String pattern, final boolean append, final boolean bufferedIO,
                 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
                 final Layout<? extends Serializable> layout, final int bufferSize, final boolean immediateFlush,
-                final boolean createOnDemand, final Configuration configuration) {
+                final boolean createOnDemand, final String filePermissions, final String fileOwner, final String fileGroup,
+                final Configuration configuration) {
             super(configuration);
             this.fileName = fileName;
             this.pattern = pattern;
@@ -503,6 +536,9 @@ public class RollingFileManager extends FileManager {
             this.layout = layout;
             this.immediateFlush = immediateFlush;
             this.createOnDemand = createOnDemand;
+            this.filePermissions = filePermissions;
+            this.fileOwner = fileOwner;
+            this.fileGroup = fileGroup;
         }
 
         public TriggeringPolicy getTriggeringPolicy()
@@ -535,6 +571,10 @@ public class RollingFileManager extends FileManager {
             builder.append(advertiseURI);
             builder.append(", layout=");
             builder.append(layout);
+            builder.append(", filePermissions=");
+            builder.append(filePermissions);
+            builder.append(", fileOwner=");
+            builder.append(fileOwner);
             builder.append("]");
             return builder.toString();
         }
@@ -590,7 +630,7 @@ public class RollingFileManager extends FileManager {
 
                 return new RollingFileManager(data.getLoggerContext(), data.fileName, data.pattern, os,
                         data.append, data.createOnDemand, size, time, data.policy, data.strategy, data.advertiseURI,
-                        data.layout, writeHeader, buffer);
+                        data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, buffer);
             } catch (final IOException ex) {
                 LOGGER.error("RollingFileManager (" + name + ") " + ex, ex);
             }
@@ -600,6 +640,11 @@ public class RollingFileManager extends FileManager {
 
     private static class EmptyQueue extends ArrayBlockingQueue<Runnable> {
 
+        /**
+         * 
+         */
+        private static final long serialVersionUID = 1L;
+
         EmptyQueue() {
             super(1);
         }
@@ -610,24 +655,24 @@ public class RollingFileManager extends FileManager {
         }
 
         @Override
-        public boolean add(Runnable runnable) {
+        public boolean add(final Runnable runnable) {
             throw new IllegalStateException("Queue is full");
         }
 
         @Override
-        public void put(Runnable runnable) throws InterruptedException {
+        public void put(final Runnable runnable) throws InterruptedException {
             /* No point in going into a permanent wait */
             throw new InterruptedException("Unable to insert into queue");
         }
 
         @Override
-        public boolean offer(Runnable runnable, long timeout, TimeUnit timeUnit) throws InterruptedException {
+        public boolean offer(final Runnable runnable, final long timeout, final TimeUnit timeUnit) throws InterruptedException {
             Thread.sleep(timeUnit.toMillis(timeout));
             return false;
         }
 
         @Override
-        public boolean addAll(Collection<? extends Runnable> collection) {
+        public boolean addAll(final Collection<? extends Runnable> collection) {
             if (collection.size() > 0) {
                 throw new IllegalArgumentException("Too many items in collection");
             }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8318a385/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 21c8918..f210bf5 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
@@ -47,12 +47,28 @@ public class RollingRandomAccessFileManager extends RollingFileManager {
     private RandomAccessFile randomAccessFile;
     private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>();
 
+    @Deprecated
     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) {
+        this(loggerContext, raf, fileName, pattern, os, append, immediateFlush, bufferSize, size, time, policy, strategy, advertiseURI,
+               layout, null, null, null, writeHeader);
+    }
+
+    /**
+     * @since 2.8.3
+     */
+    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 String filePermissions, final String fileOwner, final String fileGroup,
+            final boolean writeHeader) {
         super(loggerContext, fileName, pattern, os, append, false, size, time, policy, strategy, advertiseURI, layout,
+                filePermissions, fileOwner, fileGroup,
                 writeHeader, ByteBuffer.wrap(new byte[bufferSize]));
         this.randomAccessFile = raf;
         isEndOfBatch.set(Boolean.FALSE);
@@ -83,9 +99,11 @@ public class RollingRandomAccessFileManager extends RollingFileManager {
     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) {
+            final Layout<? extends Serializable> layout, final String filePermissions, final String fileOwner, final String fileGroup,
+            final Configuration configuration) {
         return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend,
-                immediateFlush, bufferSize, policy, strategy, advertiseURI, layout, configuration), FACTORY);
+                immediateFlush, bufferSize, policy, strategy, advertiseURI, layout,
+                filePermissions, fileOwner, fileGroup, configuration), FACTORY);
     }
 
     public Boolean isEndOfBatch() {
@@ -188,7 +206,7 @@ public class RollingRandomAccessFileManager extends RollingFileManager {
                 }
                 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);
+                        data.strategy, data.advertiseURI, data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader);
             } catch (final IOException ex) {
                 LOGGER.error("Cannot access RandomAccessFile " + ex, ex);
                 if (raf != null) {
@@ -215,6 +233,9 @@ public class RollingRandomAccessFileManager extends RollingFileManager {
         private final RolloverStrategy strategy;
         private final String advertiseURI;
         private final Layout<? extends Serializable> layout;
+        private final String filePermissions;
+        private final String fileOwner;
+        private final String fileGroup;
 
         /**
          * Create the data for the factory.
@@ -227,11 +248,16 @@ public class RollingRandomAccessFileManager extends RollingFileManager {
          * @param strategy
          * @param advertiseURI
          * @param layout
+         * @param filePermissions File permissions
+         * @param fileOwner File owner
+         * @param fileGroup File group
          * @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) {
+                final String advertiseURI, final Layout<? extends Serializable> layout,
+                final String filePermissions, final String fileOwner, final String fileGroup,
+                final Configuration configuration) {
             super(configuration);
             this.pattern = pattern;
             this.append = append;
@@ -241,6 +267,9 @@ public class RollingRandomAccessFileManager extends RollingFileManager {
             this.strategy = strategy;
             this.advertiseURI = advertiseURI;
             this.layout = layout;
+            this.filePermissions = filePermissions;
+            this.fileOwner = fileOwner;
+            this.fileGroup = fileGroup;
         }
 
         public TriggeringPolicy getTriggeringPolicy()

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8318a385/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FilePermissionsTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FilePermissionsTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FilePermissionsTest.java
new file mode 100644
index 0000000..df35c3b
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FilePermissionsTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.appender;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.junit.CleanFiles;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Tests {@link FileAppender}.
+ */
+@RunWith(Parameterized.class)
+public class FilePermissionsTest {
+
+  @Parameterized.Parameters(name = "{0} {1}")
+  public static Collection<Object[]> data() {
+      return Arrays.asList(new Object[][] { //
+              // @formatter:off
+             {"rwxrwxrwx", true},
+             {"rw-rw-r--", false},
+             {"rw-------", true},
+              });
+              // @formatter:on
+  }
+
+  private final boolean createOnDemand;
+  private final String filePermissions;
+
+    public FilePermissionsTest(final String filePermissions,  final boolean createOnDemand) {
+      this.filePermissions = filePermissions;
+      this.createOnDemand = createOnDemand;
+    }
+
+    private static final String FILE_NAME = "target/fileAppenderTest.log";
+    private static final Path PATH = Paths.get(FILE_NAME);
+
+    @Rule
+    public CleanFiles files = new CleanFiles(PATH);
+
+    @AfterClass
+    public static void cleanupClass() {
+        assertTrue("Manager for " + FILE_NAME + " not removed", !AbstractManager.hasManager(FILE_NAME));
+    }
+
+    @Test
+    public void testFilePermissions() throws Exception {
+        if (!FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
+          return;
+        }
+        final Layout<String> layout = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
+            .build();
+        // @formatter:off
+        final FileAppender appender = FileAppender.newBuilder()
+            .withFileName(FILE_NAME)
+            .withName("test")
+            .withImmediateFlush(false)
+            .withIgnoreExceptions(false)
+            .withBufferedIo(false)
+            .withBufferSize(1)
+            .withLayout(layout)
+            .withCreateOnDemand(createOnDemand)
+            .withFilePermissions(filePermissions)
+            .build();
+        // @formatter:on
+        try {
+            appender.start();
+            final File file = new File(FILE_NAME);
+            assertTrue("Appender did not start", appender.isStarted());
+            Assert.assertNotEquals(createOnDemand, 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(FilePermissionsTest.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;
+            }
+            assertEquals(filePermissions,
+                PosixFilePermissions.toString(Files.getPosixFilePermissions(PATH)));
+        } finally {
+            appender.stop();
+        }
+        assertFalse("Appender did not stop", appender.isStarted());
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8318a385/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.java
index 828f78b..f6bf296 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.java
@@ -1,139 +1,139 @@
-/*
- * 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 org.apache.logging.log4j.core.appender.RollingFileAppender;
-import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender;
-import org.apache.logging.log4j.core.config.Configurator;
-import org.apache.logging.log4j.core.config.NullConfiguration;
-import org.apache.logging.log4j.core.layout.PatternLayout;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-public class CronTriggeringPolicyTest {
-
-    private static final String CRON_EXPRESSION = "0 0 0 * * ?";
-    
-    private NullConfiguration configuration;
-
-     // TODO Need a CleanRegexFiles("testcmd.\\.log\\..*");
-     //@Rule
-     //public CleanFiles cleanFiles = new CleanFiles("testcmd1.log", "testcmd2.log", "testcmd3.log");
-
-    @Before
-    public void before() {
-        configuration = new NullConfiguration();
-    }
-
-    private CronTriggeringPolicy createPolicy() {
-        return CronTriggeringPolicy.createPolicy(configuration, Boolean.TRUE.toString(), CRON_EXPRESSION);
-    }
-
-    private DefaultRolloverStrategy createStrategy() {
-        return DefaultRolloverStrategy.createStrategy("7", "1", "max", null, null, false, configuration);
-    }
-
-    private void testBuilder() {
-        // @formatter:off
-        final RollingFileAppender raf = RollingFileAppender.newBuilder()
-            .withName("test1")
-            .withFileName("target/testcmd1.log")
-            .withFilePattern("target/testcmd1.log.%d{yyyy-MM-dd}")
-            .withPolicy(createPolicy())
-            .withStrategy(createStrategy())
-            .setConfiguration(configuration)
-            .build();
-        // @formatter:on
-        Assert.assertNotNull(raf);
-    }
-
-    /**
-     * Tests LOG4J2-1474 CronTriggeringPolicy raise exception and fail to rollover log file when evaluateOnStartup is
-     * true.
-     */
-    @Test
-    public void testBuilderOnce() {
-        testBuilder();
-    }
-
-    /**
-     * Tests LOG4J2-1740 Add CronTriggeringPolicy programmatically leads to NPE
-     */
-    @Test
-    public void testLoggerContextAndBuilder() {
-        Configurator.initialize(configuration);
-        testBuilder();
-    }
-
-    /**
-     * Tests LOG4J2-1740 Add CronTriggeringPolicy programmatically leads to NPE
-     */
-    @Test
-    public void testRollingRandomAccessFileAppender() {
-        // @formatter:off
-        RollingRandomAccessFileAppender.newBuilder()
-            .withName("test2")
-            .withFileName("target/testcmd2.log")
-            .withFilePattern("target/testcmd2.log.%d{yyyy-MM-dd}")
-            .withPolicy(createPolicy())
-            .withStrategy(createStrategy())
-            .setConfiguration(configuration)
-            .build();
-        // @formatter:on
-    }
-
-    
-    /**
-     * Tests LOG4J2-1474 CronTriggeringPolicy raise exception and fail to rollover log file when evaluateOnStartup is
-     * true.
-     */
-    @Test
-    public void testBuilderSequence() {
-        testBuilder();
-        testBuilder();
-    }
-
-    private void testFactoryMethod() {
-        final CronTriggeringPolicy triggerPolicy = createPolicy();
-        final DefaultRolloverStrategy rolloverStrategy = createStrategy();
-
-        try (RollingFileManager fileManager = RollingFileManager.getFileManager("target/testcmd3.log",
-                "target/testcmd3.log.%d{yyyy-MM-dd}", true, true, triggerPolicy, rolloverStrategy, null,
-                PatternLayout.createDefaultLayout(), 0, true, false, configuration)) {
-            // trigger rollover
-            fileManager.initialize();
-            fileManager.rollover();
-        }
-    }
-
-    /**
-     * Tests LOG4J2-1474 CronTriggeringPolicy raise exception and fail to rollover log file when evaluateOnStartup is
-     * true.
-     */
-    @Test
-    public void testFactoryMethodOnce() {
-        testFactoryMethod();
-    }
-
-    @Test
-    public void testFactoryMethodSequence() {
-        testFactoryMethod();
-        testFactoryMethod();
-    }
-}
+/*
+ * 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 org.apache.logging.log4j.core.appender.RollingFileAppender;
+import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.config.NullConfiguration;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CronTriggeringPolicyTest {
+
+    private static final String CRON_EXPRESSION = "0 0 0 * * ?";
+    
+    private NullConfiguration configuration;
+
+     // TODO Need a CleanRegexFiles("testcmd.\\.log\\..*");
+     //@Rule
+     //public CleanFiles cleanFiles = new CleanFiles("testcmd1.log", "testcmd2.log", "testcmd3.log");
+
+    @Before
+    public void before() {
+        configuration = new NullConfiguration();
+    }
+
+    private CronTriggeringPolicy createPolicy() {
+        return CronTriggeringPolicy.createPolicy(configuration, Boolean.TRUE.toString(), CRON_EXPRESSION);
+    }
+
+    private DefaultRolloverStrategy createStrategy() {
+        return DefaultRolloverStrategy.createStrategy("7", "1", "max", null, null, false, configuration);
+    }
+
+    private void testBuilder() {
+        // @formatter:off
+        final RollingFileAppender raf = RollingFileAppender.newBuilder()
+            .withName("test1")
+            .withFileName("target/testcmd1.log")
+            .withFilePattern("target/testcmd1.log.%d{yyyy-MM-dd}")
+            .withPolicy(createPolicy())
+            .withStrategy(createStrategy())
+            .setConfiguration(configuration)
+            .build();
+        // @formatter:on
+        Assert.assertNotNull(raf);
+    }
+
+    /**
+     * Tests LOG4J2-1474 CronTriggeringPolicy raise exception and fail to rollover log file when evaluateOnStartup is
+     * true.
+     */
+    @Test
+    public void testBuilderOnce() {
+        testBuilder();
+    }
+
+    /**
+     * Tests LOG4J2-1740 Add CronTriggeringPolicy programmatically leads to NPE
+     */
+    @Test
+    public void testLoggerContextAndBuilder() {
+        Configurator.initialize(configuration);
+        testBuilder();
+    }
+
+    /**
+     * Tests LOG4J2-1740 Add CronTriggeringPolicy programmatically leads to NPE
+     */
+    @Test
+    public void testRollingRandomAccessFileAppender() {
+        // @formatter:off
+        RollingRandomAccessFileAppender.newBuilder()
+            .withName("test2")
+            .withFileName("target/testcmd2.log")
+            .withFilePattern("target/testcmd2.log.%d{yyyy-MM-dd}")
+            .withPolicy(createPolicy())
+            .withStrategy(createStrategy())
+            .setConfiguration(configuration)
+            .build();
+        // @formatter:on
+    }
+
+    
+    /**
+     * Tests LOG4J2-1474 CronTriggeringPolicy raise exception and fail to rollover log file when evaluateOnStartup is
+     * true.
+     */
+    @Test
+    public void testBuilderSequence() {
+        testBuilder();
+        testBuilder();
+    }
+
+    private void testFactoryMethod() {
+        final CronTriggeringPolicy triggerPolicy = createPolicy();
+        final DefaultRolloverStrategy rolloverStrategy = createStrategy();
+
+        try (RollingFileManager fileManager = RollingFileManager.getFileManager("target/testcmd3.log",
+                "target/testcmd3.log.%d{yyyy-MM-dd}", true, true, triggerPolicy, rolloverStrategy, null,
+                PatternLayout.createDefaultLayout(), 0, true, false, null, null, null, configuration)) {
+            // trigger rollover
+            fileManager.initialize();
+            fileManager.rollover();
+        }
+    }
+
+    /**
+     * Tests LOG4J2-1474 CronTriggeringPolicy raise exception and fail to rollover log file when evaluateOnStartup is
+     * true.
+     */
+    @Test
+    public void testFactoryMethodOnce() {
+        testFactoryMethod();
+    }
+
+    @Test
+    public void testFactoryMethodSequence() {
+        testFactoryMethod();
+        testFactoryMethod();
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8318a385/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java
index 6cf8474..f05e4e2 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java
@@ -78,7 +78,7 @@ public class OnStartupTriggeringPolicyTest {
                 configuration);
         final OnStartupTriggeringPolicy policy = OnStartupTriggeringPolicy.createPolicy(1);
         try (final RollingFileManager manager = RollingFileManager.getFileManager(TARGET_FILE, TARGET_PATTERN, true, false,
-                policy, strategy, null, layout, 8192, true, false, configuration)) {
+                policy, strategy, null, layout, 8192, true, false, null, null, null, configuration)) {
             manager.initialize();
             final String files = Arrays.toString(new File(TARGET_FOLDER).listFiles());
             assertTrue(target.toString() + ", files = " + files, Files.exists(target));

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8318a385/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java
index 67abb55..8fa07b9 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java
@@ -17,6 +17,17 @@
 
 package org.apache.logging.log4j.core.appender.rolling;
 
+import static org.apache.logging.log4j.hamcrest.FileMatchers.beforeNow;
+import static org.apache.logging.log4j.hamcrest.FileMatchers.hasLength;
+import static org.apache.logging.log4j.hamcrest.FileMatchers.isEmpty;
+import static org.apache.logging.log4j.hamcrest.FileMatchers.lastModified;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -29,14 +40,6 @@ import org.apache.logging.log4j.core.util.NullOutputStream;
 import org.apache.logging.log4j.util.Strings;
 import org.junit.Test;
 
-import static org.apache.logging.log4j.hamcrest.FileMatchers.beforeNow;
-import static org.apache.logging.log4j.hamcrest.FileMatchers.hasLength;
-import static org.apache.logging.log4j.hamcrest.FileMatchers.isEmpty;
-import static org.apache.logging.log4j.hamcrest.FileMatchers.lastModified;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.lessThanOrEqualTo;
-import static org.junit.Assert.*;
-
 /**
  * Tests the RollingRandomAccessFileManager class.
  */
@@ -60,7 +63,8 @@ public class RollingRandomAccessFileManagerTest {
             final RolloverStrategy rolloverStrategy = null;
             final RollingRandomAccessFileManager manager = new RollingRandomAccessFileManager(null, raf,
                     file.getName(), Strings.EMPTY, os, append, flushNow,
-                    RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, triggerSize, time, triggerPolicy, rolloverStrategy, null, null, true);
+                    RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, triggerSize, time, triggerPolicy, rolloverStrategy,
+                    null, null, null, null, null, true);
 
             final int size = RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE * 3;
             final byte[] data = new byte[size];
@@ -89,7 +93,8 @@ public class RollingRandomAccessFileManagerTest {
             final RolloverStrategy rolloverStrategy = null;
             final RollingRandomAccessFileManager manager = new RollingRandomAccessFileManager(null, raf,
                     file.getName(), Strings.EMPTY, os, append, flushNow,
-                    RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, triggerSize, time, triggerPolicy, rolloverStrategy, null, null, true);
+                    RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, triggerSize, time, triggerPolicy, rolloverStrategy,
+                    null, null, null, null, null, true);
 
             final int size = RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE * 3 + 1;
             final byte[] data = new byte[size];
@@ -117,7 +122,7 @@ public class RollingRandomAccessFileManagerTest {
             final RolloverStrategy rolloverStrategy = null;
             final RollingRandomAccessFileManager manager = new RollingRandomAccessFileManager(null, raf,
                     file.getName(), Strings.EMPTY, os, append, flushNow, bufferSize, triggerSize, time, triggerPolicy,
-                    rolloverStrategy, null, null, true);
+                    rolloverStrategy, null, null, null, null, null, true);
 
             // check the resulting buffer size is what was requested
             assertEquals(bufferSize, manager.getBufferSize());
@@ -149,7 +154,7 @@ public class RollingRandomAccessFileManagerTest {
                 //
                 file.getAbsolutePath(), Strings.EMPTY, isAppend, immediateFlush,
                 RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, new SizeBasedTriggeringPolicy(Long.MAX_VALUE), //
-                null, null, null, null);
+                null, null, null, null, null, null, null);
         manager.write(bytes, 0, bytes.length, immediateFlush);
         final int expected = bytes.length * 2;
         assertThat("appended, not overwritten", file, hasLength(expected));
@@ -171,7 +176,7 @@ public class RollingRandomAccessFileManagerTest {
                 //
                 file.getAbsolutePath(), Strings.EMPTY, isAppend, true,
                 RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, new SizeBasedTriggeringPolicy(Long.MAX_VALUE), //
-                null, null, null, null);
+                null, null, null, null, null, null, null);
         assertTrue(manager.getFileTime() < expectedMax);
         assertTrue(manager.getFileTime() >= expectedMin);
     }
@@ -189,7 +194,7 @@ public class RollingRandomAccessFileManagerTest {
                 //
                 file.getAbsolutePath(), Strings.EMPTY, isAppend, true,
                 RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, new SizeBasedTriggeringPolicy(Long.MAX_VALUE), //
-                null, null, null, null);
+                null, null, null, null, null, null, null);
         assertThat(file, lastModified(equalTo(manager.getFileTime())));
     }
 

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8318a385/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 808fd15..b592d4b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -281,7 +281,7 @@
       <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
-        <version>3.5</version>
+        <version>3.6</version>
       </dependency>
       <dependency>
         <groupId>com.beust</groupId>

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8318a385/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 21f9b31..2f072c1 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -31,6 +31,9 @@
          - "remove" - Removed
     -->
     <release version="2.9.0" date="2017-MM-DD" description="GA Release 2.9.0">
+      <action issue="LOG4J2-1699" dev="ggregory" type="add" due-to="Demetrios Dimatos, Pierrick HYMBERT">
+        Configurable Log File Permissions with PosixFilePermission.
+      </action>
       <action issue="LOG4J2-1945" dev="ggregory" type="add">
         Generate source jas for all test jars.
       </action>