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