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