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 */
017package org.apache.logging.log4j.core.appender.rolling;
018
019import java.io.BufferedOutputStream;
020import java.io.File;
021import java.io.FileNotFoundException;
022import java.io.FileOutputStream;
023import java.io.IOException;
024import java.io.OutputStream;
025import java.io.Serializable;
026import java.util.concurrent.Semaphore;
027
028import org.apache.logging.log4j.core.Layout;
029import org.apache.logging.log4j.core.LogEvent;
030import org.apache.logging.log4j.core.appender.FileManager;
031import org.apache.logging.log4j.core.appender.ManagerFactory;
032import org.apache.logging.log4j.core.appender.rolling.action.AbstractAction;
033import org.apache.logging.log4j.core.appender.rolling.action.Action;
034
035/**
036 * The Rolling File Manager.
037 */
038public 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, final boolean writeHeader) {
053        super(fileName, os, append, false, advertiseURI, layout, bufferSize, writeHeader);
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 <T extends TriggeringPolicy> T getTriggeringPolicy() {
143        // TODO We could parameterize this class with a TriggeringPolicy instead of type casting here. 
144        return (T) this.triggeringPolicy;
145    }
146
147    /**
148     * Returns the rollover strategy
149     * @return The RolloverStrategy
150     */
151    public RolloverStrategy getRolloverStrategy() {
152        return this.rolloverStrategy;
153    }
154
155    private boolean rollover(final RolloverStrategy strategy) {
156
157        try {
158            // Block until the asynchronous operation is completed.
159            semaphore.acquire();
160        } catch (final InterruptedException ie) {
161            LOGGER.error("Thread interrupted while attempting to check rollover", ie);
162            return false;
163        }
164
165        boolean success = false;
166        Thread thread = null;
167
168        try {
169            final RolloverDescription descriptor = strategy.rollover(this);
170            if (descriptor != null) {
171                writeFooter();
172                close();
173                if (descriptor.getSynchronous() != null) {
174                    LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous());
175                    try {
176                        success = descriptor.getSynchronous().execute();
177                    } catch (final Exception ex) {
178                        LOGGER.error("Error in synchronous task", ex);
179                    }
180                }
181
182                if (success && descriptor.getAsynchronous() != null) {
183                    LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
184                    thread = new Thread(new AsyncAction(descriptor.getAsynchronous(), this));
185                    thread.start();
186                }
187                return true;
188            }
189            return false;
190        } finally {
191            if (thread == null || !thread.isAlive()) {
192                semaphore.release();
193            }
194        }
195
196    }
197
198    /**
199     * Performs actions asynchronously.
200     */
201    private static class AsyncAction extends AbstractAction {
202
203        private final Action action;
204        private final RollingFileManager manager;
205
206        /**
207         * Constructor.
208         * @param act The action to perform.
209         * @param manager The manager.
210         */
211        public AsyncAction(final Action act, final RollingFileManager manager) {
212            this.action = act;
213            this.manager = manager;
214        }
215
216        /**
217         * Perform an action.
218         *
219         * @return true if action was successful.  A return value of false will cause
220         *         the rollover to be aborted if possible.
221         * @throws java.io.IOException if IO error, a thrown exception will cause the rollover
222         *                             to be aborted if possible.
223         */
224        @Override
225        public boolean execute() throws IOException {
226            try {
227                return action.execute();
228            } finally {
229                manager.semaphore.release();
230            }
231        }
232
233        /**
234         * Cancels the action if not already initialized or waits till completion.
235         */
236        @Override
237        public void close() {
238            action.close();
239        }
240
241        /**
242         * Determines if action has been completed.
243         *
244         * @return true if action is complete.
245         */
246        @Override
247        public boolean isComplete() {
248            return action.isComplete();
249        }
250    }
251
252    /**
253     * Factory data.
254     */
255    private static class FactoryData {
256        private final String pattern;
257        private final boolean append;
258        private final boolean bufferedIO;
259        private final int bufferSize;
260        private final TriggeringPolicy policy;
261        private final RolloverStrategy strategy;
262        private final String advertiseURI;
263        private final Layout<? extends Serializable> layout;
264
265        /**
266         * Create the data for the factory.
267         * @param pattern The pattern.
268         * @param append The append flag.
269         * @param bufferedIO The bufferedIO flag.
270         * @param advertiseURI
271         * @param layout The Layout.
272         * @param bufferSize the buffer size
273         */
274        public FactoryData(final String pattern, final boolean append, final boolean bufferedIO,
275                final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
276                final Layout<? extends Serializable> layout, final int bufferSize) {
277            this.pattern = pattern;
278            this.append = append;
279            this.bufferedIO = bufferedIO;
280            this.bufferSize = bufferSize;
281            this.policy = policy;
282            this.strategy = strategy;
283            this.advertiseURI = advertiseURI;
284            this.layout = layout;
285        }
286    }
287
288    /**
289     * Factory to create a RollingFileManager.
290     */
291    private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> {
292
293        /**
294         * Create the RollingFileManager.
295         * @param name The name of the entity to manage.
296         * @param data The data required to create the entity.
297         * @return a RollingFileManager.
298         */
299        @Override
300        public RollingFileManager createManager(final String name, final FactoryData data) {
301            final File file = new File(name);
302            final File parent = file.getParentFile();
303            if (null != parent && !parent.exists()) {
304                parent.mkdirs();
305            }
306            try {
307                file.createNewFile();
308            } catch (final IOException ioe) {
309                LOGGER.error("Unable to create file " + name, ioe);
310                return null;
311            }
312            final long size = data.append ? file.length() : 0;
313
314            final boolean writeHeader = !data.append || !file.exists();
315            OutputStream os;
316            try {
317                os = new FileOutputStream(name, data.append);
318                int bufferSize = data.bufferSize;
319                if (data.bufferedIO) {
320                    os = new BufferedOutputStream(os, bufferSize);
321                } else {
322                    bufferSize = -1; // negative buffer size signals bufferedIO was configured false
323                }
324                final long time = file.lastModified(); // LOG4J2-531 create file first so time has valid value
325                return new RollingFileManager(name, data.pattern, os, data.append, size, time, data.policy,
326                    data.strategy, data.advertiseURI, data.layout, bufferSize, writeHeader);
327            } catch (final FileNotFoundException ex) {
328                LOGGER.error("FileManager (" + name + ") " + ex);
329            }
330            return null;
331        }
332    }
333
334}