View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.appender.rolling;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.OutputStream;
22  import java.io.RandomAccessFile;
23  import java.io.Serializable;
24  import java.nio.ByteBuffer;
25  
26  import org.apache.logging.log4j.core.Layout;
27  import org.apache.logging.log4j.core.appender.AppenderLoggingException;
28  import org.apache.logging.log4j.core.appender.ManagerFactory;
29  import org.apache.logging.log4j.core.util.NullOutputStream;
30  
31  /**
32   * Extends RollingFileManager but instead of using a buffered output stream,
33   * this class uses a {@code ByteBuffer} and a {@code RandomAccessFile} to do the
34   * I/O.
35   */
36  public class RollingRandomAccessFileManager extends RollingFileManager {
37      /**
38       * The default buffer size
39       */
40      public static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
41  
42      private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory();
43  
44      private final boolean isImmediateFlush;
45      private RandomAccessFile randomAccessFile;
46      private final ByteBuffer buffer;
47      private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<Boolean>();
48  
49      public RollingRandomAccessFileManager(final RandomAccessFile raf, final String fileName,
50              final String pattern, final OutputStream os, final boolean append,
51              final boolean immediateFlush, final int bufferSize, final long size, final long time,
52              final TriggeringPolicy policy, final RolloverStrategy strategy,
53              final String advertiseURI, final Layout<? extends Serializable> layout) {
54          super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout, bufferSize);
55          this.isImmediateFlush = immediateFlush;
56          this.randomAccessFile = raf;
57          isEndOfBatch.set(Boolean.FALSE);
58          this.buffer = ByteBuffer.allocate(bufferSize);
59          writeHeader();
60      }
61  
62      /**
63       * Writes the layout's header to the file if it exists.
64       */
65      private void writeHeader() {
66          if (layout == null) {
67              return;
68          }
69          final byte[] header = layout.getHeader();
70          if (header == null) {
71              return;
72          }
73          try {
74              // write to the file, not to the buffer: the buffer may not be empty
75              randomAccessFile.write(header, 0, header.length);
76          } catch (final IOException ioe) {
77              LOGGER.error("Unable to write header", ioe);
78          }
79      }
80  
81      public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName,
82              final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize, 
83              final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, 
84              final Layout<? extends Serializable> layout) {
85          return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend, 
86                  immediateFlush, bufferSize, policy, strategy, advertiseURI, layout), FACTORY);
87      }
88  
89      public Boolean isEndOfBatch() {
90          return isEndOfBatch.get();
91      }
92  
93      public void setEndOfBatch(final boolean isEndOfBatch) {
94          this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch));
95      }
96  
97      @Override
98      protected synchronized void write(final byte[] bytes, int offset, int length) {
99          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 }