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     */
017    package org.apache.logging.log4j.core.appender.rolling;
018    
019    import java.io.File;
020    import java.io.FileNotFoundException;
021    import java.io.IOException;
022    import java.io.OutputStream;
023    import java.io.RandomAccessFile;
024    import java.nio.ByteBuffer;
025    
026    import org.apache.logging.log4j.core.Layout;
027    import org.apache.logging.log4j.core.appender.AppenderRuntimeException;
028    import 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     */
035    public class FastRollingFileManager extends RollingFileManager {
036        private static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
037    
038        private static final FastRollingFileManagerFactory FACTORY = new FastRollingFileManagerFactory();
039    
040        private final boolean isImmediateFlush;
041        private RandomAccessFile randomAccessFile;
042        private final ByteBuffer buffer;
043        private ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<Boolean>();
044    
045        public FastRollingFileManager(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 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 FastRollingFileManager getFastRollingFileManager(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 layout) {
062            return (FastRollingFileManager) 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(boolean isEndOfBatch) {
071            this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch));
072        }
073    
074        @Override
075        protected synchronized void write(byte[] bytes, int offset, int length) {
076            super.write(bytes, offset, length); // writes to dummy output stream
077    
078            if (length > buffer.remaining()) {
079                flush();
080            }
081            buffer.put(bytes, offset, length);
082            if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) {
083                flush();
084            }
085        }
086    
087        @Override
088        protected void createFileAfterRollover() throws IOException {
089            this.randomAccessFile = new RandomAccessFile(getFileName(), "rw");
090            if (isAppend()) {
091                randomAccessFile.seek(randomAccessFile.length());
092            }
093        }
094    
095        @Override
096        public void flush() {
097            buffer.flip();
098            try {
099                randomAccessFile.write(buffer.array(), 0, buffer.limit());
100            } catch (IOException ex) {
101                String msg = "Error writing to RandomAccessFile " + getName();
102                throw new AppenderRuntimeException(msg, ex);
103            }
104            buffer.clear();
105        }
106    
107        @Override
108        public void close() {
109            flush();
110            try {
111                randomAccessFile.close();
112            } catch (final IOException ex) {
113                LOGGER.error("Unable to close RandomAccessFile " + getName() + ". "
114                        + ex);
115            }
116        }
117    
118        /**
119         * Factory to create a FastRollingFileManager.
120         */
121        private static class FastRollingFileManagerFactory implements ManagerFactory<FastRollingFileManager, FactoryData> {
122    
123            /**
124             * Create the FastRollingFileManager.
125             *
126             * @param name The name of the entity to manage.
127             * @param data The data required to create the entity.
128             * @return a RollingFileManager.
129             */
130            @Override
131            public FastRollingFileManager createManager(String name, FactoryData data) {
132                File file = new File(name);
133                final File parent = file.getParentFile();
134                if (null != parent && !parent.exists()) {
135                    parent.mkdirs();
136                }
137                if (!data.append) {
138                    file.delete();
139                }
140                long size = data.append ? file.length() : 0;
141                long time = file.lastModified();
142    
143                RandomAccessFile raf;
144                try {
145                    raf = new RandomAccessFile(name, "rw");
146                    return new FastRollingFileManager(raf, name, data.pattern, new DummyOutputStream(), data.append,
147                            data.immediateFlush, size, time, data.policy, data.strategy, data.advertiseURI, data.layout);
148                } catch (FileNotFoundException ex) {
149                    LOGGER.error("FastRollingFileManager (" + name + ") " + ex);
150                }
151                return null;
152            }
153        }
154    
155        /** {@code OutputStream} subclass that does not write anything. */
156        private static class DummyOutputStream extends OutputStream {
157            @Override
158            public void write(int b) throws IOException {
159            }
160    
161            @Override
162            public void write(byte[] b, int off, int len) throws IOException {
163            }
164        }
165    
166        /**
167         * Factory data.
168         */
169        private static class FactoryData {
170            private final String pattern;
171            private final boolean append;
172            private final boolean immediateFlush;
173            private final TriggeringPolicy policy;
174            private final RolloverStrategy strategy;
175            private final String advertiseURI;
176            private final Layout layout;
177    
178            /**
179             * Create the data for the factory.
180             *
181             * @param pattern The pattern.
182             * @param append The append flag.
183             * @param immediateFlush
184             */
185            public FactoryData(final String pattern, final boolean append, final boolean immediateFlush,
186                               final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
187                               final Layout layout) {
188                this.pattern = pattern;
189                this.append = append;
190                this.immediateFlush = immediateFlush;
191                this.policy = policy;
192                this.strategy = strategy;
193                this.advertiseURI = advertiseURI;
194                this.layout = layout;
195            }
196        }
197    
198    }