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