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