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;
18  
19  import java.io.File;
20  import java.io.FileNotFoundException;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.io.Serializable;
25  import java.nio.ByteBuffer;
26  import java.nio.channels.FileChannel;
27  import java.nio.channels.FileLock;
28  import java.util.Date;
29  import java.util.HashMap;
30  import java.util.Map;
31  
32  import org.apache.logging.log4j.core.Layout;
33  import org.apache.logging.log4j.core.LoggerContext;
34  import org.apache.logging.log4j.core.config.Configuration;
35  import org.apache.logging.log4j.core.util.Constants;
36  import org.apache.logging.log4j.core.util.FileUtils;
37  
38  
39  /**
40   * Manages actual File I/O for File Appenders.
41   */
42  public class FileManager extends OutputStreamManager {
43  
44      private static final FileManagerFactory FACTORY = new FileManagerFactory();
45  
46      private final boolean isAppend;
47      private final boolean createOnDemand;
48      private final boolean isLocking;
49      private final String advertiseURI;
50      private final int bufferSize;
51  
52      /**
53       * @deprecated
54       */
55      @Deprecated
56      protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking,
57              final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize,
58              final boolean writeHeader) {
59          this(fileName, os, append, locking, advertiseURI, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize]));
60      }
61  
62      /**
63       * @deprecated
64       * @since 2.6
65       */
66      @Deprecated
67      protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking,
68              final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader,
69              final ByteBuffer buffer) {
70          super(os, fileName, layout, writeHeader, buffer);
71          this.isAppend = append;
72          this.createOnDemand = false;
73          this.isLocking = locking;
74          this.advertiseURI = advertiseURI;
75          this.bufferSize = buffer.capacity();
76      }
77  
78      /**
79       * @since 2.7
80       */
81      protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking,
82              final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
83              final boolean writeHeader, final ByteBuffer buffer) {
84          super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer);
85          this.isAppend = append;
86          this.createOnDemand = createOnDemand;
87          this.isLocking = locking;
88          this.advertiseURI = advertiseURI;
89          this.bufferSize = buffer.capacity();
90      }
91  
92      /**
93       * Returns the FileManager.
94       * @param fileName The name of the file to manage.
95       * @param append true if the file should be appended to, false if it should be overwritten.
96       * @param locking true if the file should be locked while writing, false otherwise.
97       * @param bufferedIo true if the contents should be buffered as they are written.
98       * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.)
99       * @param advertiseUri the URI to use when advertising the file
100      * @param layout The layout
101      * @param bufferSize buffer size for buffered IO
102      * @param configuration The configuration.
103      * @return A FileManager for the File.
104      */
105     public static FileManager getFileManager(final String fileName, final boolean append, boolean locking,
106             final boolean bufferedIo, final boolean createOnDemand, final String advertiseUri,
107             final Layout<? extends Serializable> layout, final int bufferSize, final Configuration configuration) {
108 
109         if (locking && bufferedIo) {
110             locking = false;
111         }
112         return (FileManager) getManager(fileName, new FactoryData(append, locking, bufferedIo, bufferSize,
113                 createOnDemand, advertiseUri, layout, configuration), FACTORY);
114     }
115 
116     @Override
117     protected OutputStream createOutputStream() throws FileNotFoundException {
118         String filename = getFileName();
119         LOGGER.debug("Now writing to {} at {}", filename, new Date());
120         return new FileOutputStream(filename, isAppend);
121     }
122 
123     @Override
124     protected synchronized void write(final byte[] bytes, final int offset, final int length,
125             final boolean immediateFlush) {
126         if (isLocking) {
127             try {
128                 @SuppressWarnings("resource")
129                 final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel();
130                 /*
131                  * Lock the whole file. This could be optimized to only lock from the current file position. Note that
132                  * locking may be advisory on some systems and mandatory on others, so locking just from the current
133                  * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an
134                  * exception if the region of the file is already locked by another FileChannel in the same JVM.
135                  * Hopefully, that will be avoided since every file should have a single file manager - unless two
136                  * different files strings are configured that somehow map to the same file.
137                  */
138                 try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) {
139                     super.write(bytes, offset, length, immediateFlush);
140                 }
141             } catch (final IOException ex) {
142                 throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex);
143             }
144         } else {
145             super.write(bytes, offset, length, immediateFlush);
146         }
147     }
148 
149     /**
150      * Overrides {@link OutputStreamManager#writeToDestination(byte[], int, int)} to add support for file locking.
151      *
152      * @param bytes the array containing data
153      * @param offset from where to write
154      * @param length how many bytes to write
155      * @since 2.8
156      */
157     protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
158         if (isLocking) {
159             try {
160                 @SuppressWarnings("resource")
161                 final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel();
162                 /*
163                  * Lock the whole file. This could be optimized to only lock from the current file position. Note that
164                  * locking may be advisory on some systems and mandatory on others, so locking just from the current
165                  * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an
166                  * exception if the region of the file is already locked by another FileChannel in the same JVM.
167                  * Hopefully, that will be avoided since every file should have a single file manager - unless two
168                  * different files strings are configured that somehow map to the same file.
169                  */
170                 try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) {
171                     super.writeToDestination(bytes, offset, length);
172                 }
173             } catch (final IOException ex) {
174                 throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex);
175             }
176         } else {
177             super.writeToDestination(bytes, offset, length);
178         }
179     }
180 
181     /**
182      * Returns the name of the File being managed.
183      * @return The name of the File being managed.
184      */
185     public String getFileName() {
186         return getName();
187     }
188 
189     /**
190      * Returns the append status.
191      * @return true if the file will be appended to, false if it is overwritten.
192      */
193     public boolean isAppend() {
194         return isAppend;
195     }
196 
197     /**
198      * Returns the lazy-create.
199      * @return true if the file will be lazy-created.
200      */
201     public boolean isCreateOnDemand() {
202         return createOnDemand;
203     }
204 
205     /**
206      * Returns the lock status.
207      * @return true if the file will be locked when writing, false otherwise.
208      */
209     public boolean isLocking() {
210         return isLocking;
211     }
212 
213     /**
214      * Returns the buffer size to use if the appender was configured with BufferedIO=true, otherwise returns a negative
215      * number.
216      * @return the buffer size, or a negative number if the output stream is not buffered
217      */
218     public int getBufferSize() {
219         return bufferSize;
220     }
221 
222     /**
223      * FileManager's content format is specified by: <code>Key: "fileURI" Value: provided "advertiseURI" param</code>.
224      *
225      * @return Map of content format keys supporting FileManager
226      */
227     @Override
228     public Map<String, String> getContentFormat() {
229         final Map<String, String> result = new HashMap<>(super.getContentFormat());
230         result.put("fileURI", advertiseURI);
231         return result;
232     }
233 
234     /**
235      * Factory Data.
236      */
237     private static class FactoryData extends ConfigurationFactoryData {
238         private final boolean append;
239         private final boolean locking;
240         private final boolean bufferedIo;
241         private final int bufferSize;
242         private final boolean createOnDemand;
243         private final String advertiseURI;
244         private final Layout<? extends Serializable> layout;
245 
246         /**
247          * Constructor.
248          * @param append Append status.
249          * @param locking Locking status.
250          * @param bufferedIo Buffering flag.
251          * @param bufferSize Buffer size.
252          * @param createOnDemand if you want to lazy-create the file (a.k.a. on-demand.)
253          * @param advertiseURI the URI to use when advertising the file
254          * @param layout The layout
255          * @param configuration the configuration
256          */
257         public FactoryData(final boolean append, final boolean locking, final boolean bufferedIo, final int bufferSize,
258                 final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
259                 final Configuration configuration) {
260             super(configuration);
261             this.append = append;
262             this.locking = locking;
263             this.bufferedIo = bufferedIo;
264             this.bufferSize = bufferSize;
265             this.createOnDemand = createOnDemand;
266             this.advertiseURI = advertiseURI;
267             this.layout = layout;
268         }
269     }
270 
271     /**
272      * Factory to create a FileManager.
273      */
274     private static class FileManagerFactory implements ManagerFactory<FileManager, FactoryData> {
275 
276         /**
277          * Creates a FileManager.
278          * @param name The name of the File.
279          * @param data The FactoryData
280          * @return The FileManager for the File.
281          */
282         @Override
283         public FileManager createManager(final String name, final FactoryData data) {
284             final File file = new File(name);
285             try {
286                 FileUtils.makeParentDirs(file);
287                 final boolean writeHeader = !data.append || !file.exists();
288                 final int actualSize = data.bufferedIo ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
289                 final ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[actualSize]);
290                 final FileOutputStream fos = data.createOnDemand ? null : new FileOutputStream(file, data.append);
291                 return new FileManager(data.getLoggerContext(), name, fos, data.append, data.locking,
292                         data.createOnDemand, data.advertiseURI, data.layout, writeHeader, byteBuffer);
293             } catch (final IOException ex) {
294                 LOGGER.error("FileManager (" + name + ") " + ex, ex);
295             }
296             return null;
297         }
298     }
299 
300 }