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