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