    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     */
    017    package org.apache.logging.log4j.core.appender.rolling;
    018
    019    import java.io.BufferedOutputStream;
    020    import java.io.File;
    021    import java.io.FileNotFoundException;
    022    import java.io.FileOutputStream;
    023    import java.io.IOException;
    024    import java.io.OutputStream;
    025    import java.io.Serializable;
    026    import java.util.concurrent.Semaphore;
    027
    028    import org.apache.logging.log4j.core.Layout;
    029    import org.apache.logging.log4j.core.LogEvent;
    030    import org.apache.logging.log4j.core.appender.FileManager;
    031    import org.apache.logging.log4j.core.appender.ManagerFactory;
    032    import org.apache.logging.log4j.core.appender.rolling.action.AbstractAction;
    033    import org.apache.logging.log4j.core.appender.rolling.action.Action;
    034
    035    /**
    036     * The Rolling File Manager.
    037     */
    038    public class RollingFileManager extends FileManager {
    039
    040        private static RollingFileManagerFactory factory = new RollingFileManagerFactory();
    041
    042        private long size;
    043        private long initialTime;
    044        private final PatternProcessor patternProcessor;
    045        private final Semaphore semaphore = new Semaphore(1);
    046        private final TriggeringPolicy triggeringPolicy;
    047        private final RolloverStrategy rolloverStrategy;
    048
    049        protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
    050                final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy,
    051                final RolloverStrategy rolloverStrategy, final String advertiseURI,
    052                final Layout<? extends Serializable> layout, final int bufferSize) {
    053            super(fileName, os, append, false, advertiseURI, layout, bufferSize);
    054            this.size = size;
    055            this.initialTime = time;
    056            this.triggeringPolicy = triggeringPolicy;
    057            this.rolloverStrategy = rolloverStrategy;
    058            this.patternProcessor = new PatternProcessor(pattern);
    059            triggeringPolicy.initialize(this);
    060        }
    061
    062        /**
    063         * Returns a RollingFileManager.
    064         * @param fileName The file name.
    065         * @param pattern The pattern for rolling file.
    066         * @param append true if the file should be appended to.
    067         * @param bufferedIO true if data should be buffered.
    068         * @param policy The TriggeringPolicy.
    069         * @param strategy The RolloverStrategy.
    070         * @param advertiseURI the URI to use when advertising the file
    071         * @param layout The Layout.
    072         * @param bufferSize buffer size to use if bufferedIO is true
    073         * @return A RollingFileManager.
    074         */
    075        public static RollingFileManager getFileManager(final String fileName, final String pattern, final boolean append,
    076                final boolean bufferedIO, final TriggeringPolicy policy, final RolloverStrategy strategy,
    077                final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize) {
    078
    079            return (RollingFileManager) getManager(fileName, new FactoryData(pattern, append,
    080                bufferedIO, policy, strategy, advertiseURI, layout, bufferSize), factory);
    081        }
    082
    083        @Override
    084        protected synchronized void write(final byte[] bytes, final int offset, final int length) {
    085            size += length;
    086            super.write(bytes, offset, length);
    087        }
    088
    089        /**
    090         * Returns the current size of the file.
    091         * @return The size of the file in bytes.
    092         */
    093        public long getFileSize() {
    094            return size;
    095        }
    096
    097        /**
    098         * Returns the time the file was created.
    099         * @return The time the file was created.
    100         */
    101        public long getFileTime() {
    102            return initialTime;
    103        }
    104
    105        /**
    106         * Determine if a rollover should occur.
    107         * @param event The LogEvent.
    108         */
    109        public synchronized void checkRollover(final LogEvent event) {
    110            if (triggeringPolicy.isTriggeringEvent(event) && rollover(rolloverStrategy)) {
    111                try {
    112                    size = 0;
    113                    initialTime = System.currentTimeMillis();
    114                    createFileAfterRollover();
    115                } catch (final IOException ex) {
    116                    LOGGER.error("FileManager (" + getFileName() + ") " + ex);
    117                }
    118            }
    119        }
    120
    121        protected void createFileAfterRollover() throws IOException {
    122            final OutputStream os = new FileOutputStream(getFileName(), isAppend());
    123            if (getBufferSize() > 0) { // negative buffer size means no buffering
    124                setOutputStream(new BufferedOutputStream(os, getBufferSize()));
    125            } else {
    126                setOutputStream(os);
    127            }
    128        }
    129
    130        /**
    131         * Returns the pattern processor.
    132         * @return The PatternProcessor.
    133         */
    134        public PatternProcessor getPatternProcessor() {
    135            return patternProcessor;
    136        }
    137
    138        /**
    139         * Returns the triggering policy
    140         * @return The TriggeringPolicy
    141         */
    142        public TriggeringPolicy getTriggeringPolicy() {
    143            return this.triggeringPolicy;
    144        }
    145
    146        /**
    147         * Returns the rollover strategy
    148         * @return The RolloverStrategy
    149         */
    150        public RolloverStrategy getRolloverStrategy() {
    151            return this.rolloverStrategy;
    152        }
    153
    154        private boolean rollover(final RolloverStrategy strategy) {
    155
    156            try {
    157                // Block until the asynchronous operation is completed.
    158                semaphore.acquire();
    159            } catch (final InterruptedException ie) {
    160                LOGGER.error("Thread interrupted while attempting to check rollover", ie);
    161                return false;
    162            }
    163
    164            boolean success = false;
    165            Thread thread = null;
    166
    167            try {
    168                final RolloverDescription descriptor = strategy.rollover(this);
    169                if (descriptor != null) {
    170                    writeFooter();
    171                    close();
    172                    if (descriptor.getSynchronous() != null) {
    173                        LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous());
    174                        try {
    175                            success = descriptor.getSynchronous().execute();
    176                        } catch (final Exception ex) {
    177                            LOGGER.error("Error in synchronous task", ex);
    178                        }
    179                    }
    180
    181                    if (success && descriptor.getAsynchronous() != null) {
    182                        LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
    183                        thread = new Thread(new AsyncAction(descriptor.getAsynchronous(), this));
    184                        thread.start();
    185                    }
    186                    return true;
    187                }
    188                return false;
    189            } finally {
    190                if (thread == null || !thread.isAlive()) {
    191                    semaphore.release();
    192                }
    193            }
    194
    195        }
    196
    197        /**
    198         * Performs actions asynchronously.
    199         */
    200        private static class AsyncAction extends AbstractAction {
    201
    202            private final Action action;
    203            private final RollingFileManager manager;
    204
    205            /**
    206             * Constructor.
    207             * @param act The action to perform.
    208             * @param manager The manager.
    209             */
    210            public AsyncAction(final Action act, final RollingFileManager manager) {
    211                this.action = act;
    212                this.manager = manager;
    213            }
    214
    215            /**
    216             * Perform an action.
    217             *
    218             * @return true if action was successful.  A return value of false will cause
    219             *         the rollover to be aborted if possible.
    220             * @throws java.io.IOException if IO error, a thrown exception will cause the rollover
    221             *                             to be aborted if possible.
    222             */
    223            @Override
    224            public boolean execute() throws IOException {
    225                try {
    226                    return action.execute();
    227                } finally {
    228                    manager.semaphore.release();
    229                }
    230            }
    231
    232            /**
    233             * Cancels the action if not already initialized or waits till completion.
    234             */
    235            @Override
    236            public void close() {
    237                action.close();
    238            }
    239
    240            /**
    241             * Determines if action has been completed.
    242             *
    243             * @return true if action is complete.
    244             */
    245            @Override
    246            public boolean isComplete() {
    247                return action.isComplete();
    248            }
    249        }
    250
    251        /**
    252         * Factory data.
    253         */
    254        private static class FactoryData {
    255            private final String pattern;
    256            private final boolean append;
    257            private final boolean bufferedIO;
    258            private final int bufferSize;
    259            private final TriggeringPolicy policy;
    260            private final RolloverStrategy strategy;
    261            private final String advertiseURI;
    262            private final Layout<? extends Serializable> layout;
    263
    264            /**
    265             * Create the data for the factory.
    266             * @param pattern The pattern.
    267             * @param append The append flag.
    268             * @param bufferedIO The bufferedIO flag.
    269             * @param advertiseURI
    270             * @param layout The Layout.
    271             * @param bufferSize the buffer size
    272             */
    273            public FactoryData(final String pattern, final boolean append, final boolean bufferedIO,
    274                    final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
    275                    final Layout<? extends Serializable> layout, final int bufferSize) {
    276                this.pattern = pattern;
    277                this.append = append;
    278                this.bufferedIO = bufferedIO;
    279                this.bufferSize = bufferSize;
    280                this.policy = policy;
    281                this.strategy = strategy;
    282                this.advertiseURI = advertiseURI;
    283                this.layout = layout;
    284            }
    285        }
    286
    287        /**
    288         * Factory to create a RollingFileManager.
    289         */
    290        private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> {
    291
    292            /**
    293             * Create the RollingFileManager.
    294             * @param name The name of the entity to manage.
    295             * @param data The data required to create the entity.
    296             * @return a RollingFileManager.
    297             */
    298            @Override
    299            public RollingFileManager createManager(final String name, final FactoryData data) {
    300                final File file = new File(name);
    301                final File parent = file.getParentFile();
    302                if (null != parent && !parent.exists()) {
    303                    parent.mkdirs();
    304                }
    305                try {
    306                    file.createNewFile();
    307                } catch (final IOException ioe) {
    308                    LOGGER.error("Unable to create file " + name, ioe);
    309                    return null;
    310                }
    311                final long size = data.append ? file.length() : 0;
    312
    313                OutputStream os;
    314                try {
    315                    os = new FileOutputStream(name, data.append);
    316                    int bufferSize = data.bufferSize;
    317                    if (data.bufferedIO) {
    318                        os = new BufferedOutputStream(os, bufferSize);
    319                    } else {
    320                        bufferSize = -1; // negative buffer size signals bufferedIO was configured false
    321                    }
    322                    final long time = file.lastModified(); // LOG4J2-531 create file first so time has valid value
    323                    return new RollingFileManager(name, data.pattern, os, data.append, size, time, data.policy,
    324                        data.strategy, data.advertiseURI, data.layout, bufferSize);
    325                } catch (final FileNotFoundException ex) {
    326                    LOGGER.error("FileManager (" + name + ") " + ex);
    327                }
    328                return null;
    329            }
    330        }
    331
    332    }