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