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.LoggerContext;
28  import org.apache.logging.log4j.core.appender.AppenderLoggingException;
29  import org.apache.logging.log4j.core.appender.ConfigurationFactoryData;
30  import org.apache.logging.log4j.core.appender.ManagerFactory;
31  import org.apache.logging.log4j.core.config.Configuration;
32  import org.apache.logging.log4j.core.util.NullOutputStream;
33  
34  /**
35   * Extends RollingFileManager but instead of using a buffered output stream, this class uses a {@code ByteBuffer} and a
36   * {@code RandomAccessFile} to do the I/O.
37   */
38  public class RollingRandomAccessFileManager extends RollingFileManager {
39      /**
40       * The default buffer size.
41       */
42      public static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
43  
44      private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory();
45  
46      private RandomAccessFile randomAccessFile;
47      private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>();
48  
49      public RollingRandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile raf,
50              final String fileName, 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, final String advertiseURI,
53              final Layout<? extends Serializable> layout, final boolean writeHeader) {
54          super(loggerContext, fileName, pattern, os, append, false, size, time, policy, strategy, advertiseURI, layout,
55                  writeHeader, ByteBuffer.wrap(new byte[bufferSize]));
56          this.randomAccessFile = raf;
57          isEndOfBatch.set(Boolean.FALSE);
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              if (randomAccessFile.length() == 0) {
74                  // write to the file, not to the buffer: the buffer may not be empty
75                  randomAccessFile.write(header, 0, header.length);
76              }
77          } catch (final IOException e) {
78              logError("Unable to write header", e);
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, final Configuration configuration) {
86          return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend,
87                  immediateFlush, bufferSize, policy, strategy, advertiseURI, layout, configuration), FACTORY);
88      }
89  
90      public Boolean isEndOfBatch() {
91          return isEndOfBatch.get();
92      }
93  
94      public void setEndOfBatch(final boolean endOfBatch) {
95          this.isEndOfBatch.set(Boolean.valueOf(endOfBatch));
96      }
97  
98      // override to make visible for unit tests
99      @Override
100     protected synchronized void write(final byte[] bytes, final int offset, final int length,
101             final boolean immediateFlush) {
102         super.write(bytes, offset, length, immediateFlush);
103     }
104 
105     @Override
106     protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
107         try {
108             randomAccessFile.write(bytes, offset, length);
109             size += length;
110         } catch (final IOException ex) {
111             final String msg = "Error writing to RandomAccessFile " + getName();
112             throw new AppenderLoggingException(msg, ex);
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         flushBuffer(byteBuffer);
128     }
129 
130     @Override
131     public synchronized boolean closeOutputStream() {
132         flush();
133         try {
134             randomAccessFile.close();
135             return true;
136         } catch (final IOException e) {
137             logError("Unable to close RandomAccessFile", e);
138             return false;
139         }
140     }
141 
142     /**
143      * Returns the buffer capacity.
144      *
145      * @return the buffer size
146      */
147     @Override
148     public int getBufferSize() {
149         return byteBuffer.capacity();
150     }
151 
152     /**
153      * Factory to create a RollingRandomAccessFileManager.
154      */
155     private static class RollingRandomAccessFileManagerFactory implements
156             ManagerFactory<RollingRandomAccessFileManager, FactoryData> {
157 
158         /**
159          * Create the RollingRandomAccessFileManager.
160          *
161          * @param name The name of the entity to manage.
162          * @param data The data required to create the entity.
163          * @return a RollingFileManager.
164          */
165         @Override
166         public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
167             final File file = new File(name);
168             final File parent = file.getParentFile();
169             if (null != parent && !parent.exists()) {
170                 parent.mkdirs();
171             }
172 
173             if (!data.append) {
174                 file.delete();
175             }
176             final long size = data.append ? file.length() : 0;
177             final long time = file.exists() ? file.lastModified() : System.currentTimeMillis();
178 
179             final boolean writeHeader = !data.append || !file.exists();
180             RandomAccessFile raf = null;
181             try {
182                 raf = new RandomAccessFile(name, "rw");
183                 if (data.append) {
184                     final long length = raf.length();
185                     LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
186                     raf.seek(length);
187                 } else {
188                     LOGGER.trace("RandomAccessFile {} set length to 0", name);
189                     raf.setLength(0);
190                 }
191                 return new RollingRandomAccessFileManager(data.getLoggerContext(), raf, name, data.pattern,
192                         NullOutputStream.getInstance(), data.append, data.immediateFlush, data.bufferSize, size, time, data.policy,
193                         data.strategy, data.advertiseURI, data.layout, writeHeader);
194             } catch (final IOException ex) {
195                 LOGGER.error("Cannot access RandomAccessFile " + ex, ex);
196                 if (raf != null) {
197                     try {
198                         raf.close();
199                     } catch (final IOException e) {
200                         LOGGER.error("Cannot close RandomAccessFile {}", name, e);
201                     }
202                 }
203             }
204             return null;
205         }
206     }
207 
208     /**
209      * Factory data.
210      */
211     private static class FactoryData extends ConfigurationFactoryData {
212         private final String pattern;
213         private final boolean append;
214         private final boolean immediateFlush;
215         private final int bufferSize;
216         private final TriggeringPolicy policy;
217         private final RolloverStrategy strategy;
218         private final String advertiseURI;
219         private final Layout<? extends Serializable> layout;
220 
221         /**
222          * Create the data for the factory.
223          *
224          * @param pattern The pattern.
225          * @param append The append flag.
226          * @param immediateFlush
227          * @param bufferSize
228          * @param policy
229          * @param strategy
230          * @param advertiseURI
231          * @param layout
232          * @param configuration
233          */
234         public FactoryData(final String pattern, final boolean append, final boolean immediateFlush,
235                 final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy,
236                 final String advertiseURI, final Layout<? extends Serializable> layout, final Configuration configuration) {
237             super(configuration);
238             this.pattern = pattern;
239             this.append = append;
240             this.immediateFlush = immediateFlush;
241             this.bufferSize = bufferSize;
242             this.policy = policy;
243             this.strategy = strategy;
244             this.advertiseURI = advertiseURI;
245             this.layout = layout;
246         }
247 
248         public TriggeringPolicy getTriggeringPolicy()
249         {
250             return this.policy;
251         }
252 
253         public RolloverStrategy getRolloverStrategy()
254         {
255             return this.strategy;
256         }
257     }
258 
259     @Override
260     public void updateData(final Object data) {
261         final FactoryData factoryData = (FactoryData) data;
262         setRolloverStrategy(factoryData.getRolloverStrategy());
263         setTriggeringPolicy(factoryData.getTriggeringPolicy());
264     }
265 }