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
18 package org.apache.log4j;
19
20 import java.io.BufferedWriter;
21 import java.io.File;
22 import java.io.FileNotFoundException;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.InterruptedIOException;
26 import java.io.Writer;
27
28 import org.apache.log4j.helpers.LogLog;
29 import org.apache.log4j.helpers.QuietWriter;
30 import org.apache.log4j.spi.ErrorCode;
31
32 // Contibutors: Jens Uwe Pipka <jens.pipka@gmx.de>
33 // Ben Sandee
34
35 /**
36 * FileAppender appends log events to a file.
37 *
38 * <p>Support for <code>java.io.Writer</code> and console appending
39 * has been deprecated and then removed. See the replacement
40 * solutions: {@link WriterAppender} and {@link ConsoleAppender}.
41 *
42 * @author Ceki Gülcü
43 * */
44 public class FileAppender extends WriterAppender {
45
46 /** Controls file truncatation. The default value for this variable
47 * is <code>true</code>, meaning that by default a
48 * <code>FileAppender</code> will append to an existing file and not
49 * truncate it.
50 *
51 * <p>This option is meaningful only if the FileAppender opens the
52 * file.
53 */
54 protected boolean fileAppend = true;
55
56 /**
57 The name of the log file. */
58 protected String fileName = null;
59
60 /**
61 Do we do bufferedIO? */
62 protected boolean bufferedIO = false;
63
64 /**
65 * Determines the size of IO buffer be. Default is 8K.
66 */
67 protected int bufferSize = 8*1024;
68
69
70 /**
71 The default constructor does not do anything.
72 */
73 public
74 FileAppender() {
75 }
76
77 /**
78 Instantiate a <code>FileAppender</code> and open the file
79 designated by <code>filename</code>. The opened filename will
80 become the output destination for this appender.
81
82 <p>If the <code>append</code> parameter is true, the file will be
83 appended to. Otherwise, the file designated by
84 <code>filename</code> will be truncated before being opened.
85
86 <p>If the <code>bufferedIO</code> parameter is <code>true</code>,
87 then buffered IO will be used to write to the output file.
88
89 */
90 public
91 FileAppender(Layout layout, String filename, boolean append, boolean bufferedIO,
92 int bufferSize) throws IOException {
93 this.layout = layout;
94 this.setFile(filename, append, bufferedIO, bufferSize);
95 }
96
97 /**
98 Instantiate a FileAppender and open the file designated by
99 <code>filename</code>. The opened filename will become the output
100 destination for this appender.
101
102 <p>If the <code>append</code> parameter is true, the file will be
103 appended to. Otherwise, the file designated by
104 <code>filename</code> will be truncated before being opened.
105 */
106 public
107 FileAppender(Layout layout, String filename, boolean append)
108 throws IOException {
109 this.layout = layout;
110 this.setFile(filename, append, false, bufferSize);
111 }
112
113 /**
114 Instantiate a FileAppender and open the file designated by
115 <code>filename</code>. The opened filename will become the output
116 destination for this appender.
117
118 <p>The file will be appended to. */
119 public
120 FileAppender(Layout layout, String filename) throws IOException {
121 this(layout, filename, true);
122 }
123
124 /**
125 The <b>File</b> property takes a string value which should be the
126 name of the file to append to.
127
128 <p><font color="#DD0044"><b>Note that the special values
129 "System.out" or "System.err" are no longer honored.</b></font>
130
131 <p>Note: Actual opening of the file is made when {@link
132 #activateOptions} is called, not when the options are set. */
133 public void setFile(String file) {
134 // Trim spaces from both ends. The users probably does not want
135 // trailing spaces in file names.
136 String val = file.trim();
137 fileName = val;
138 }
139
140 /**
141 Returns the value of the <b>Append</b> option.
142 */
143 public
144 boolean getAppend() {
145 return fileAppend;
146 }
147
148
149 /** Returns the value of the <b>File</b> option. */
150 public
151 String getFile() {
152 return fileName;
153 }
154
155 /**
156 If the value of <b>File</b> is not <code>null</code>, then {@link
157 #setFile} is called with the values of <b>File</b> and
158 <b>Append</b> properties.
159
160 @since 0.8.1 */
161 public
162 void activateOptions() {
163 if(fileName != null) {
164 try {
165 setFile(fileName, fileAppend, bufferedIO, bufferSize);
166 }
167 catch(java.io.IOException e) {
168 errorHandler.error("setFile("+fileName+","+fileAppend+") call failed.",
169 e, ErrorCode.FILE_OPEN_FAILURE);
170 }
171 } else {
172 //LogLog.error("File option not set for appender ["+name+"].");
173 LogLog.warn("File option not set for appender ["+name+"].");
174 LogLog.warn("Are you using FileAppender instead of ConsoleAppender?");
175 }
176 }
177
178 /**
179 Closes the previously opened file.
180 */
181 protected
182 void closeFile() {
183 if(this.qw != null) {
184 try {
185 this.qw.close();
186 }
187 catch(java.io.IOException e) {
188 if (e instanceof InterruptedIOException) {
189 Thread.currentThread().interrupt();
190 }
191 // Exceptionally, it does not make sense to delegate to an
192 // ErrorHandler. Since a closed appender is basically dead.
193 LogLog.error("Could not close " + qw, e);
194 }
195 }
196 }
197
198 /**
199 Get the value of the <b>BufferedIO</b> option.
200
201 <p>BufferedIO will significatnly increase performance on heavily
202 loaded systems.
203
204 */
205 public
206 boolean getBufferedIO() {
207 return this.bufferedIO;
208 }
209
210
211 /**
212 Get the size of the IO buffer.
213 */
214 public
215 int getBufferSize() {
216 return this.bufferSize;
217 }
218
219
220
221 /**
222 The <b>Append</b> option takes a boolean value. It is set to
223 <code>true</code> by default. If true, then <code>File</code>
224 will be opened in append mode by {@link #setFile setFile} (see
225 above). Otherwise, {@link #setFile setFile} will open
226 <code>File</code> in truncate mode.
227
228 <p>Note: Actual opening of the file is made when {@link
229 #activateOptions} is called, not when the options are set.
230 */
231 public
232 void setAppend(boolean flag) {
233 fileAppend = flag;
234 }
235
236 /**
237 The <b>BufferedIO</b> option takes a boolean value. It is set to
238 <code>false</code> by default. If true, then <code>File</code>
239 will be opened and the resulting {@link java.io.Writer} wrapped
240 around a {@link BufferedWriter}.
241
242 BufferedIO will significatnly increase performance on heavily
243 loaded systems.
244
245 */
246 public
247 void setBufferedIO(boolean bufferedIO) {
248 this.bufferedIO = bufferedIO;
249 if(bufferedIO) {
250 immediateFlush = false;
251 }
252 }
253
254
255 /**
256 Set the size of the IO buffer.
257 */
258 public
259 void setBufferSize(int bufferSize) {
260 this.bufferSize = bufferSize;
261 }
262
263 /**
264 <p>Sets and <i>opens</i> the file where the log output will
265 go. The specified file must be writable.
266
267 <p>If there was already an opened file, then the previous file
268 is closed first.
269
270 <p><b>Do not use this method directly. To configure a FileAppender
271 or one of its subclasses, set its properties one by one and then
272 call activateOptions.</b>
273
274 @param fileName The path to the log file.
275 @param append If true will append to fileName. Otherwise will
276 truncate fileName. */
277 public
278 synchronized
279 void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize)
280 throws IOException {
281 LogLog.debug("setFile called: "+fileName+", "+append);
282
283 // It does not make sense to have immediate flush and bufferedIO.
284 if(bufferedIO) {
285 setImmediateFlush(false);
286 }
287
288 reset();
289 FileOutputStream ostream = null;
290 try {
291 //
292 // attempt to create file
293 //
294 ostream = new FileOutputStream(fileName, append);
295 } catch(FileNotFoundException ex) {
296 //
297 // if parent directory does not exist then
298 // attempt to create it and try to create file
299 // see bug 9150
300 //
301 String parentName = new File(fileName).getParent();
302 if (parentName != null) {
303 File parentDir = new File(parentName);
304 if(!parentDir.exists() && parentDir.mkdirs()) {
305 ostream = new FileOutputStream(fileName, append);
306 } else {
307 throw ex;
308 }
309 } else {
310 throw ex;
311 }
312 }
313 Writer fw = createWriter(ostream);
314 if(bufferedIO) {
315 fw = new BufferedWriter(fw, bufferSize);
316 }
317 this.setQWForFiles(fw);
318 this.fileName = fileName;
319 this.fileAppend = append;
320 this.bufferedIO = bufferedIO;
321 this.bufferSize = bufferSize;
322 writeHeader();
323 LogLog.debug("setFile ended");
324 }
325
326
327 /**
328 Sets the quiet writer being used.
329
330 This method is overriden by {@link RollingFileAppender}.
331 */
332 protected
333 void setQWForFiles(Writer writer) {
334 this.qw = new QuietWriter(writer, errorHandler);
335 }
336
337
338 /**
339 Close any previously opened file and call the parent's
340 <code>reset</code>. */
341 protected
342 void reset() {
343 closeFile();
344 this.fileName = null;
345 super.reset();
346 }
347 }
348