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