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