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.IOException;
021import java.io.OutputStream;
022import java.io.RandomAccessFile;
023import java.io.Serializable;
024import java.nio.ByteBuffer;
025
026import org.apache.logging.log4j.core.Layout;
027import org.apache.logging.log4j.core.LoggerContext;
028import org.apache.logging.log4j.core.appender.AppenderLoggingException;
029import org.apache.logging.log4j.core.appender.ConfigurationFactoryData;
030import org.apache.logging.log4j.core.appender.ManagerFactory;
031import org.apache.logging.log4j.core.config.Configuration;
032import org.apache.logging.log4j.core.util.FileUtils;
033import org.apache.logging.log4j.core.util.NullOutputStream;
034
035/**
036 * Extends RollingFileManager but instead of using a buffered output stream, this class uses a {@code ByteBuffer} and a
037 * {@code RandomAccessFile} to do the I/O.
038 */
039public class RollingRandomAccessFileManager extends RollingFileManager {
040    /**
041     * The default buffer size.
042     */
043    public static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
044
045    private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory();
046
047    private RandomAccessFile randomAccessFile;
048    private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>();
049
050    @Deprecated
051    public RollingRandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile raf,
052            final String fileName, final String pattern, final OutputStream os, final boolean append,
053            final boolean immediateFlush, final int bufferSize, final long size, final long time,
054            final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
055            final Layout<? extends Serializable> layout, final boolean writeHeader) {
056        this(loggerContext, raf, fileName, pattern, os, append, immediateFlush, bufferSize, size, time, policy, strategy, advertiseURI,
057               layout, null, null, null, writeHeader);
058    }
059
060    /**
061     * @since 2.8.3
062     */
063    public RollingRandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile raf,
064            final String fileName, final String pattern, final OutputStream os, final boolean append,
065            final boolean immediateFlush, final int bufferSize, final long size, final long initialTime,
066            final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
067            final Layout<? extends Serializable> layout,
068            final String filePermissions, final String fileOwner, final String fileGroup,
069            final boolean writeHeader) {
070        super(loggerContext, fileName, pattern, os, append, false, size, initialTime, policy, strategy, advertiseURI,
071                layout, filePermissions, fileOwner, fileGroup, writeHeader, ByteBuffer.wrap(new byte[bufferSize]));
072        this.randomAccessFile = raf;
073        isEndOfBatch.set(Boolean.FALSE);
074        writeHeader();
075    }
076
077    /**
078     * Writes the layout's header to the file if it exists.
079     */
080    private void writeHeader() {
081        if (layout == null) {
082            return;
083        }
084        final byte[] header = layout.getHeader();
085        if (header == null) {
086            return;
087        }
088        try {
089            if (randomAccessFile != null && randomAccessFile.length() == 0) {
090                // write to the file, not to the buffer: the buffer may not be empty
091                randomAccessFile.write(header, 0, header.length);
092            }
093        } catch (final IOException e) {
094            logError("Unable to write header", e);
095        }
096    }
097
098    public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName,
099            final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize,
100            final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
101            final Layout<? extends Serializable> layout, final String filePermissions, final String fileOwner, final String fileGroup,
102            final Configuration configuration) {
103        if (strategy instanceof DirectWriteRolloverStrategy && fileName != null) {
104            LOGGER.error("The fileName attribute must not be specified with the DirectWriteRolloverStrategy");
105            return null;
106        }
107        final String name = fileName == null ? filePattern : fileName;
108        return narrow(RollingRandomAccessFileManager.class, getManager(name, new FactoryData(fileName, filePattern, isAppend,
109                immediateFlush, bufferSize, policy, strategy, advertiseURI, layout,
110                filePermissions, fileOwner, fileGroup, configuration), FACTORY));
111    }
112
113    public Boolean isEndOfBatch() {
114        return isEndOfBatch.get();
115    }
116
117    public void setEndOfBatch(final boolean endOfBatch) {
118        this.isEndOfBatch.set(Boolean.valueOf(endOfBatch));
119    }
120
121    // override to make visible for unit tests
122    @Override
123    protected synchronized void write(final byte[] bytes, final int offset, final int length,
124            final boolean immediateFlush) {
125        super.write(bytes, offset, length, immediateFlush);
126    }
127
128    @Override
129    protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
130        try {
131            if (randomAccessFile == null) {
132                final String fileName = getFileName();
133                final File file = new File(fileName);
134                FileUtils.makeParentDirs(file);
135                createFileAfterRollover(fileName);
136            }
137            randomAccessFile.write(bytes, offset, length);
138            size += length;
139        } catch (final IOException ex) {
140            final String msg = "Error writing to RandomAccessFile " + getName();
141            throw new AppenderLoggingException(msg, ex);
142        }
143    }
144
145    @Override
146    protected void createFileAfterRollover() throws IOException {
147        createFileAfterRollover(getFileName());
148    }
149
150    private void createFileAfterRollover(final String fileName) throws IOException {
151        this.randomAccessFile = new RandomAccessFile(fileName, "rw");
152        if (isAppend()) {
153            randomAccessFile.seek(randomAccessFile.length());
154        }
155        writeHeader();
156    }
157
158    @Override
159    public synchronized void flush() {
160        flushBuffer(byteBuffer);
161    }
162
163    @Override
164        public synchronized boolean closeOutputStream() {
165                flush();
166                if (randomAccessFile != null) {
167                        try {
168                                randomAccessFile.close();
169                                return true;
170                        } catch (final IOException e) {
171                                logError("Unable to close RandomAccessFile", e);
172                                return false;
173                        }
174                }
175                return true;
176        }
177
178    /**
179     * Returns the buffer capacity.
180     *
181     * @return the buffer size
182     */
183    @Override
184    public int getBufferSize() {
185        return byteBuffer.capacity();
186    }
187
188    /**
189     * Factory to create a RollingRandomAccessFileManager.
190     */
191    private static class RollingRandomAccessFileManagerFactory implements
192            ManagerFactory<RollingRandomAccessFileManager, FactoryData> {
193
194        /**
195         * Create the RollingRandomAccessFileManager.
196         *
197         * @param name The name of the entity to manage.
198         * @param data The data required to create the entity.
199         * @return a RollingFileManager.
200         */
201        @Override
202        public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
203            File file = null;
204            long size = 0;
205            long time = System.currentTimeMillis();
206            RandomAccessFile raf = null;
207            if (data.fileName != null) {
208                file = new File(name);
209
210                if (!data.append) {
211                    file.delete();
212                }
213                size = data.append ? file.length() : 0;
214                if (file.exists()) {
215                    time = file.lastModified();
216                }
217                try {
218                    FileUtils.makeParentDirs(file);
219                    raf = new RandomAccessFile(name, "rw");
220                    if (data.append) {
221                        final long length = raf.length();
222                        LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
223                        raf.seek(length);
224                    } else {
225                        LOGGER.trace("RandomAccessFile {} set length to 0", name);
226                        raf.setLength(0);
227                    }
228                } catch (final IOException ex) {
229                    LOGGER.error("Cannot access RandomAccessFile " + ex, ex);
230                    if (raf != null) {
231                        try {
232                            raf.close();
233                        } catch (final IOException e) {
234                            LOGGER.error("Cannot close RandomAccessFile {}", name, e);
235                        }
236                    }
237                    return null;
238                }
239            }
240            final boolean writeHeader = !data.append || file == null || !file.exists();
241
242            final RollingRandomAccessFileManager rrm = new RollingRandomAccessFileManager(data.getLoggerContext(), raf, name, data.pattern,
243                    NullOutputStream.getInstance(), data.append, data.immediateFlush, data.bufferSize, size, time, data.policy,
244                    data.strategy, data.advertiseURI, data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader);
245            if (rrm.isAttributeViewEnabled()) {
246                rrm.defineAttributeView(file.toPath());
247            }
248            return rrm;
249        }
250    }
251
252    /**
253     * Factory data.
254     */
255    private static class FactoryData extends ConfigurationFactoryData {
256        private final String fileName;
257        private final String pattern;
258        private final boolean append;
259        private final boolean immediateFlush;
260        private final int bufferSize;
261        private final TriggeringPolicy policy;
262        private final RolloverStrategy strategy;
263        private final String advertiseURI;
264        private final Layout<? extends Serializable> layout;
265        private final String filePermissions;
266        private final String fileOwner;
267        private final String fileGroup;
268
269        /**
270         * Create the data for the factory.
271         *
272         * @param fileName The file name.
273         * @param pattern The pattern.
274         * @param append The append flag.
275         * @param immediateFlush
276         * @param bufferSize
277         * @param policy
278         * @param strategy
279         * @param advertiseURI
280         * @param layout
281         * @param filePermissions File permissions
282         * @param fileOwner File owner
283         * @param fileGroup File group
284         * @param configuration
285         */
286        public FactoryData(final String fileName, final String pattern, final boolean append, final boolean immediateFlush,
287                final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy,
288                final String advertiseURI, final Layout<? extends Serializable> layout,
289                final String filePermissions, final String fileOwner, final String fileGroup,
290                final Configuration configuration) {
291            super(configuration);
292            this.fileName = fileName;
293            this.pattern = pattern;
294            this.append = append;
295            this.immediateFlush = immediateFlush;
296            this.bufferSize = bufferSize;
297            this.policy = policy;
298            this.strategy = strategy;
299            this.advertiseURI = advertiseURI;
300            this.layout = layout;
301            this.filePermissions = filePermissions;
302            this.fileOwner = fileOwner;
303            this.fileGroup = fileGroup;
304        }
305
306        public String getPattern() {
307            return pattern;
308        }
309
310        public TriggeringPolicy getTriggeringPolicy() {
311            return this.policy;
312        }
313
314        public RolloverStrategy getRolloverStrategy() {
315            return this.strategy;
316        }
317
318    }
319
320    @Override
321    public void updateData(final Object data) {
322        final FactoryData factoryData = (FactoryData) data;
323        setRolloverStrategy(factoryData.getRolloverStrategy());
324        setTriggeringPolicy(factoryData.getTriggeringPolicy());
325        setPatternProcessor(new PatternProcessor(factoryData.getPattern(), getPatternProcessor()));
326    }
327}