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.IOException;
021    import java.io.OutputStream;
022    import java.io.RandomAccessFile;
023    import java.io.Serializable;
024    import java.nio.ByteBuffer;
025    
026    import org.apache.logging.log4j.core.Layout;
027    import org.apache.logging.log4j.core.appender.AppenderLoggingException;
028    import org.apache.logging.log4j.core.appender.ManagerFactory;
029    import 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     */
036    public 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    }