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      @Deprecated
51      public RollingRandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile raf,
52              final String fileName, final String pattern, final OutputStream os, final boolean append,
53              final boolean immediateFlush, final int bufferSize, final long size, final long time,
54              final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
55              final Layout<? extends Serializable> layout, final boolean writeHeader) {
56          this(loggerContext, raf, fileName, pattern, os, append, immediateFlush, bufferSize, size, time, policy, strategy, advertiseURI,
57                 layout, null, null, null, writeHeader);
58      }
59  
60      /**
61       * @since 2.8.3
62       */
63      public RollingRandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile raf,
64              final String fileName, final String pattern, final OutputStream os, final boolean append,
65              final boolean immediateFlush, final int bufferSize, final long size, final long time,
66              final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
67              final Layout<? extends Serializable> layout,
68              final String filePermissions, final String fileOwner, final String fileGroup,
69              final boolean writeHeader) {
70          super(loggerContext, fileName, pattern, os, append, false, size, time, policy, strategy, advertiseURI, layout,
71                  filePermissions, fileOwner, fileGroup,
72                  writeHeader, ByteBuffer.wrap(new byte[bufferSize]));
73          this.randomAccessFile = raf;
74          isEndOfBatch.set(Boolean.FALSE);
75          writeHeader();
76      }
77  
78      /**
79       * Writes the layout's header to the file if it exists.
80       */
81      private void writeHeader() {
82          if (layout == null) {
83              return;
84          }
85          final byte[] header = layout.getHeader();
86          if (header == null) {
87              return;
88          }
89          try {
90              if (randomAccessFile.length() == 0) {
91                  // write to the file, not to the buffer: the buffer may not be empty
92                  randomAccessFile.write(header, 0, header.length);
93              }
94          } catch (final IOException e) {
95              logError("Unable to write header", e);
96          }
97      }
98  
99      public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName,
100             final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize,
101             final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
102             final Layout<? extends Serializable> layout, final String filePermissions, final String fileOwner, final String fileGroup,
103             final Configuration configuration) {
104         if (strategy instanceof DirectWriteRolloverStrategy && fileName != null) {
105             LOGGER.error("The fileName attribute must not be specified with the DirectWriteRolloverStrategy");
106             return null;
107         }
108         final String name = fileName == null ? filePattern : fileName;
109         return narrow(RollingRandomAccessFileManager.class, getManager(name, new FactoryData(fileName, filePattern, isAppend,
110                 immediateFlush, bufferSize, policy, strategy, advertiseURI, layout,
111                 filePermissions, fileOwner, fileGroup, configuration), FACTORY));
112     }
113 
114     public Boolean isEndOfBatch() {
115         return isEndOfBatch.get();
116     }
117 
118     public void setEndOfBatch(final boolean endOfBatch) {
119         this.isEndOfBatch.set(Boolean.valueOf(endOfBatch));
120     }
121 
122     // override to make visible for unit tests
123     @Override
124     protected synchronized void write(final byte[] bytes, final int offset, final int length,
125             final boolean immediateFlush) {
126         super.write(bytes, offset, length, immediateFlush);
127     }
128 
129     @Override
130     protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
131         try {
132             if (randomAccessFile == null) {
133                 String fileName = getFileName();
134                 File file = new File(fileName);
135                 FileUtils.makeParentDirs(file);
136                 createFileAfterRollover(fileName);
137             }
138             randomAccessFile.write(bytes, offset, length);
139             size += length;
140         } catch (final IOException ex) {
141             final String msg = "Error writing to RandomAccessFile " + getName();
142             throw new AppenderLoggingException(msg, ex);
143         }
144     }
145 
146     @Override
147     protected void createFileAfterRollover() throws IOException {
148         createFileAfterRollover(getFileName());
149     }
150 
151     private void createFileAfterRollover(String fileName) throws IOException {
152         this.randomAccessFile = new RandomAccessFile(fileName, "rw");
153         if (isAppend()) {
154             randomAccessFile.seek(randomAccessFile.length());
155         }
156         writeHeader();
157     }
158 
159     @Override
160     public synchronized void flush() {
161         flushBuffer(byteBuffer);
162     }
163 
164     @Override
165     public synchronized boolean closeOutputStream() {
166         flush();
167         try {
168             randomAccessFile.close();
169             return true;
170         } catch (final IOException e) {
171             logError("Unable to close RandomAccessFile", e);
172             return false;
173         }
174     }
175 
176     /**
177      * Returns the buffer capacity.
178      *
179      * @return the buffer size
180      */
181     @Override
182     public int getBufferSize() {
183         return byteBuffer.capacity();
184     }
185 
186     /**
187      * Factory to create a RollingRandomAccessFileManager.
188      */
189     private static class RollingRandomAccessFileManagerFactory implements
190             ManagerFactory<RollingRandomAccessFileManager, FactoryData> {
191 
192         /**
193          * Create the RollingRandomAccessFileManager.
194          *
195          * @param name The name of the entity to manage.
196          * @param data The data required to create the entity.
197          * @return a RollingFileManager.
198          */
199         @Override
200         public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
201             File file = null;
202             long size = 0;
203             long time = System.currentTimeMillis();
204             RandomAccessFile raf = null;
205             if (data.fileName != null) {
206                 file = new File(name);
207 
208                 if (!data.append) {
209                     file.delete();
210                 }
211                 size = data.append ? file.length() : 0;
212                 if (file.exists()) {
213                     time = file.lastModified();
214                 }
215                 try {
216                     FileUtils.makeParentDirs(file);
217                     raf = new RandomAccessFile(name, "rw");
218                     if (data.append) {
219                         final long length = raf.length();
220                         LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
221                         raf.seek(length);
222                     } else {
223                         LOGGER.trace("RandomAccessFile {} set length to 0", name);
224                         raf.setLength(0);
225                     }
226                 } catch (final IOException ex) {
227                     LOGGER.error("Cannot access RandomAccessFile " + ex, ex);
228                     if (raf != null) {
229                         try {
230                             raf.close();
231                         } catch (final IOException e) {
232                             LOGGER.error("Cannot close RandomAccessFile {}", name, e);
233                         }
234                     }
235                     return null;
236                 }
237             }
238             final boolean writeHeader = !data.append || file == null || !file.exists();
239 
240             final RollingRandomAccessFileManager rrm = new RollingRandomAccessFileManager(data.getLoggerContext(), raf, name, data.pattern,
241                     NullOutputStream.getInstance(), data.append, data.immediateFlush, data.bufferSize, size, time, data.policy,
242                     data.strategy, data.advertiseURI, data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader);
243             if (rrm.isAttributeViewEnabled()) {
244                 rrm.defineAttributeView(file.toPath());
245             }
246             return rrm;
247         }
248     }
249 
250     /**
251      * Factory data.
252      */
253     private static class FactoryData extends ConfigurationFactoryData {
254         private final String fileName;
255         private final String pattern;
256         private final boolean append;
257         private final boolean immediateFlush;
258         private final int bufferSize;
259         private final TriggeringPolicy policy;
260         private final RolloverStrategy strategy;
261         private final String advertiseURI;
262         private final Layout<? extends Serializable> layout;
263         private final String filePermissions;
264         private final String fileOwner;
265         private final String fileGroup;
266 
267         /**
268          * Create the data for the factory.
269          *
270          * @param fileName The file name.
271          * @param pattern The pattern.
272          * @param append The append flag.
273          * @param immediateFlush
274          * @param bufferSize
275          * @param policy
276          * @param strategy
277          * @param advertiseURI
278          * @param layout
279          * @param filePermissions File permissions
280          * @param fileOwner File owner
281          * @param fileGroup File group
282          * @param configuration
283          */
284         public FactoryData(final String fileName, final String pattern, final boolean append, final boolean immediateFlush,
285                 final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy,
286                 final String advertiseURI, final Layout<? extends Serializable> layout,
287                 final String filePermissions, final String fileOwner, final String fileGroup,
288                 final Configuration configuration) {
289             super(configuration);
290             this.fileName = fileName;
291             this.pattern = pattern;
292             this.append = append;
293             this.immediateFlush = immediateFlush;
294             this.bufferSize = bufferSize;
295             this.policy = policy;
296             this.strategy = strategy;
297             this.advertiseURI = advertiseURI;
298             this.layout = layout;
299             this.filePermissions = filePermissions;
300             this.fileOwner = fileOwner;
301             this.fileGroup = fileGroup;
302         }
303 
304         public TriggeringPolicy getTriggeringPolicy()
305         {
306             return this.policy;
307         }
308 
309         public RolloverStrategy getRolloverStrategy()
310         {
311             return this.strategy;
312         }
313     }
314 
315     @Override
316     public void updateData(final Object data) {
317         final FactoryData factoryData = (FactoryData) data;
318         setRolloverStrategy(factoryData.getRolloverStrategy());
319         setTriggeringPolicy(factoryData.getTriggeringPolicy());
320     }
321 }