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, this class uses a {@code ByteBuffer} and a
33   * {@code RandomAccessFile} to do the I/O.
34   */
35  public class RollingRandomAccessFileManager extends RollingFileManager {
36      /**
37       * The default buffer size.
38       */
39      public static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
40  
41      private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory();
42  
43      private final boolean isImmediateFlush;
44      private RandomAccessFile randomAccessFile;
45      private final ByteBuffer buffer;
46      private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>();
47  
48      public RollingRandomAccessFileManager(final RandomAccessFile raf, final String fileName, final String pattern,
49              final OutputStream os, final boolean append, final boolean immediateFlush, final int bufferSize,
50              final long size, final long time, final TriggeringPolicy policy, final RolloverStrategy strategy,
51              final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader) {
52          super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout, bufferSize,
53                  writeHeader);
54          this.isImmediateFlush = immediateFlush;
55          this.randomAccessFile = raf;
56          isEndOfBatch.set(Boolean.FALSE);
57          this.buffer = ByteBuffer.allocate(bufferSize);
58          writeHeader();
59      }
60  
61      /**
62       * Writes the layout's header to the file if it exists.
63       */
64      private void writeHeader() {
65          if (layout == null) {
66              return;
67          }
68          final byte[] header = layout.getHeader();
69          if (header == null) {
70              return;
71          }
72          try {
73              // write to the file, not to the buffer: the buffer may not be empty
74              randomAccessFile.write(header, 0, header.length);
75          } catch (final IOException e) {
76              logError("unable to write header", e);
77          }
78      }
79  
80      public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName,
81              final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize,
82              final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
83              final Layout<? extends Serializable> layout) {
84          return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend,
85                  immediateFlush, bufferSize, policy, strategy, advertiseURI, layout), FACTORY);
86      }
87  
88      public Boolean isEndOfBatch() {
89          return isEndOfBatch.get();
90      }
91  
92      public void setEndOfBatch(final boolean endOfBatch) {
93          this.isEndOfBatch.set(Boolean.valueOf(endOfBatch));
94      }
95  
96      @Override
97      protected synchronized void write(final byte[] bytes, int offset, int length) {
98          super.write(bytes, offset, length); // writes to dummy output stream, needed to track file size
99  
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 e) {
143             logError("unable to close RandomAccessFile", e);
144         }
145     }
146 
147     /**
148      * Returns the buffer capacity.
149      * 
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
161             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             final boolean writeHeader = !data.append || !file.exists();
185             RandomAccessFile raf = null;
186             try {
187                 raf = new RandomAccessFile(name, "rw");
188                 if (data.append) {
189                     final long length = raf.length();
190                     LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
191                     raf.seek(length);
192                 } else {
193                     LOGGER.trace("RandomAccessFile {} set length to 0", name);
194                     raf.setLength(0);
195                 }
196                 return new RollingRandomAccessFileManager(raf, name, data.pattern, NullOutputStream.NULL_OUTPUT_STREAM,
197                         data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, data.strategy,
198                         data.advertiseURI, data.layout, writeHeader);
199             } catch (final IOException ex) {
200                 LOGGER.error("Cannot access RandomAccessFile {}) " + ex);
201                 if (raf != null) {
202                     try {
203                         raf.close();
204                     } catch (final IOException e) {
205                         LOGGER.error("Cannot close RandomAccessFile {}", name, e);
206                     }
207                 }
208             }
209             return null;
210         }
211     }
212 
213     /**
214      * Factory data.
215      */
216     private static class FactoryData {
217         private final String pattern;
218         private final boolean append;
219         private final boolean immediateFlush;
220         private final int bufferSize;
221         private final TriggeringPolicy policy;
222         private final RolloverStrategy strategy;
223         private final String advertiseURI;
224         private final Layout<? extends Serializable> layout;
225 
226         /**
227          * Create the data for the factory.
228          *
229          * @param pattern The pattern.
230          * @param append The append flag.
231          * @param immediateFlush
232          * @param bufferSize
233          * @param policy
234          * @param strategy
235          * @param advertiseURI
236          * @param layout
237          */
238         public FactoryData(final String pattern, final boolean append, final boolean immediateFlush,
239                 final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy,
240                 final String advertiseURI, final Layout<? extends Serializable> layout) {
241             this.pattern = pattern;
242             this.append = append;
243             this.immediateFlush = immediateFlush;
244             this.bufferSize = bufferSize;
245             this.policy = policy;
246             this.strategy = strategy;
247             this.advertiseURI = advertiseURI;
248             this.layout = layout;
249         }
250 
251         public TriggeringPolicy getTriggeringPolicy()
252         {
253             return this.policy;
254         }
255 
256         public RolloverStrategy getRolloverStrategy()
257         {
258             return this.strategy;
259         }
260     }
261 
262     @Override
263     public void updateData(final Object data)
264     {
265         final FactoryData factoryData = (FactoryData) data;
266         setRolloverStrategy(factoryData.getRolloverStrategy());
267         setTriggeringPolicy(factoryData.getTriggeringPolicy());
268     }
269 }