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 time,
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, time, policy, strategy, advertiseURI, layout,
071                filePermissions, fileOwner, fileGroup,
072                writeHeader, ByteBuffer.wrap(new byte[bufferSize]));
073        this.randomAccessFile = raf;
074        isEndOfBatch.set(Boolean.FALSE);
075        writeHeader();
076    }
077
078    /**
079     * Writes the layout's header to the file if it exists.
080     */
081    private void writeHeader() {
082        if (layout == null) {
083            return;
084        }
085        final byte[] header = layout.getHeader();
086        if (header == null) {
087            return;
088        }
089        try {
090            if (randomAccessFile.length() == 0) {
091                // write to the file, not to the buffer: the buffer may not be empty
092                randomAccessFile.write(header, 0, header.length);
093            }
094        } catch (final IOException e) {
095            logError("Unable to write header", e);
096        }
097    }
098
099    public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName,
100            final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize,
101            final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
102            final Layout<? extends Serializable> layout, final String filePermissions, final String fileOwner, final String fileGroup,
103            final Configuration configuration) {
104        if (strategy instanceof DirectWriteRolloverStrategy && fileName != null) {
105            LOGGER.error("The fileName attribute must not be specified with the DirectWriteRolloverStrategy");
106            return null;
107        }
108        final String name = fileName == null ? filePattern : fileName;
109        return narrow(RollingRandomAccessFileManager.class, getManager(name, new FactoryData(fileName, filePattern, isAppend,
110                immediateFlush, bufferSize, policy, strategy, advertiseURI, layout,
111                filePermissions, fileOwner, fileGroup, configuration), FACTORY));
112    }
113
114    public Boolean isEndOfBatch() {
115        return isEndOfBatch.get();
116    }
117
118    public void setEndOfBatch(final boolean endOfBatch) {
119        this.isEndOfBatch.set(Boolean.valueOf(endOfBatch));
120    }
121
122    // override to make visible for unit tests
123    @Override
124    protected synchronized void write(final byte[] bytes, final int offset, final int length,
125            final boolean immediateFlush) {
126        super.write(bytes, offset, length, immediateFlush);
127    }
128
129    @Override
130    protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
131        try {
132            if (randomAccessFile == null) {
133                String fileName = getFileName();
134                File file = new File(fileName);
135                FileUtils.makeParentDirs(file);
136                createFileAfterRollover(fileName);
137            }
138            randomAccessFile.write(bytes, offset, length);
139            size += length;
140        } catch (final IOException ex) {
141            final String msg = "Error writing to RandomAccessFile " + getName();
142            throw new AppenderLoggingException(msg, ex);
143        }
144    }
145
146    @Override
147    protected void createFileAfterRollover() throws IOException {
148        createFileAfterRollover(getFileName());
149    }
150
151    private void createFileAfterRollover(String fileName) throws IOException {
152        this.randomAccessFile = new RandomAccessFile(fileName, "rw");
153        if (isAppend()) {
154            randomAccessFile.seek(randomAccessFile.length());
155        }
156        writeHeader();
157    }
158
159    @Override
160    public synchronized void flush() {
161        flushBuffer(byteBuffer);
162    }
163
164    @Override
165    public synchronized boolean closeOutputStream() {
166        flush();
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
176    /**
177     * Returns the buffer capacity.
178     *
179     * @return the buffer size
180     */
181    @Override
182    public int getBufferSize() {
183        return byteBuffer.capacity();
184    }
185
186    /**
187     * Factory to create a RollingRandomAccessFileManager.
188     */
189    private static class RollingRandomAccessFileManagerFactory implements
190            ManagerFactory<RollingRandomAccessFileManager, FactoryData> {
191
192        /**
193         * Create the RollingRandomAccessFileManager.
194         *
195         * @param name The name of the entity to manage.
196         * @param data The data required to create the entity.
197         * @return a RollingFileManager.
198         */
199        @Override
200        public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
201            File file = null;
202            long size = 0;
203            long time = System.currentTimeMillis();
204            RandomAccessFile raf = null;
205            if (data.fileName != null) {
206                file = new File(name);
207
208                if (!data.append) {
209                    file.delete();
210                }
211                size = data.append ? file.length() : 0;
212                if (file.exists()) {
213                    time = file.lastModified();
214                }
215                try {
216                    FileUtils.makeParentDirs(file);
217                    raf = new RandomAccessFile(name, "rw");
218                    if (data.append) {
219                        final long length = raf.length();
220                        LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
221                        raf.seek(length);
222                    } else {
223                        LOGGER.trace("RandomAccessFile {} set length to 0", name);
224                        raf.setLength(0);
225                    }
226                } catch (final IOException ex) {
227                    LOGGER.error("Cannot access RandomAccessFile " + ex, ex);
228                    if (raf != null) {
229                        try {
230                            raf.close();
231                        } catch (final IOException e) {
232                            LOGGER.error("Cannot close RandomAccessFile {}", name, e);
233                        }
234                    }
235                    return null;
236                }
237            }
238            final boolean writeHeader = !data.append || file == null || !file.exists();
239
240            final RollingRandomAccessFileManager rrm = new RollingRandomAccessFileManager(data.getLoggerContext(), raf, name, data.pattern,
241                    NullOutputStream.getInstance(), data.append, data.immediateFlush, data.bufferSize, size, time, data.policy,
242                    data.strategy, data.advertiseURI, data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader);
243            if (rrm.isAttributeViewEnabled()) {
244                rrm.defineAttributeView(file.toPath());
245            }
246            return rrm;
247        }
248    }
249
250    /**
251     * Factory data.
252     */
253    private static class FactoryData extends ConfigurationFactoryData {
254        private final String fileName;
255        private final String pattern;
256        private final boolean append;
257        private final boolean immediateFlush;
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        private final String filePermissions;
264        private final String fileOwner;
265        private final String fileGroup;
266
267        /**
268         * Create the data for the factory.
269         *
270         * @param fileName The file name.
271         * @param pattern The pattern.
272         * @param append The append flag.
273         * @param immediateFlush
274         * @param bufferSize
275         * @param policy
276         * @param strategy
277         * @param advertiseURI
278         * @param layout
279         * @param filePermissions File permissions
280         * @param fileOwner File owner
281         * @param fileGroup File group
282         * @param configuration
283         */
284        public FactoryData(final String fileName, final String pattern, final boolean append, final boolean immediateFlush,
285                final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy,
286                final String advertiseURI, final Layout<? extends Serializable> layout,
287                final String filePermissions, final String fileOwner, final String fileGroup,
288                final Configuration configuration) {
289            super(configuration);
290            this.fileName = fileName;
291            this.pattern = pattern;
292            this.append = append;
293            this.immediateFlush = immediateFlush;
294            this.bufferSize = bufferSize;
295            this.policy = policy;
296            this.strategy = strategy;
297            this.advertiseURI = advertiseURI;
298            this.layout = layout;
299            this.filePermissions = filePermissions;
300            this.fileOwner = fileOwner;
301            this.fileGroup = fileGroup;
302        }
303
304        public TriggeringPolicy getTriggeringPolicy()
305        {
306            return this.policy;
307        }
308
309        public RolloverStrategy getRolloverStrategy()
310        {
311            return this.strategy;
312        }
313    }
314
315    @Override
316    public void updateData(final Object data) {
317        final FactoryData factoryData = (FactoryData) data;
318        setRolloverStrategy(factoryData.getRolloverStrategy());
319        setTriggeringPolicy(factoryData.getTriggeringPolicy());
320    }
321}