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