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