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             writeHeader(fos);
200         }
201         defineAttributeView(Paths.get(filename));
202         return fos;
203     }
204 
205     protected void defineAttributeView(final Path path) {
206         if (attributeViewEnabled) {
207             try {
208                 // FileOutputStream may not create new file on all jvm
209                 path.toFile().createNewFile();
210 
211                 FileUtils.defineFilePosixAttributeView(path, filePermissions, fileOwner, fileGroup);
212             } catch (final Exception e) {
213                 LOGGER.error("Could not define attribute view on path \"{}\" got {}", path, e.getMessage(), e);
214             }
215         }
216     }
217 
218     @Override
219     protected synchronized void write(final byte[] bytes, final int offset, final int length,
220             final boolean immediateFlush) {
221         if (isLocking) {
222             try {
223                 @SuppressWarnings("resource")
224                 final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel();
225                 /*
226                  * Lock the whole file. This could be optimized to only lock from the current file position. Note that
227                  * locking may be advisory on some systems and mandatory on others, so locking just from the current
228                  * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an
229                  * exception if the region of the file is already locked by another FileChannel in the same JVM.
230                  * Hopefully, that will be avoided since every file should have a single file manager - unless two
231                  * different files strings are configured that somehow map to the same file.
232                  */
233                 try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) {
234                     super.write(bytes, offset, length, immediateFlush);
235                 }
236             } catch (final IOException ex) {
237                 throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex);
238             }
239         } else {
240             super.write(bytes, offset, length, immediateFlush);
241         }
242     }
243 
244     /**
245      * Overrides {@link OutputStreamManager#writeToDestination(byte[], int, int)} to add support for file locking.
246      *
247      * @param bytes the array containing data
248      * @param offset from where to write
249      * @param length how many bytes to write
250      * @since 2.8
251      */
252     @Override
253     protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
254         if (isLocking) {
255             try {
256                 @SuppressWarnings("resource")
257                 final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel();
258                 /*
259                  * Lock the whole file. This could be optimized to only lock from the current file position. Note that
260                  * locking may be advisory on some systems and mandatory on others, so locking just from the current
261                  * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an
262                  * exception if the region of the file is already locked by another FileChannel in the same JVM.
263                  * Hopefully, that will be avoided since every file should have a single file manager - unless two
264                  * different files strings are configured that somehow map to the same file.
265                  */
266                 try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) {
267                     super.writeToDestination(bytes, offset, length);
268                 }
269             } catch (final IOException ex) {
270                 throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex);
271             }
272         } else {
273             super.writeToDestination(bytes, offset, length);
274         }
275     }
276 
277     /**
278      * Returns the name of the File being managed.
279      * @return The name of the File being managed.
280      */
281     public String getFileName() {
282         return getName();
283     }
284     /**
285      * Returns the append status.
286      * @return true if the file will be appended to, false if it is overwritten.
287      */
288     public boolean isAppend() {
289         return isAppend;
290     }
291 
292     /**
293      * Returns the lazy-create.
294      * @return true if the file will be lazy-created.
295      */
296     public boolean isCreateOnDemand() {
297         return createOnDemand;
298     }
299 
300     /**
301      * Returns the lock status.
302      * @return true if the file will be locked when writing, false otherwise.
303      */
304     public boolean isLocking() {
305         return isLocking;
306     }
307 
308     /**
309      * Returns the buffer size to use if the appender was configured with BufferedIO=true, otherwise returns a negative
310      * number.
311      * @return the buffer size, or a negative number if the output stream is not buffered
312      */
313     public int getBufferSize() {
314         return bufferSize;
315     }
316 
317     /**
318      * Returns posix file permissions if defined and the OS supports posix file attribute,
319      * null otherwise.
320      * @return File posix permissions
321      * @see PosixFileAttributeView
322      */
323     public Set<PosixFilePermission> getFilePermissions() {
324         return filePermissions;
325     }
326 
327     /**
328      * Returns file owner if defined and the OS supports owner file attribute view,
329      * null otherwise.
330      * @return File owner
331      * @see FileOwnerAttributeView
332      */
333     public String getFileOwner() {
334         return fileOwner;
335     }
336 
337     /**
338      * Returns file group if defined and the OS supports posix/group file attribute view,
339      * null otherwise.
340      * @return File group
341      * @see PosixFileAttributeView
342      */
343     public String getFileGroup() {
344         return fileGroup;
345     }
346 
347     /**
348      * Returns true if file attribute view enabled for this file manager.
349      *
350      * @return True if posix or owner supported and defined false otherwise.
351      */
352     public boolean isAttributeViewEnabled() {
353         return attributeViewEnabled;
354     }
355 
356     /**
357      * FileManager's content format is specified by: <code>Key: "fileURI" Value: provided "advertiseURI" param</code>.
358      *
359      * @return Map of content format keys supporting FileManager
360      */
361     @Override
362     public Map<String, String> getContentFormat() {
363         final Map<String, String> result = new HashMap<>(super.getContentFormat());
364         result.put("fileURI", advertiseURI);
365         return result;
366     }
367 
368     /**
369      * Factory Data.
370      */
371     private static class FactoryData extends ConfigurationFactoryData {
372         private final boolean append;
373         private final boolean locking;
374         private final boolean bufferedIo;
375         private final int bufferSize;
376         private final boolean createOnDemand;
377         private final String advertiseURI;
378         private final Layout<? extends Serializable> layout;
379         private final String filePermissions;
380         private final String fileOwner;
381         private final String fileGroup;
382 
383         /**
384          * Constructor.
385          * @param append Append status.
386          * @param locking Locking status.
387          * @param bufferedIo Buffering flag.
388          * @param bufferSize Buffer size.
389          * @param createOnDemand if you want to lazy-create the file (a.k.a. on-demand.)
390          * @param advertiseURI the URI to use when advertising the file
391          * @param layout The layout
392          * @param filePermissions File permissions
393          * @param fileOwner File owner
394          * @param fileGroup File group
395          * @param configuration the configuration
396          */
397         public FactoryData(final boolean append, final boolean locking, final boolean bufferedIo, final int bufferSize,
398                 final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
399                 final String filePermissions, final String fileOwner, final String fileGroup,
400                 final Configuration configuration) {
401             super(configuration);
402             this.append = append;
403             this.locking = locking;
404             this.bufferedIo = bufferedIo;
405             this.bufferSize = bufferSize;
406             this.createOnDemand = createOnDemand;
407             this.advertiseURI = advertiseURI;
408             this.layout = layout;
409             this.filePermissions = filePermissions;
410             this.fileOwner = fileOwner;
411             this.fileGroup = fileGroup;
412         }
413     }
414 
415     /**
416      * Factory to create a FileManager.
417      */
418     private static class FileManagerFactory implements ManagerFactory<FileManager, FactoryData> {
419 
420         /**
421          * Creates a FileManager.
422          * @param name The name of the File.
423          * @param data The FactoryData
424          * @return The FileManager for the File.
425          */
426         @Override
427         public FileManager createManager(final String name, final FactoryData data) {
428             final File file = new File(name);
429             try {
430                 FileUtils.makeParentDirs(file);
431                 final boolean writeHeader = !data.append || !file.exists();
432                 final int actualSize = data.bufferedIo ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
433                 final ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[actualSize]);
434                 final FileOutputStream fos = data.createOnDemand ? null : new FileOutputStream(file, data.append);
435                 final FileManager fm = new FileManager(data.getLoggerContext(), name, fos, data.append, data.locking,
436                         data.createOnDemand, data.advertiseURI, data.layout,
437                         data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, byteBuffer);
438                 if (fos != null && fm.attributeViewEnabled) {
439                     fm.defineAttributeView(file.toPath());
440                 }
441                 return fm;
442             } catch (final IOException ex) {
443                 LOGGER.error("FileManager (" + name + ") " + ex, ex);
444             }
445             return null;
446         }
447     }
448 
449 }