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