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