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