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