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