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<>();
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, final boolean writeHeader) {
54          super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout, bufferSize,
55                  writeHeader);
56          this.isImmediateFlush = immediateFlush;
57          this.randomAccessFile = raf;
58          isEndOfBatch.set(Boolean.FALSE);
59          this.buffer = ByteBuffer.allocate(bufferSize);
60          writeHeader();
61      }
62  
63      /**
64       * Writes the layout's header to the file if it exists.
65       */
66      private void writeHeader() {
67          if (layout == null) {
68              return;
69          }
70          final byte[] header = layout.getHeader();
71          if (header == null) {
72              return;
73          }
74          try {
75              // write to the file, not to the buffer: the buffer may not be empty
76              randomAccessFile.write(header, 0, header.length);
77          } catch (final IOException ioe) {
78              LOGGER.error("Unable to write header", ioe);
79          }
80      }
81  
82      public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName,
83              final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize, 
84              final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, 
85              final Layout<? extends Serializable> layout) {
86          return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend, 
87                  immediateFlush, bufferSize, policy, strategy, advertiseURI, layout), FACTORY);
88      }
89  
90      public Boolean isEndOfBatch() {
91          return isEndOfBatch.get();
92      }
93  
94      public void setEndOfBatch(final boolean isEndOfBatch) {
95          this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch));
96      }
97  
98      @Override
99      protected synchronized void write(final byte[] bytes, int offset, int length) {
100         super.write(bytes, offset, length); // writes to dummy output stream, needed to track file size
101 
102         int chunk = 0;
103         do {
104             if (length > buffer.remaining()) {
105                 flush();
106             }
107             chunk = Math.min(length, buffer.remaining());
108             buffer.put(bytes, offset, chunk);
109             offset += chunk;
110             length -= chunk;
111         } while (length > 0);
112 
113         if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) {
114             flush();
115         }
116     }
117 
118     @Override
119     protected void createFileAfterRollover() throws IOException {
120         this.randomAccessFile = new RandomAccessFile(getFileName(), "rw");
121         if (isAppend()) {
122             randomAccessFile.seek(randomAccessFile.length());
123         }
124         writeHeader();
125     }
126 
127     @Override
128     public synchronized void flush() {
129         buffer.flip();
130         try {
131             randomAccessFile.write(buffer.array(), 0, buffer.limit());
132         } catch (final IOException ex) {
133             final String msg = "Error writing to RandomAccessFile " + getName();
134             throw new AppenderLoggingException(msg, ex);
135         }
136         buffer.clear();
137     }
138 
139     @Override
140     public synchronized void close() {
141         flush();
142         try {
143             randomAccessFile.close();
144         } catch (final IOException ex) {
145             LOGGER.error("Unable to close RandomAccessFile " + getName() + ". "
146                     + ex);
147         }
148     }
149     
150     /**
151      * Returns the buffer capacity.
152      * @return the buffer size
153      */
154     @Override
155     public int getBufferSize() {
156         return buffer.capacity();
157     }
158 
159     /**
160      * Factory to create a RollingRandomAccessFileManager.
161      */
162     private static class RollingRandomAccessFileManagerFactory implements ManagerFactory<RollingRandomAccessFileManager, FactoryData> {
163 
164         /**
165          * Create the RollingRandomAccessFileManager.
166          *
167          * @param name The name of the entity to manage.
168          * @param data The data required to create the entity.
169          * @return a RollingFileManager.
170          */
171         @Override
172         public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
173             final File file = new File(name);
174             final File parent = file.getParentFile();
175             if (null != parent && !parent.exists()) {
176                 parent.mkdirs();
177             }
178 
179             if (!data.append) {
180                 file.delete();
181             }
182             final long size = data.append ? file.length() : 0;
183             final long time = file.exists() ? file.lastModified() : System.currentTimeMillis();
184 
185             final boolean writeHeader = !data.append || !file.exists();
186             RandomAccessFile raf = null;
187             try {
188                 raf = new RandomAccessFile(name, "rw");
189                 if (data.append) {
190                     final long length = raf.length();
191                     LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
192                     raf.seek(length);
193                 } else {
194                     LOGGER.trace("RandomAccessFile {} set length to 0", name);
195                     raf.setLength(0);
196                 }
197                 return new RollingRandomAccessFileManager(raf, name, data.pattern, NullOutputStream.NULL_OUTPUT_STREAM,
198                         data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, data.strategy,
199                         data.advertiseURI, data.layout, writeHeader);
200             } catch (final IOException ex) {
201                 LOGGER.error("Cannot access RandomAccessFile {}) " + ex);
202                 if (raf != null) {
203                     try {
204                         raf.close();
205                     } catch (final IOException e) {
206                         LOGGER.error("Cannot close RandomAccessFile {}", name, e);
207                     }
208                 }
209             }
210             return null;
211         }
212     }
213 
214     /**
215      * Factory data.
216      */
217     private static class FactoryData {
218         private final String pattern;
219         private final boolean append;
220         private final boolean immediateFlush;
221         private final int bufferSize;
222         private final TriggeringPolicy policy;
223         private final RolloverStrategy strategy;
224         private final String advertiseURI;
225         private final Layout<? extends Serializable> layout;
226 
227         /**
228          * Create the data for the factory.
229          *
230          * @param pattern The pattern.
231          * @param append The append flag.
232          * @param immediateFlush
233          * @param bufferSize
234          * @param policy
235          * @param strategy
236          * @param advertiseURI
237          * @param layout
238          */
239         public FactoryData(final String pattern, final boolean append, final boolean immediateFlush,
240                 final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy,
241                 final String advertiseURI, final Layout<? extends Serializable> layout) {
242             this.pattern = pattern;
243             this.append = append;
244             this.immediateFlush = immediateFlush;
245             this.bufferSize = bufferSize;
246             this.policy = policy;
247             this.strategy = strategy;
248             this.advertiseURI = advertiseURI;
249             this.layout = layout;
250         }
251     }
252 
253 }