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