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    }