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;
027import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
028
029import org.apache.logging.log4j.core.Layout;
030import org.apache.logging.log4j.core.LogEvent;
031import org.apache.logging.log4j.core.appender.FileManager;
032import org.apache.logging.log4j.core.appender.ManagerFactory;
033import org.apache.logging.log4j.core.appender.rolling.action.AbstractAction;
034import org.apache.logging.log4j.core.appender.rolling.action.Action;
035import org.apache.logging.log4j.core.util.Log4jThread;
036
037/**
038 * The Rolling File Manager.
039 */
040public class RollingFileManager extends FileManager {
041
042    private static RollingFileManagerFactory factory = new RollingFileManagerFactory();
043
044    private long size;
045    private long initialTime;
046    private final PatternProcessor patternProcessor;
047    private final Semaphore semaphore = new Semaphore(1);
048    private volatile TriggeringPolicy triggeringPolicy;
049    private volatile RolloverStrategy rolloverStrategy;
050
051    private static final AtomicReferenceFieldUpdater<RollingFileManager, TriggeringPolicy> triggeringPolicyUpdater =
052            AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, TriggeringPolicy.class, "triggeringPolicy");
053
054    private static final AtomicReferenceFieldUpdater<RollingFileManager, RolloverStrategy> rolloverStrategyUpdater =
055            AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, RolloverStrategy.class, "rolloverStrategy");
056
057    protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
058            final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy,
059            final RolloverStrategy rolloverStrategy, final String advertiseURI,
060            final Layout<? extends Serializable> layout, final int bufferSize, final boolean writeHeader) {
061        super(fileName, os, append, false, advertiseURI, layout, bufferSize, writeHeader);
062        this.size = size;
063        this.initialTime = time;
064        this.triggeringPolicy = triggeringPolicy;
065        this.rolloverStrategy = rolloverStrategy;
066        this.patternProcessor = new PatternProcessor(pattern);
067        triggeringPolicy.initialize(this);
068    }
069
070    /**
071     * Returns a RollingFileManager.
072     * @param fileName The file name.
073     * @param pattern The pattern for rolling file.
074     * @param append true if the file should be appended to.
075     * @param bufferedIO true if data should be buffered.
076     * @param policy The TriggeringPolicy.
077     * @param strategy The RolloverStrategy.
078     * @param advertiseURI the URI to use when advertising the file
079     * @param layout The Layout.
080     * @param bufferSize buffer size to use if bufferedIO is true
081     * @return A RollingFileManager.
082     */
083    public static RollingFileManager getFileManager(final String fileName, final String pattern, final boolean append,
084            final boolean bufferedIO, final TriggeringPolicy policy, final RolloverStrategy strategy,
085            final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize) {
086
087        return (RollingFileManager) getManager(fileName, new FactoryData(pattern, append,
088            bufferedIO, policy, strategy, advertiseURI, layout, bufferSize), factory);
089    }
090
091    @Override
092    protected synchronized void write(final byte[] bytes, final int offset, final int length) {
093        size += length;
094        super.write(bytes, offset, length);
095    }
096
097    /**
098     * Returns the current size of the file.
099     * @return The size of the file in bytes.
100     */
101    public long getFileSize() {
102        return size;
103    }
104
105    /**
106     * Returns the time the file was created.
107     * @return The time the file was created.
108     */
109    public long getFileTime() {
110        return initialTime;
111    }
112
113    /**
114     * Determine if a rollover should occur.
115     * @param event The LogEvent.
116     */
117    public synchronized void checkRollover(final LogEvent event) {
118        if (triggeringPolicy.isTriggeringEvent(event)) {
119            rollover();
120        }
121    }
122
123    public synchronized void rollover() {
124        if (rollover(rolloverStrategy)) {
125            try {
126                size = 0;
127                initialTime = System.currentTimeMillis();
128                createFileAfterRollover();
129            } catch (final IOException e) {
130                logError("failed to create file after rollover", e);
131            }
132        }
133    }
134
135    protected void createFileAfterRollover() throws IOException  {
136        final OutputStream os = new FileOutputStream(getFileName(), isAppend());
137        if (getBufferSize() > 0) { // negative buffer size means no buffering
138            setOutputStream(new BufferedOutputStream(os, getBufferSize()));
139        } else {
140            setOutputStream(os);
141        }
142    }
143
144    /**
145     * Returns the pattern processor.
146     * @return The PatternProcessor.
147     */
148    public PatternProcessor getPatternProcessor() {
149        return patternProcessor;
150    }
151
152    public void setTriggeringPolicy(final TriggeringPolicy triggeringPolicy)
153    {
154        triggeringPolicy.initialize(this);
155        triggeringPolicyUpdater.compareAndSet(this, this.triggeringPolicy, triggeringPolicy);
156    }
157
158    public void setRolloverStrategy(final RolloverStrategy rolloverStrategy)
159    {
160        rolloverStrategyUpdater.compareAndSet(this, this.rolloverStrategy, rolloverStrategy);
161    }
162
163    /**
164     * Returns the triggering policy.
165     * @param <T> TriggeringPolicy type
166     * @return The TriggeringPolicy
167     */
168    @SuppressWarnings("unchecked")
169    public <T extends TriggeringPolicy> T getTriggeringPolicy() {
170        // TODO We could parameterize this class with a TriggeringPolicy instead of type casting here.
171        return (T) this.triggeringPolicy;
172    }
173
174    /**
175     * Returns the rollover strategy.
176     * @return The RolloverStrategy
177     */
178    public RolloverStrategy getRolloverStrategy() {
179        return this.rolloverStrategy;
180    }
181
182    private boolean rollover(final RolloverStrategy strategy) {
183
184        try {
185            // Block until the asynchronous operation is completed.
186            semaphore.acquire();
187        } catch (final InterruptedException e) {
188            logError("Thread interrupted while attempting to check rollover", e);
189            return false;
190        }
191
192        boolean success = false;
193        Thread thread = null;
194
195        try {
196            final RolloverDescription descriptor = strategy.rollover(this);
197            if (descriptor != null) {
198                writeFooter();
199                close();
200                if (descriptor.getSynchronous() != null) {
201                    LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous());
202                    try {
203                        success = descriptor.getSynchronous().execute();
204                    } catch (final Exception ex) {
205                        logError("caught error in synchronous task", ex);
206                    }
207                }
208
209                if (success && descriptor.getAsynchronous() != null) {
210                    LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
211                    thread = new Log4jThread(new AsyncAction(descriptor.getAsynchronous(), this));
212                    thread.start();
213                }
214                return true;
215            }
216            return false;
217        } finally {
218            if (thread == null || !thread.isAlive()) {
219                semaphore.release();
220            }
221        }
222
223    }
224
225    /**
226     * Performs actions asynchronously.
227     */
228    private static class AsyncAction extends AbstractAction {
229
230        private final Action action;
231        private final RollingFileManager manager;
232
233        /**
234         * Constructor.
235         * @param act The action to perform.
236         * @param manager The manager.
237         */
238        public AsyncAction(final Action act, final RollingFileManager manager) {
239            this.action = act;
240            this.manager = manager;
241        }
242
243        /**
244         * Perform an action.
245         *
246         * @return true if action was successful.  A return value of false will cause
247         *         the rollover to be aborted if possible.
248         * @throws java.io.IOException if IO error, a thrown exception will cause the rollover
249         *                             to be aborted if possible.
250         */
251        @Override
252        public boolean execute() throws IOException {
253            try {
254                return action.execute();
255            } finally {
256                manager.semaphore.release();
257            }
258        }
259
260        /**
261         * Cancels the action if not already initialized or waits till completion.
262         */
263        @Override
264        public void close() {
265            action.close();
266        }
267
268        /**
269         * Determines if action has been completed.
270         *
271         * @return true if action is complete.
272         */
273        @Override
274        public boolean isComplete() {
275            return action.isComplete();
276        }
277    }
278
279    /**
280     * Factory data.
281     */
282    private static class FactoryData {
283        private final String pattern;
284        private final boolean append;
285        private final boolean bufferedIO;
286        private final int bufferSize;
287        private final TriggeringPolicy policy;
288        private final RolloverStrategy strategy;
289        private final String advertiseURI;
290        private final Layout<? extends Serializable> layout;
291
292        /**
293         * Create the data for the factory.
294         * @param pattern The pattern.
295         * @param append The append flag.
296         * @param bufferedIO The bufferedIO flag.
297         * @param advertiseURI
298         * @param layout The Layout.
299         * @param bufferSize the buffer size
300         */
301        public FactoryData(final String pattern, final boolean append, final boolean bufferedIO,
302                final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
303                final Layout<? extends Serializable> layout, final int bufferSize) {
304            this.pattern = pattern;
305            this.append = append;
306            this.bufferedIO = bufferedIO;
307            this.bufferSize = bufferSize;
308            this.policy = policy;
309            this.strategy = strategy;
310            this.advertiseURI = advertiseURI;
311            this.layout = layout;
312        }
313
314        public TriggeringPolicy getTriggeringPolicy()
315        {
316            return this.policy;
317        }
318
319        public RolloverStrategy getRolloverStrategy()
320        {
321            return this.strategy;
322        }
323    }
324
325    @Override
326    public void updateData(final Object data)
327    {
328        final FactoryData factoryData = (FactoryData) data;
329        setRolloverStrategy(factoryData.getRolloverStrategy());
330        setTriggeringPolicy(factoryData.getTriggeringPolicy());
331    }
332
333    /**
334     * Factory to create a RollingFileManager.
335     */
336    private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> {
337
338        /**
339         * Create the RollingFileManager.
340         * @param name The name of the entity to manage.
341         * @param data The data required to create the entity.
342         * @return a RollingFileManager.
343         */
344        @Override
345        public RollingFileManager createManager(final String name, final FactoryData data) {
346            final File file = new File(name);
347            final File parent = file.getParentFile();
348            if (null != parent && !parent.exists()) {
349                parent.mkdirs();
350            }
351            // LOG4J2-1140: check writeHeader before creating the file
352            final boolean writeHeader = !data.append || !file.exists();
353            try {
354                file.createNewFile();
355            } catch (final IOException ioe) {
356                LOGGER.error("Unable to create file " + name, ioe);
357                return null;
358            }
359            final long size = data.append ? file.length() : 0;
360
361            OutputStream os;
362            try {
363                os = new FileOutputStream(name, data.append);
364                int bufferSize = data.bufferSize;
365                if (data.bufferedIO) {
366                    os = new BufferedOutputStream(os, bufferSize);
367                } else {
368                    bufferSize = -1; // negative buffer size signals bufferedIO was configured false
369                }
370                final long time = file.lastModified(); // LOG4J2-531 create file first so time has valid value
371                return new RollingFileManager(name, data.pattern, os, data.append, size, time, data.policy,
372                    data.strategy, data.advertiseURI, data.layout, bufferSize, writeHeader);
373            } catch (final FileNotFoundException ex) {
374                LOGGER.error("FileManager (" + name + ") " + ex);
375            }
376            return null;
377        }
378    }
379
380}