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.appender.AppenderLoggingException;
028import org.apache.logging.log4j.core.appender.ManagerFactory;
029import org.apache.logging.log4j.core.util.NullOutputStream;
030
031/**
032 * Extends RollingFileManager but instead of using a buffered output stream, this class uses a {@code ByteBuffer} and a
033 * {@code RandomAccessFile} to do the I/O.
034 */
035public class RollingRandomAccessFileManager extends RollingFileManager {
036    /**
037     * The default buffer size.
038     */
039    public static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
040
041    private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory();
042
043    private final boolean isImmediateFlush;
044    private RandomAccessFile randomAccessFile;
045    private final ByteBuffer buffer;
046    private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>();
047
048    public RollingRandomAccessFileManager(final RandomAccessFile raf, final String fileName, final String pattern,
049            final OutputStream os, final boolean append, final boolean immediateFlush, final int bufferSize,
050            final long size, final long time, final TriggeringPolicy policy, final RolloverStrategy strategy,
051            final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader) {
052        super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout, bufferSize,
053                writeHeader);
054        this.isImmediateFlush = immediateFlush;
055        this.randomAccessFile = raf;
056        isEndOfBatch.set(Boolean.FALSE);
057        this.buffer = ByteBuffer.allocate(bufferSize);
058        writeHeader();
059    }
060
061    /**
062     * Writes the layout's header to the file if it exists.
063     */
064    private void writeHeader() {
065        if (layout == null) {
066            return;
067        }
068        final byte[] header = layout.getHeader();
069        if (header == null) {
070            return;
071        }
072        try {
073            // write to the file, not to the buffer: the buffer may not be empty
074            randomAccessFile.write(header, 0, header.length);
075        } catch (final IOException e) {
076            logError("unable to write header", e);
077        }
078    }
079
080    public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName,
081            final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize,
082            final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
083            final Layout<? extends Serializable> layout) {
084        return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend,
085                immediateFlush, bufferSize, policy, strategy, advertiseURI, layout), FACTORY);
086    }
087
088    public Boolean isEndOfBatch() {
089        return isEndOfBatch.get();
090    }
091
092    public void setEndOfBatch(final boolean endOfBatch) {
093        this.isEndOfBatch.set(Boolean.valueOf(endOfBatch));
094    }
095
096    @Override
097    protected synchronized void write(final byte[] bytes, int offset, int length) {
098        super.write(bytes, offset, length); // writes to dummy output stream, needed to track file size
099
100        int chunk = 0;
101        do {
102            if (length > buffer.remaining()) {
103                flush();
104            }
105            chunk = Math.min(length, buffer.remaining());
106            buffer.put(bytes, offset, chunk);
107            offset += chunk;
108            length -= chunk;
109        } while (length > 0);
110
111        if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) {
112            flush();
113        }
114    }
115
116    @Override
117    protected void createFileAfterRollover() throws IOException {
118        this.randomAccessFile = new RandomAccessFile(getFileName(), "rw");
119        if (isAppend()) {
120            randomAccessFile.seek(randomAccessFile.length());
121        }
122        writeHeader();
123    }
124
125    @Override
126    public synchronized void flush() {
127        buffer.flip();
128        try {
129            randomAccessFile.write(buffer.array(), 0, buffer.limit());
130        } catch (final IOException ex) {
131            final String msg = "Error writing to RandomAccessFile " + getName();
132            throw new AppenderLoggingException(msg, ex);
133        }
134        buffer.clear();
135    }
136
137    @Override
138    public synchronized void close() {
139        flush();
140        try {
141            randomAccessFile.close();
142        } catch (final IOException e) {
143            logError("unable to close RandomAccessFile", e);
144        }
145    }
146
147    /**
148     * Returns the buffer capacity.
149     * 
150     * @return the buffer size
151     */
152    @Override
153    public int getBufferSize() {
154        return buffer.capacity();
155    }
156
157    /**
158     * Factory to create a RollingRandomAccessFileManager.
159     */
160    private static class RollingRandomAccessFileManagerFactory implements
161            ManagerFactory<RollingRandomAccessFileManager, FactoryData> {
162
163        /**
164         * Create the RollingRandomAccessFileManager.
165         *
166         * @param name The name of the entity to manage.
167         * @param data The data required to create the entity.
168         * @return a RollingFileManager.
169         */
170        @Override
171        public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
172            final File file = new File(name);
173            final File parent = file.getParentFile();
174            if (null != parent && !parent.exists()) {
175                parent.mkdirs();
176            }
177
178            if (!data.append) {
179                file.delete();
180            }
181            final long size = data.append ? file.length() : 0;
182            final long time = file.exists() ? file.lastModified() : System.currentTimeMillis();
183
184            final boolean writeHeader = !data.append || !file.exists();
185            RandomAccessFile raf = null;
186            try {
187                raf = new RandomAccessFile(name, "rw");
188                if (data.append) {
189                    final long length = raf.length();
190                    LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
191                    raf.seek(length);
192                } else {
193                    LOGGER.trace("RandomAccessFile {} set length to 0", name);
194                    raf.setLength(0);
195                }
196                return new RollingRandomAccessFileManager(raf, name, data.pattern, NullOutputStream.NULL_OUTPUT_STREAM,
197                        data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, data.strategy,
198                        data.advertiseURI, data.layout, writeHeader);
199            } catch (final IOException ex) {
200                LOGGER.error("Cannot access RandomAccessFile {}) " + ex);
201                if (raf != null) {
202                    try {
203                        raf.close();
204                    } catch (final IOException e) {
205                        LOGGER.error("Cannot close RandomAccessFile {}", name, e);
206                    }
207                }
208            }
209            return null;
210        }
211    }
212
213    /**
214     * Factory data.
215     */
216    private static class FactoryData {
217        private final String pattern;
218        private final boolean append;
219        private final boolean immediateFlush;
220        private final int bufferSize;
221        private final TriggeringPolicy policy;
222        private final RolloverStrategy strategy;
223        private final String advertiseURI;
224        private final Layout<? extends Serializable> layout;
225
226        /**
227         * Create the data for the factory.
228         *
229         * @param pattern The pattern.
230         * @param append The append flag.
231         * @param immediateFlush
232         * @param bufferSize
233         * @param policy
234         * @param strategy
235         * @param advertiseURI
236         * @param layout
237         */
238        public FactoryData(final String pattern, final boolean append, final boolean immediateFlush,
239                final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy,
240                final String advertiseURI, final Layout<? extends Serializable> layout) {
241            this.pattern = pattern;
242            this.append = append;
243            this.immediateFlush = immediateFlush;
244            this.bufferSize = bufferSize;
245            this.policy = policy;
246            this.strategy = strategy;
247            this.advertiseURI = advertiseURI;
248            this.layout = layout;
249        }
250
251        public TriggeringPolicy getTriggeringPolicy()
252        {
253            return this.policy;
254        }
255
256        public RolloverStrategy getRolloverStrategy()
257        {
258            return this.strategy;
259        }
260    }
261
262    @Override
263    public void updateData(final Object data)
264    {
265        final FactoryData factoryData = (FactoryData) data;
266        setRolloverStrategy(factoryData.getRolloverStrategy());
267        setTriggeringPolicy(factoryData.getTriggeringPolicy());
268    }
269}