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