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