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            final 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        @Override
153        public int getBufferSize() {
154            return buffer.capacity();
155        }
156    
157        /**
158         * Factory to create a RollingRandomAccessFileManager.
159         */
160        private static class RollingRandomAccessFileManagerFactory implements ManagerFactory<RollingRandomAccessFileManager, FactoryData> {
161    
162            /**
163             * Create the RollingRandomAccessFileManager.
164             *
165             * @param name The name of the entity to manage.
166             * @param data The data required to create the entity.
167             * @return a RollingFileManager.
168             */
169            @Override
170            public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
171                final File file = new File(name);
172                final File parent = file.getParentFile();
173                if (null != parent && !parent.exists()) {
174                    parent.mkdirs();
175                }
176    
177                if (!data.append) {
178                    file.delete();
179                }
180                final long size = data.append ? file.length() : 0;
181                final long time = file.exists() ? file.lastModified() : System.currentTimeMillis();
182    
183                RandomAccessFile raf = null;
184                try {
185                    raf = new RandomAccessFile(name, "rw");
186                    if (data.append) {
187                        final long length = raf.length();
188                        LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
189                        raf.seek(length);
190                    } else {
191                        LOGGER.trace("RandomAccessFile {} set length to 0", name);
192                        raf.setLength(0);
193                    }
194                    return new RollingRandomAccessFileManager(raf, name, data.pattern, new DummyOutputStream(), data.append,
195                            data.immediateFlush, data.bufferSize, size, time, data.policy, data.strategy, data.advertiseURI,
196                            data.layout);
197                } catch (final IOException ex) {
198                    LOGGER.error("Cannot access RandomAccessFile {}) " + ex);
199                    if (raf != null) {
200                        try {
201                            raf.close();
202                        } catch (final IOException e) {
203                            LOGGER.error("Cannot close RandomAccessFile {}", name, e);
204                        }
205                    }
206                }
207                return null;
208            }
209        }
210    
211        /** {@code OutputStream} subclass that does not write anything. */
212        static class DummyOutputStream extends OutputStream {
213            @Override
214            public void write(final int b) throws IOException {
215            }
216    
217            @Override
218            public void write(final byte[] b, final int off, final int len) throws IOException {
219            }
220        }
221    
222        /**
223         * Factory data.
224         */
225        private static class FactoryData {
226            private final String pattern;
227            private final boolean append;
228            private final boolean immediateFlush;
229            private final int bufferSize;
230            private final TriggeringPolicy policy;
231            private final RolloverStrategy strategy;
232            private final String advertiseURI;
233            private final Layout<? extends Serializable> layout;
234    
235            /**
236             * Create the data for the factory.
237             *
238             * @param pattern The pattern.
239             * @param append The append flag.
240             * @param immediateFlush
241             * @param bufferSize
242             * @param policy
243             * @param strategy
244             * @param advertiseURI
245             * @param layout
246             */
247            public FactoryData(final String pattern, final boolean append, final boolean immediateFlush,
248                    final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy,
249                    final String advertiseURI, final Layout<? extends Serializable> layout) {
250                this.pattern = pattern;
251                this.append = append;
252                this.immediateFlush = immediateFlush;
253                this.bufferSize = bufferSize;
254                this.policy = policy;
255                this.strategy = strategy;
256                this.advertiseURI = advertiseURI;
257                this.layout = layout;
258            }
259        }
260    
261    }