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