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.FileOutputStream;
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.io.Serializable;
24  import java.nio.ByteBuffer;
25  import java.nio.channels.FileChannel;
26  import java.nio.channels.FileLock;
27  import java.nio.file.FileSystems;
28  import java.nio.file.Path;
29  import java.nio.file.Paths;
30  import java.nio.file.attribute.FileOwnerAttributeView;
31  import java.nio.file.attribute.PosixFileAttributeView;
32  import java.nio.file.attribute.PosixFilePermission;
33  import java.nio.file.attribute.PosixFilePermissions;
34  import java.util.Date;
35  import java.util.HashMap;
36  import java.util.Map;
37  import java.util.Set;
38  
39  import org.apache.logging.log4j.core.Layout;
40  import org.apache.logging.log4j.core.LoggerContext;
41  import org.apache.logging.log4j.core.config.Configuration;
42  import org.apache.logging.log4j.core.util.Constants;
43  import org.apache.logging.log4j.core.util.FileUtils;
44  
45  
46  /**
47   * Manages actual File I/O for File Appenders.
48   */
49  public class FileManager extends OutputStreamManager {
50  
51      private static final FileManagerFactory FACTORY = new FileManagerFactory();
52  
53      private final boolean isAppend;
54      private final boolean createOnDemand;
55      private final boolean isLocking;
56      private final String advertiseURI;
57      private final int bufferSize;
58      private final Set<PosixFilePermission> filePermissions;
59      private final String fileOwner;
60      private final String fileGroup;
61      private final boolean attributeViewEnabled;
62  
63      /**
64       * @deprecated
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 int bufferSize,
69              final boolean writeHeader) {
70          this(fileName, os, append, locking, advertiseURI, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize]));
71      }
72  
73      /**
74       * @deprecated
75       * @since 2.6
76       */
77      @Deprecated
78      protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking,
79              final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader,
80              final ByteBuffer buffer) {
81          super(os, fileName, layout, writeHeader, buffer);
82          this.isAppend = append;
83          this.createOnDemand = false;
84          this.isLocking = locking;
85          this.advertiseURI = advertiseURI;
86          this.bufferSize = buffer.capacity();
87          this.filePermissions = null;
88          this.fileOwner = null;
89          this.fileGroup = null;
90          this.attributeViewEnabled = false;
91      }
92  
93      /**
94       * @deprecated
95       * @since 2.7
96       */
97      @Deprecated
98      protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking,
99              final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
100             final boolean writeHeader, final ByteBuffer buffer) {
101         super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer);
102         this.isAppend = append;
103         this.createOnDemand = createOnDemand;
104         this.isLocking = locking;
105         this.advertiseURI = advertiseURI;
106         this.bufferSize = buffer.capacity();
107         this.filePermissions = null;
108         this.fileOwner = null;
109         this.fileGroup = null;
110         this.attributeViewEnabled = false;
111     }
112 
113     /**
114      * @since 2.9
115      */
116     protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking,
117             final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
118             final String filePermissions, final String fileOwner, final String fileGroup, final boolean writeHeader,
119             final ByteBuffer buffer) {
120         super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer);
121         this.isAppend = append;
122         this.createOnDemand = createOnDemand;
123         this.isLocking = locking;
124         this.advertiseURI = advertiseURI;
125         this.bufferSize = buffer.capacity();
126 
127         final Set<String> views = FileSystems.getDefault().supportedFileAttributeViews();
128         if (views.contains("posix")) {
129             this.filePermissions = filePermissions != null ? PosixFilePermissions.fromString(filePermissions) : null;
130             this.fileGroup = fileGroup;
131         } else {
132             this.filePermissions = null;
133             this.fileGroup = null;
134             if (filePermissions != null) {
135                 LOGGER.warn("Posix file attribute permissions defined but it is not supported by this files system.");
136             }
137             if (fileGroup != null) {
138                 LOGGER.warn("Posix file attribute group defined but it is not supported by this files system.");
139             }
140         }
141 
142         if (views.contains("owner")) {
143             this.fileOwner = fileOwner;
144         } else {
145             this.fileOwner = null;
146             if (fileOwner != null) {
147                 LOGGER.warn("Owner file attribute defined but it is not supported by this files system.");
148             }
149         }
150 
151         // Supported and defined
152         this.attributeViewEnabled = this.filePermissions != null || this.fileOwner != null || this.fileGroup != null;
153     }
154 
155     /**
156      * Returns the FileManager.
157      * @param fileName The name of the file to manage.
158      * @param append true if the file should be appended to, false if it should be overwritten.
159      * @param locking true if the file should be locked while writing, false otherwise.
160      * @param bufferedIo true if the contents should be buffered as they are written.
161      * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.)
162      * @param advertiseUri the URI to use when advertising the file
163      * @param layout The layout
164      * @param bufferSize buffer size for buffered IO
165      * @param filePermissions File permissions
166      * @param fileOwner File owner
167      * @param fileOwner File group
168      * @param configuration The configuration.
169      * @return A FileManager for the File.
170      */
171     public static FileManager getFileManager(final String fileName, final boolean append, boolean locking,
172             final boolean bufferedIo, final boolean createOnDemand, final String advertiseUri,
173             final Layout<? extends Serializable> layout,
174             final int bufferSize, final String filePermissions, final String fileOwner, final String fileGroup,
175             final Configuration configuration) {
176 
177         if (locking && bufferedIo) {
178             locking = false;
179         }
180         return narrow(FileManager.class, getManager(fileName, new FactoryData(append, locking, bufferedIo, bufferSize,
181                 createOnDemand, advertiseUri, layout, filePermissions, fileOwner, fileGroup, configuration), FACTORY));
182     }
183 
184     @Override
185     protected OutputStream createOutputStream() throws IOException {
186         final String filename = getFileName();
187         LOGGER.debug("Now writing to {} at {}", filename, new Date());
188         final FileOutputStream fos = new FileOutputStream(filename, isAppend);
189         defineAttributeView(Paths.get(filename));
190         return fos;
191     }
192 
193     protected void defineAttributeView(final Path path) {
194         if (attributeViewEnabled) {
195             try {
196                 // FileOutputStream may not create new file on all jvm
197                 path.toFile().createNewFile();
198 
199                 FileUtils.defineFilePosixAttributeView(path, filePermissions, fileOwner, fileGroup);
200             } catch (final Exception e) {
201                 LOGGER.error("Could not define attribute view on path \"{}\" got {}", path, e.getMessage(), e);
202             }
203         }
204     }
205 
206     @Override
207     protected synchronized void write(final byte[] bytes, final int offset, final int length,
208             final boolean immediateFlush) {
209         if (isLocking) {
210             try {
211                 @SuppressWarnings("resource")
212                 final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel();
213                 /*
214                  * Lock the whole file. This could be optimized to only lock from the current file position. Note that
215                  * locking may be advisory on some systems and mandatory on others, so locking just from the current
216                  * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an
217                  * exception if the region of the file is already locked by another FileChannel in the same JVM.
218                  * Hopefully, that will be avoided since every file should have a single file manager - unless two
219                  * different files strings are configured that somehow map to the same file.
220                  */
221                 try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) {
222                     super.write(bytes, offset, length, immediateFlush);
223                 }
224             } catch (final IOException ex) {
225                 throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex);
226             }
227         } else {
228             super.write(bytes, offset, length, immediateFlush);
229         }
230     }
231 
232     /**
233      * Overrides {@link OutputStreamManager#writeToDestination(byte[], int, int)} to add support for file locking.
234      *
235      * @param bytes the array containing data
236      * @param offset from where to write
237      * @param length how many bytes to write
238      * @since 2.8
239      */
240     @Override
241     protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
242         if (isLocking) {
243             try {
244                 @SuppressWarnings("resource")
245                 final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel();
246                 /*
247                  * Lock the whole file. This could be optimized to only lock from the current file position. Note that
248                  * locking may be advisory on some systems and mandatory on others, so locking just from the current
249                  * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an
250                  * exception if the region of the file is already locked by another FileChannel in the same JVM.
251                  * Hopefully, that will be avoided since every file should have a single file manager - unless two
252                  * different files strings are configured that somehow map to the same file.
253                  */
254                 try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) {
255                     super.writeToDestination(bytes, offset, length);
256                 }
257             } catch (final IOException ex) {
258                 throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex);
259             }
260         } else {
261             super.writeToDestination(bytes, offset, length);
262         }
263     }
264 
265     /**
266      * Returns the name of the File being managed.
267      * @return The name of the File being managed.
268      */
269     public String getFileName() {
270         return getName();
271     }
272     /**
273      * Returns the append status.
274      * @return true if the file will be appended to, false if it is overwritten.
275      */
276     public boolean isAppend() {
277         return isAppend;
278     }
279 
280     /**
281      * Returns the lazy-create.
282      * @return true if the file will be lazy-created.
283      */
284     public boolean isCreateOnDemand() {
285         return createOnDemand;
286     }
287 
288     /**
289      * Returns the lock status.
290      * @return true if the file will be locked when writing, false otherwise.
291      */
292     public boolean isLocking() {
293         return isLocking;
294     }
295 
296     /**
297      * Returns the buffer size to use if the appender was configured with BufferedIO=true, otherwise returns a negative
298      * number.
299      * @return the buffer size, or a negative number if the output stream is not buffered
300      */
301     public int getBufferSize() {
302         return bufferSize;
303     }
304 
305     /**
306      * Returns posix file permissions if defined and the OS supports posix file attribute,
307      * null otherwise.
308      * @return File posix permissions
309      * @see PosixFileAttributeView
310      */
311     public Set<PosixFilePermission> getFilePermissions() {
312         return filePermissions;
313     }
314 
315     /**
316      * Returns file owner if defined and the OS supports owner file attribute view,
317      * null otherwise.
318      * @return File owner
319      * @see FileOwnerAttributeView
320      */
321     public String getFileOwner() {
322         return fileOwner;
323     }
324 
325     /**
326      * Returns file group if defined and the OS supports posix/group file attribute view,
327      * null otherwise.
328      * @return File group
329      * @see PosixFileAttributeView
330      */
331     public String getFileGroup() {
332         return fileGroup;
333     }
334 
335     /**
336      * Returns true if file attribute view enabled for this file manager.
337      *
338      * @return True if posix or owner supported and defined false otherwise.
339      */
340     public boolean isAttributeViewEnabled() {
341         return attributeViewEnabled;
342     }
343 
344     /**
345      * FileManager's content format is specified by: <code>Key: "fileURI" Value: provided "advertiseURI" param</code>.
346      *
347      * @return Map of content format keys supporting FileManager
348      */
349     @Override
350     public Map<String, String> getContentFormat() {
351         final Map<String, String> result = new HashMap<>(super.getContentFormat());
352         result.put("fileURI", advertiseURI);
353         return result;
354     }
355 
356     /**
357      * Factory Data.
358      */
359     private static class FactoryData extends ConfigurationFactoryData {
360         private final boolean append;
361         private final boolean locking;
362         private final boolean bufferedIo;
363         private final int bufferSize;
364         private final boolean createOnDemand;
365         private final String advertiseURI;
366         private final Layout<? extends Serializable> layout;
367         private final String filePermissions;
368         private final String fileOwner;
369         private final String fileGroup;
370 
371         /**
372          * Constructor.
373          * @param append Append status.
374          * @param locking Locking status.
375          * @param bufferedIo Buffering flag.
376          * @param bufferSize Buffer size.
377          * @param createOnDemand if you want to lazy-create the file (a.k.a. on-demand.)
378          * @param advertiseURI the URI to use when advertising the file
379          * @param layout The layout
380          * @param filePermissions File permissions
381          * @param fileOwner File owner
382          * @param fileGroup File group
383          * @param configuration the configuration
384          */
385         public FactoryData(final boolean append, final boolean locking, final boolean bufferedIo, final int bufferSize,
386                 final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
387                 final String filePermissions, final String fileOwner, final String fileGroup,
388                 final Configuration configuration) {
389             super(configuration);
390             this.append = append;
391             this.locking = locking;
392             this.bufferedIo = bufferedIo;
393             this.bufferSize = bufferSize;
394             this.createOnDemand = createOnDemand;
395             this.advertiseURI = advertiseURI;
396             this.layout = layout;
397             this.filePermissions = filePermissions;
398             this.fileOwner = fileOwner;
399             this.fileGroup = fileGroup;
400         }
401     }
402 
403     /**
404      * Factory to create a FileManager.
405      */
406     private static class FileManagerFactory implements ManagerFactory<FileManager, FactoryData> {
407 
408         /**
409          * Creates a FileManager.
410          * @param name The name of the File.
411          * @param data The FactoryData
412          * @return The FileManager for the File.
413          */
414         @Override
415         public FileManager createManager(final String name, final FactoryData data) {
416             final File file = new File(name);
417             try {
418                 FileUtils.makeParentDirs(file);
419                 final boolean writeHeader = !data.append || !file.exists();
420                 final int actualSize = data.bufferedIo ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
421                 final ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[actualSize]);
422                 final FileOutputStream fos = data.createOnDemand ? null : new FileOutputStream(file, data.append);
423                 final FileManager fm = new FileManager(data.getLoggerContext(), name, fos, data.append, data.locking,
424                         data.createOnDemand, data.advertiseURI, data.layout,
425                         data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, byteBuffer);
426                 if (fos != null && fm.attributeViewEnabled) {
427                     fm.defineAttributeView(file.toPath());
428                 }
429                 return fm;
430             } catch (final IOException ex) {
431                 LOGGER.error("FileManager (" + name + ") " + ex, ex);
432             }
433             return null;
434         }
435     }
436 
437 }