001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.appender.rolling;
018
019import java.io.File;
020import java.io.FileOutputStream;
021import java.io.IOException;
022import java.io.OutputStream;
023import java.io.Serializable;
024import java.nio.ByteBuffer;
025import java.nio.file.Files;
026import java.nio.file.Path;
027import java.nio.file.attribute.BasicFileAttributes;
028import java.nio.file.attribute.FileTime;
029import java.util.Collection;
030import java.util.Date;
031import java.util.concurrent.ArrayBlockingQueue;
032import java.util.concurrent.ExecutorService;
033import java.util.concurrent.Semaphore;
034import java.util.concurrent.ThreadPoolExecutor;
035import java.util.concurrent.TimeUnit;
036import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
037
038import org.apache.logging.log4j.core.Layout;
039import org.apache.logging.log4j.core.LifeCycle;
040import org.apache.logging.log4j.core.LifeCycle2;
041import org.apache.logging.log4j.core.LogEvent;
042import org.apache.logging.log4j.core.LoggerContext;
043import org.apache.logging.log4j.core.appender.ConfigurationFactoryData;
044import org.apache.logging.log4j.core.appender.FileManager;
045import org.apache.logging.log4j.core.appender.ManagerFactory;
046import org.apache.logging.log4j.core.appender.rolling.action.AbstractAction;
047import org.apache.logging.log4j.core.appender.rolling.action.Action;
048import org.apache.logging.log4j.core.config.Configuration;
049import org.apache.logging.log4j.core.util.Constants;
050import org.apache.logging.log4j.core.util.FileUtils;
051import org.apache.logging.log4j.core.util.Log4jThreadFactory;
052
053/**
054 * The Rolling File Manager.
055 */
056public class RollingFileManager extends FileManager {
057
058    private static RollingFileManagerFactory factory = new RollingFileManagerFactory();
059    private static final int MAX_TRIES = 3;
060    private static final int MIN_DURATION = 100;
061    private static final FileTime EPOCH = FileTime.fromMillis(0);
062
063    protected long size;
064    private long initialTime;
065    private volatile PatternProcessor patternProcessor;
066    private final Semaphore semaphore = new Semaphore(1);
067    private final Log4jThreadFactory threadFactory = Log4jThreadFactory.createThreadFactory("RollingFileManager");
068    private volatile TriggeringPolicy triggeringPolicy;
069    private volatile RolloverStrategy rolloverStrategy;
070    private volatile boolean renameEmptyFiles = false;
071    private volatile boolean initialized = false;
072    private volatile String fileName;
073    private final FileExtension fileExtension;
074
075    /* This executor pool will create a new Thread for every work async action to be performed. Using it allows
076       us to make sure all the Threads are completed when the Manager is stopped. */
077    private final ExecutorService asyncExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0, TimeUnit.MILLISECONDS,
078            new EmptyQueue(), threadFactory);
079
080    private static final AtomicReferenceFieldUpdater<RollingFileManager, TriggeringPolicy> triggeringPolicyUpdater =
081            AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, TriggeringPolicy.class, "triggeringPolicy");
082
083    private static final AtomicReferenceFieldUpdater<RollingFileManager, RolloverStrategy> rolloverStrategyUpdater =
084            AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, RolloverStrategy.class, "rolloverStrategy");
085
086    private static final AtomicReferenceFieldUpdater<RollingFileManager, PatternProcessor> patternProcessorUpdater =
087            AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, PatternProcessor.class, "patternProcessor");
088
089    @Deprecated
090    protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
091            final boolean append, final long size, final long initialTime, final TriggeringPolicy triggeringPolicy,
092            final RolloverStrategy rolloverStrategy, final String advertiseURI,
093            final Layout<? extends Serializable> layout, final int bufferSize, final boolean writeHeader) {
094        this(fileName, pattern, os, append, size, initialTime, triggeringPolicy, rolloverStrategy, advertiseURI, layout,
095                writeHeader, ByteBuffer.wrap(new byte[Constants.ENCODER_BYTE_BUFFER_SIZE]));
096    }
097
098    @Deprecated
099    protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
100            final boolean append, final long size, final long initialTime, final TriggeringPolicy triggeringPolicy,
101            final RolloverStrategy rolloverStrategy, final String advertiseURI,
102            final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) {
103        super(fileName != null ? fileName : pattern, os, append, false, advertiseURI, layout, writeHeader,
104                        buffer);
105        this.size = size;
106        this.initialTime = initialTime;
107        this.triggeringPolicy = triggeringPolicy;
108        this.rolloverStrategy = rolloverStrategy;
109        this.patternProcessor = new PatternProcessor(pattern);
110        this.patternProcessor.setPrevFileTime(initialTime);
111        this.fileName = fileName;
112        this.fileExtension = FileExtension.lookupForFile(pattern);
113    }
114
115    @Deprecated
116    protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os,
117            final boolean append, final boolean createOnDemand, final long size, final long initialTime,
118            final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy,
119            final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) {
120        super(loggerContext, fileName != null ? fileName : pattern, os, append, false, createOnDemand,
121                        advertiseURI, layout, writeHeader, buffer);
122        this.size = size;
123        this.initialTime = initialTime;
124        this.triggeringPolicy = triggeringPolicy;
125        this.rolloverStrategy = rolloverStrategy;
126        this.patternProcessor = new PatternProcessor(pattern);
127        this.patternProcessor.setPrevFileTime(initialTime);
128        this.fileName = fileName;
129        this.fileExtension = FileExtension.lookupForFile(pattern);
130    }
131
132    /**
133     * @since 2.9
134     */
135    protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os,
136            final boolean append, final boolean createOnDemand, final long size, final long initialTime,
137            final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy,
138            final String advertiseURI, final Layout<? extends Serializable> layout,
139            final String filePermissions, final String fileOwner, final String fileGroup,
140            final boolean writeHeader, final ByteBuffer buffer) {
141        super(loggerContext, fileName != null ? fileName : pattern, os, append, false, createOnDemand,
142                        advertiseURI, layout, filePermissions, fileOwner, fileGroup, writeHeader, buffer);
143        this.size = size;
144        this.initialTime = initialTime;
145        this.triggeringPolicy = triggeringPolicy;
146        this.rolloverStrategy = rolloverStrategy;
147        this.patternProcessor = new PatternProcessor(pattern);
148        this.patternProcessor.setPrevFileTime(initialTime);
149        this.fileName = fileName;
150        this.fileExtension = FileExtension.lookupForFile(pattern);
151    }
152
153    public void initialize() {
154
155        if (!initialized) {
156            LOGGER.debug("Initializing triggering policy {}", triggeringPolicy);
157            initialized = true;
158            triggeringPolicy.initialize(this);
159            if (triggeringPolicy instanceof LifeCycle) {
160                ((LifeCycle) triggeringPolicy).start();
161            }
162            if (rolloverStrategy instanceof DirectFileRolloverStrategy) {
163                // LOG4J2-2485: Initialize size from the most recently written file.
164                File file = new File(getFileName());
165                if (file.exists()) {
166                    size = file.length();
167                } else {
168                    ((DirectFileRolloverStrategy) rolloverStrategy).clearCurrentFileName();
169                }
170            }
171        }
172    }
173
174    /**
175     * Returns a RollingFileManager.
176     * @param fileName The file name.
177     * @param pattern The pattern for rolling file.
178     * @param append true if the file should be appended to.
179     * @param bufferedIO true if data should be buffered.
180     * @param policy The TriggeringPolicy.
181     * @param strategy The RolloverStrategy.
182     * @param advertiseURI the URI to use when advertising the file
183     * @param layout The Layout.
184     * @param bufferSize buffer size to use if bufferedIO is true
185     * @param immediateFlush flush on every write or not
186     * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.)
187     * @param filePermissions File permissions
188     * @param fileOwner File owner
189     * @param fileGroup File group
190     * @param configuration The configuration.
191     * @return A RollingFileManager.
192     */
193    public static RollingFileManager getFileManager(final String fileName, final String pattern, final boolean append,
194            final boolean bufferedIO, final TriggeringPolicy policy, final RolloverStrategy strategy,
195            final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize,
196            final boolean immediateFlush, final boolean createOnDemand,
197            final String filePermissions, final String fileOwner, final String fileGroup,
198            final Configuration configuration) {
199
200        if (strategy instanceof DirectWriteRolloverStrategy && fileName != null) {
201            LOGGER.error("The fileName attribute must not be specified with the DirectWriteRolloverStrategy");
202            return null;
203        }
204        final String name = fileName == null ? pattern : fileName;
205        return narrow(RollingFileManager.class, getManager(name, new FactoryData(fileName, pattern, append,
206            bufferedIO, policy, strategy, advertiseURI, layout, bufferSize, immediateFlush, createOnDemand,
207            filePermissions, fileOwner, fileGroup, configuration), factory));
208    }
209
210    /**
211     * Returns the name of the File being managed.
212     * @return The name of the File being managed.
213     */
214    @Override
215    public String getFileName() {
216        if (rolloverStrategy instanceof DirectFileRolloverStrategy) {
217            fileName = ((DirectFileRolloverStrategy) rolloverStrategy).getCurrentFileName(this);
218        }
219        return fileName;
220    }
221
222    public FileExtension getFileExtension() {
223        return fileExtension;
224    }
225
226    // override to make visible for unit tests
227    @Override
228    protected synchronized void write(final byte[] bytes, final int offset, final int length,
229            final boolean immediateFlush) {
230        super.write(bytes, offset, length, immediateFlush);
231    }
232
233    @Override
234    protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
235        size += length;
236        super.writeToDestination(bytes, offset, length);
237    }
238
239    public boolean isRenameEmptyFiles() {
240        return renameEmptyFiles;
241    }
242
243    public void setRenameEmptyFiles(final boolean renameEmptyFiles) {
244        this.renameEmptyFiles = renameEmptyFiles;
245    }
246
247    /**
248     * Returns the current size of the file.
249     * @return The size of the file in bytes.
250     */
251    public long getFileSize() {
252        return size + byteBuffer.position();
253    }
254
255    /**
256     * Returns the time the file was created.
257     * @return The time the file was created.
258     */
259    public long getFileTime() {
260        return initialTime;
261    }
262
263    /**
264     * Determines if a rollover should occur.
265     * @param event The LogEvent.
266     */
267    public synchronized void checkRollover(final LogEvent event) {
268        if (triggeringPolicy.isTriggeringEvent(event)) {
269            rollover();
270        }
271    }
272
273    @Override
274    public boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
275        LOGGER.debug("Shutting down RollingFileManager {}", getName());
276        boolean stopped = true;
277        if (triggeringPolicy instanceof LifeCycle2) {
278            stopped &= ((LifeCycle2) triggeringPolicy).stop(timeout, timeUnit);
279        } else if (triggeringPolicy instanceof LifeCycle) {
280            ((LifeCycle) triggeringPolicy).stop();
281            stopped &= true;
282        }
283        final boolean status = super.releaseSub(timeout, timeUnit) && stopped;
284        asyncExecutor.shutdown();
285        try {
286            // Allow at least the minimum interval to pass so async actions can complete.
287            final long millis = timeUnit.toMillis(timeout);
288            final long waitInterval = MIN_DURATION < millis ? millis : MIN_DURATION;
289
290            for (int count = 1; count <= MAX_TRIES && !asyncExecutor.isTerminated(); ++count) {
291                asyncExecutor.awaitTermination(waitInterval * count, TimeUnit.MILLISECONDS);
292            }
293            if (asyncExecutor.isTerminated()) {
294                LOGGER.debug("All asynchronous threads have terminated");
295            } else {
296                asyncExecutor.shutdownNow();
297                try {
298                    asyncExecutor.awaitTermination(timeout, timeUnit);
299                    if (asyncExecutor.isTerminated()) {
300                        LOGGER.debug("All asynchronous threads have terminated");
301                    } else {
302                        LOGGER.debug("RollingFileManager shutting down but some asynchronous services may not have completed");
303                    }
304                } catch (final InterruptedException inner) {
305                    LOGGER.warn("RollingFileManager stopped but some asynchronous services may not have completed.");
306                }
307            }
308        } catch (final InterruptedException ie) {
309            asyncExecutor.shutdownNow();
310            try {
311                asyncExecutor.awaitTermination(timeout, timeUnit);
312                if (asyncExecutor.isTerminated()) {
313                    LOGGER.debug("All asynchronous threads have terminated");
314                }
315            } catch (final InterruptedException inner) {
316                LOGGER.warn("RollingFileManager stopped but some asynchronous services may not have completed.");
317            }
318            // Preserve interrupt status
319            Thread.currentThread().interrupt();
320        }
321        LOGGER.debug("RollingFileManager shutdown completed with status {}", status);
322        return status;
323    }
324
325    public synchronized void rollover(Date prevFileTime, Date prevRollTime) {
326                getPatternProcessor().setPrevFileTime(prevFileTime.getTime());
327                getPatternProcessor().setCurrentFileTime(prevRollTime.getTime());
328                rollover();
329        }
330
331    public synchronized void rollover() {
332        if (!hasOutputStream()) {
333            return;
334        }
335        if (rollover(rolloverStrategy)) {
336            try {
337                size = 0;
338                initialTime = System.currentTimeMillis();
339                createFileAfterRollover();
340            } catch (final IOException e) {
341                logError("Failed to create file after rollover", e);
342            }
343        }
344    }
345
346    protected void createFileAfterRollover() throws IOException  {
347        setOutputStream(createOutputStream());
348    }
349
350    /**
351     * Returns the pattern processor.
352     * @return The PatternProcessor.
353     */
354    public PatternProcessor getPatternProcessor() {
355        return patternProcessor;
356    }
357
358    public void setTriggeringPolicy(final TriggeringPolicy triggeringPolicy) {
359        triggeringPolicy.initialize(this);
360        final TriggeringPolicy policy = this.triggeringPolicy;
361        int count = 0;
362        boolean policyUpdated = false;
363        do {
364            ++count;
365        } while (!(policyUpdated = triggeringPolicyUpdater.compareAndSet(this, this.triggeringPolicy, triggeringPolicy))
366                && count < MAX_TRIES);
367        if (policyUpdated) {
368            if (triggeringPolicy instanceof LifeCycle) {
369                ((LifeCycle) triggeringPolicy).start();
370            }
371            if (policy instanceof LifeCycle) {
372                ((LifeCycle) policy).stop();
373            }
374        } else {
375            if (triggeringPolicy instanceof LifeCycle) {
376                ((LifeCycle) triggeringPolicy).stop();
377            }
378        }
379    }
380
381    public void setRolloverStrategy(final RolloverStrategy rolloverStrategy) {
382        rolloverStrategyUpdater.compareAndSet(this, this.rolloverStrategy, rolloverStrategy);
383    }
384
385    public void setPatternProcessor(final PatternProcessor patternProcessor) {
386        patternProcessorUpdater.compareAndSet(this, this.patternProcessor, patternProcessor);
387    }
388
389    /**
390     * Returns the triggering policy.
391     * @param <T> TriggeringPolicy type
392     * @return The TriggeringPolicy
393     */
394    @SuppressWarnings("unchecked")
395    public <T extends TriggeringPolicy> T getTriggeringPolicy() {
396        // TODO We could parameterize this class with a TriggeringPolicy instead of type casting here.
397        return (T) this.triggeringPolicy;
398    }
399
400    /**
401     * Returns the rollover strategy.
402     * @return The RolloverStrategy
403     */
404    public RolloverStrategy getRolloverStrategy() {
405        return this.rolloverStrategy;
406    }
407
408    private boolean rollover(final RolloverStrategy strategy) {
409
410        boolean releaseRequired = false;
411        try {
412            // Block until the asynchronous operation is completed.
413            semaphore.acquire();
414            releaseRequired = true;
415        } catch (final InterruptedException e) {
416            logError("Thread interrupted while attempting to check rollover", e);
417            return false;
418        }
419
420        boolean success = true;
421
422        try {
423            final RolloverDescription descriptor = strategy.rollover(this);
424            if (descriptor != null) {
425                writeFooter();
426                closeOutputStream();
427                if (descriptor.getSynchronous() != null) {
428                    LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous());
429                    try {
430                        success = descriptor.getSynchronous().execute();
431                    } catch (final Exception ex) {
432                        success = false;
433                        logError("Caught error in synchronous task", ex);
434                    }
435                }
436
437                if (success && descriptor.getAsynchronous() != null) {
438                    LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
439                    asyncExecutor.execute(new AsyncAction(descriptor.getAsynchronous(), this));
440                    releaseRequired = false;
441                }
442                return true;
443            }
444            return false;
445        } finally {
446            if (releaseRequired) {
447                semaphore.release();
448            }
449        }
450
451    }
452
453    /**
454     * Performs actions asynchronously.
455     */
456    private static class AsyncAction extends AbstractAction {
457
458        private final Action action;
459        private final RollingFileManager manager;
460
461        /**
462         * Constructor.
463         * @param act The action to perform.
464         * @param manager The manager.
465         */
466        public AsyncAction(final Action act, final RollingFileManager manager) {
467            this.action = act;
468            this.manager = manager;
469        }
470
471        /**
472         * Executes an action.
473         *
474         * @return true if action was successful.  A return value of false will cause
475         *         the rollover to be aborted if possible.
476         * @throws java.io.IOException if IO error, a thrown exception will cause the rollover
477         *                             to be aborted if possible.
478         */
479        @Override
480        public boolean execute() throws IOException {
481            try {
482                return action.execute();
483            } finally {
484                manager.semaphore.release();
485            }
486        }
487
488        /**
489         * Cancels the action if not already initialized or waits till completion.
490         */
491        @Override
492        public void close() {
493            action.close();
494        }
495
496        /**
497         * Determines if action has been completed.
498         *
499         * @return true if action is complete.
500         */
501        @Override
502        public boolean isComplete() {
503            return action.isComplete();
504        }
505
506        @Override
507        public String toString() {
508            final StringBuilder builder = new StringBuilder();
509            builder.append(super.toString());
510            builder.append("[action=");
511            builder.append(action);
512            builder.append(", manager=");
513            builder.append(manager);
514            builder.append(", isComplete()=");
515            builder.append(isComplete());
516            builder.append(", isInterrupted()=");
517            builder.append(isInterrupted());
518            builder.append("]");
519            return builder.toString();
520        }
521    }
522
523    /**
524     * Factory data.
525     */
526    private static class FactoryData extends ConfigurationFactoryData {
527        private final String fileName;
528        private final String pattern;
529        private final boolean append;
530        private final boolean bufferedIO;
531        private final int bufferSize;
532        private final boolean immediateFlush;
533        private final boolean createOnDemand;
534        private final TriggeringPolicy policy;
535        private final RolloverStrategy strategy;
536        private final String advertiseURI;
537        private final Layout<? extends Serializable> layout;
538        private final String filePermissions;
539        private final String fileOwner;
540        private final String fileGroup;
541
542        /**
543         * Creates the data for the factory.
544         * @param pattern The pattern.
545         * @param append The append flag.
546         * @param bufferedIO The bufferedIO flag.
547         * @param advertiseURI
548         * @param layout The Layout.
549         * @param bufferSize the buffer size
550         * @param immediateFlush flush on every write or not
551         * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.)
552         * @param filePermissions File permissions
553         * @param fileOwner File owner
554         * @param fileGroup File group
555         * @param configuration The configuration
556         */
557        public FactoryData(final String fileName, final String pattern, final boolean append, final boolean bufferedIO,
558                final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
559                final Layout<? extends Serializable> layout, final int bufferSize, final boolean immediateFlush,
560                final boolean createOnDemand, final String filePermissions, final String fileOwner, final String fileGroup,
561                final Configuration configuration) {
562            super(configuration);
563            this.fileName = fileName;
564            this.pattern = pattern;
565            this.append = append;
566            this.bufferedIO = bufferedIO;
567            this.bufferSize = bufferSize;
568            this.policy = policy;
569            this.strategy = strategy;
570            this.advertiseURI = advertiseURI;
571            this.layout = layout;
572            this.immediateFlush = immediateFlush;
573            this.createOnDemand = createOnDemand;
574            this.filePermissions = filePermissions;
575            this.fileOwner = fileOwner;
576            this.fileGroup = fileGroup;
577        }
578
579        public TriggeringPolicy getTriggeringPolicy() {
580            return this.policy;
581        }
582
583        public RolloverStrategy getRolloverStrategy() {
584            return this.strategy;
585        }
586
587        public String getPattern() {
588            return pattern;
589        }
590
591        @Override
592        public String toString() {
593            final StringBuilder builder = new StringBuilder();
594            builder.append(super.toString());
595            builder.append("[pattern=");
596            builder.append(pattern);
597            builder.append(", append=");
598            builder.append(append);
599            builder.append(", bufferedIO=");
600            builder.append(bufferedIO);
601            builder.append(", bufferSize=");
602            builder.append(bufferSize);
603            builder.append(", policy=");
604            builder.append(policy);
605            builder.append(", strategy=");
606            builder.append(strategy);
607            builder.append(", advertiseURI=");
608            builder.append(advertiseURI);
609            builder.append(", layout=");
610            builder.append(layout);
611            builder.append(", filePermissions=");
612            builder.append(filePermissions);
613            builder.append(", fileOwner=");
614            builder.append(fileOwner);
615            builder.append("]");
616            return builder.toString();
617        }
618    }
619
620    @Override
621    public void updateData(final Object data) {
622        final FactoryData factoryData = (FactoryData) data;
623        setRolloverStrategy(factoryData.getRolloverStrategy());
624        setTriggeringPolicy(factoryData.getTriggeringPolicy());
625        setPatternProcessor(new PatternProcessor(factoryData.getPattern(), getPatternProcessor()));
626    }
627
628    /**
629     * Factory to create a RollingFileManager.
630     */
631    private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> {
632
633        /**
634         * Creates a RollingFileManager.
635         * @param name The name of the entity to manage.
636         * @param data The data required to create the entity.
637         * @return a RollingFileManager.
638         */
639        @Override
640        public RollingFileManager createManager(final String name, final FactoryData data) {
641            long size = 0;
642            boolean writeHeader = !data.append;
643            File file = null;
644            if (data.fileName != null) {
645                file = new File(data.fileName);
646                // LOG4J2-1140: check writeHeader before creating the file
647                writeHeader = !data.append || !file.exists();
648
649                try {
650                    FileUtils.makeParentDirs(file);
651                    final boolean created = data.createOnDemand ? false : file.createNewFile();
652                    LOGGER.trace("New file '{}' created = {}", name, created);
653                } catch (final IOException ioe) {
654                    LOGGER.error("Unable to create file " + name, ioe);
655                    return null;
656                }
657                size = data.append ? file.length() : 0;
658            }
659
660            try {
661                final int actualSize = data.bufferedIO ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
662                final ByteBuffer buffer = ByteBuffer.wrap(new byte[actualSize]);
663                final OutputStream os = data.createOnDemand  || data.fileName == null ? null :
664                        new FileOutputStream(data.fileName, data.append);
665                final long initialTime = data.createOnDemand || file == null ?
666                        0 : initialFileTime(file); // LOG4J2-531 create file first so time has valid value
667
668                final RollingFileManager rm = new RollingFileManager(data.getLoggerContext(), data.fileName, data.pattern, os,
669                    data.append, data.createOnDemand, size, initialTime, data.policy, data.strategy, data.advertiseURI,
670                    data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, buffer);
671                if (os != null && rm.isAttributeViewEnabled()) {
672                    rm.defineAttributeView(file.toPath());
673                }
674
675                return rm;
676            } catch (final IOException ex) {
677                LOGGER.error("RollingFileManager (" + name + ") " + ex, ex);
678            }
679            return null;
680        }
681    }
682
683    private static long initialFileTime(final File file) {
684        final Path path = file.toPath();
685        if (Files.exists(path)) {
686            try {
687                final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
688                final FileTime fileTime = attrs.creationTime();
689                if (fileTime.compareTo(EPOCH) > 0) {
690                    LOGGER.debug("Returning file creation time for {}", file.getAbsolutePath());
691                    return fileTime.toMillis();
692                }
693                LOGGER.info("Unable to obtain file creation time for " + file.getAbsolutePath());
694            } catch (final Exception ex) {
695                LOGGER.info("Unable to calculate file creation time for " + file.getAbsolutePath() + ": " + ex.getMessage());
696            }
697        }
698        return file.lastModified();
699    }
700
701    private static class EmptyQueue extends ArrayBlockingQueue<Runnable> {
702
703        /**
704         *
705         */
706        private static final long serialVersionUID = 1L;
707
708        EmptyQueue() {
709            super(1);
710        }
711
712        @Override
713        public int remainingCapacity() {
714            return 0;
715        }
716
717        @Override
718        public boolean add(final Runnable runnable) {
719            throw new IllegalStateException("Queue is full");
720        }
721
722        @Override
723        public void put(final Runnable runnable) throws InterruptedException {
724            /* No point in going into a permanent wait */
725            throw new InterruptedException("Unable to insert into queue");
726        }
727
728        @Override
729        public boolean offer(final Runnable runnable, final long timeout, final TimeUnit timeUnit) throws InterruptedException {
730            Thread.sleep(timeUnit.toMillis(timeout));
731            return false;
732        }
733
734        @Override
735        public boolean addAll(final Collection<? extends Runnable> collection) {
736            if (collection.size() > 0) {
737                throw new IllegalArgumentException("Too many items in collection");
738            }
739            return false;
740        }
741
742    }
743
744}