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  
30  /**
31   * Extends RollingFileManager but instead of using a buffered output stream,
32   * this class uses a {@code ByteBuffer} and a {@code RandomAccessFile} to do the
33   * 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<Boolean>();
47  
48      public RollingRandomAccessFileManager(final RandomAccessFile raf, final String fileName,
49              final String pattern, final OutputStream os, final boolean append,
50              final boolean immediateFlush, final int bufferSize, final long size, final long time,
51              final TriggeringPolicy policy, final RolloverStrategy strategy,
52              final String advertiseURI, final Layout<? extends Serializable> layout) {
53          super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout, bufferSize);
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 ioe) {
76              LOGGER.error("Unable to write header", ioe);
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 isEndOfBatch) {
93          this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch));
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 ex) {
143             LOGGER.error("Unable to close RandomAccessFile " + getName() + ". "
144                     + ex);
145         }
146     }
147     
148     /**
149      * Returns the buffer capacity.
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 ManagerFactory<RollingRandomAccessFileManager, FactoryData> {
161 
162         /**
163          * Create the RollingRandomAccessFileManager.
164          *
165          * @param name The name of the entity to manage.
166          * @param data The data required to create the entity.
167          * @return a RollingFileManager.
168          */
169         @Override
170         public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
171             final File file = new File(name);
172             final File parent = file.getParentFile();
173             if (null != parent && !parent.exists()) {
174                 parent.mkdirs();
175             }
176 
177             if (!data.append) {
178                 file.delete();
179             }
180             final long size = data.append ? file.length() : 0;
181             final long time = file.exists() ? file.lastModified() : System.currentTimeMillis();
182 
183             RandomAccessFile raf = null;
184             try {
185                 raf = new RandomAccessFile(name, "rw");
186                 if (data.append) {
187                     final long length = raf.length();
188                     LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
189                     raf.seek(length);
190                 } else {
191                     LOGGER.trace("RandomAccessFile {} set length to 0", name);
192                     raf.setLength(0);
193                 }
194                 return new RollingRandomAccessFileManager(raf, name, data.pattern, new DummyOutputStream(), data.append,
195                         data.immediateFlush, data.bufferSize, size, time, data.policy, data.strategy, data.advertiseURI,
196                         data.layout);
197             } catch (final IOException ex) {
198                 LOGGER.error("Cannot access RandomAccessFile {}) " + ex);
199                 if (raf != null) {
200                     try {
201                         raf.close();
202                     } catch (final IOException e) {
203                         LOGGER.error("Cannot close RandomAccessFile {}", name, e);
204                     }
205                 }
206             }
207             return null;
208         }
209     }
210 
211     /** {@code OutputStream} subclass that does not write anything. */
212     static class DummyOutputStream extends OutputStream {
213         @Override
214         public void write(final int b) throws IOException {
215         }
216 
217         @Override
218         public void write(final byte[] b, final int off, final int len) throws IOException {
219         }
220     }
221 
222     /**
223      * Factory data.
224      */
225     private static class FactoryData {
226         private final String pattern;
227         private final boolean append;
228         private final boolean immediateFlush;
229         private final int bufferSize;
230         private final TriggeringPolicy policy;
231         private final RolloverStrategy strategy;
232         private final String advertiseURI;
233         private final Layout<? extends Serializable> layout;
234 
235         /**
236          * Create the data for the factory.
237          *
238          * @param pattern The pattern.
239          * @param append The append flag.
240          * @param immediateFlush
241          * @param bufferSize
242          * @param policy
243          * @param strategy
244          * @param advertiseURI
245          * @param layout
246          */
247         public FactoryData(final String pattern, final boolean append, final boolean immediateFlush,
248                 final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy,
249                 final String advertiseURI, final Layout<? extends Serializable> layout) {
250             this.pattern = pattern;
251             this.append = append;
252             this.immediateFlush = immediateFlush;
253             this.bufferSize = bufferSize;
254             this.policy = policy;
255             this.strategy = strategy;
256             this.advertiseURI = advertiseURI;
257             this.layout = layout;
258         }
259     }
260 
261 }