1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.log4j.rolling;
18
19 import org.apache.log4j.Appender;
20 import org.apache.log4j.FileAppender;
21 import org.apache.log4j.Logger;
22 import org.apache.log4j.helpers.LogLog;
23 import org.apache.log4j.helpers.QuietWriter;
24 import org.apache.log4j.rolling.helper.Action;
25 import org.apache.log4j.spi.ErrorHandler;
26 import org.apache.log4j.spi.LoggingEvent;
27 import org.apache.log4j.spi.OptionHandler;
28 import org.apache.log4j.xml.UnrecognizedElementHandler;
29 import org.w3c.dom.Element;
30
31 import java.io.File;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.io.OutputStream;
35 import java.io.OutputStreamWriter;
36 import java.io.Writer;
37 import java.util.Properties;
38
39
40 /***
41 * <code>RollingFileAppender</code> extends {@link FileAppender} to backup the log files
42 * depending on {@link RollingPolicy} and {@link TriggeringPolicy}.
43 * <p>
44 * To be of any use, a <code>RollingFileAppender</code> instance must have both
45 * a <code>RollingPolicy</code> and a <code>TriggeringPolicy</code> set up.
46 * However, if its <code>RollingPolicy</code> also implements the
47 * <code>TriggeringPolicy</code> interface, then only the former needs to be
48 * set up. For example, {@link TimeBasedRollingPolicy} acts both as a
49 * <code>RollingPolicy</code> and a <code>TriggeringPolicy</code>.
50 *
51 * <p><code>RollingFileAppender</code> can be configured programattically or
52 * using {@link org.apache.log4j.extras.DOMConfigurator} or
53 * {@link org.apache.log4j.xml.DOMConfigurator} in log4j 1.2.15 or later. Here is a sample
54 * configration file:
55
56 <pre><?xml version="1.0" encoding="UTF-8" ?>
57 <!DOCTYPE log4j:configuration>
58
59 <log4j:configuration debug="true">
60
61 <appender name="ROLL" class="org.apache.log4j.rolling.RollingFileAppender">
62 <b><rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy">
63 <param name="FileNamePattern" value="/wombat/foo.%d{yyyy-MM}.gz"/>
64 </rollingPolicy></b>
65
66 <layout class="org.apache.log4j.PatternLayout">
67 <param name="ConversionPattern" value="%c{1} - %m%n"/>
68 </layout>
69 </appender>
70
71 <root">
72 <appender-ref ref="ROLL"/>
73 </root>
74
75 </log4j:configuration>
76 </pre>
77
78 *<p>This configuration file specifies a monthly rollover schedule including
79 * automatic compression of the archived files. See
80 * {@link TimeBasedRollingPolicy} for more details.
81 *
82 * @author Heinz Richter
83 * @author Ceki Gülcü
84 * @since 1.3
85 * */
86 public final class RollingFileAppender extends FileAppender
87 implements UnrecognizedElementHandler {
88 /***
89 * Triggering policy.
90 */
91 private TriggeringPolicy triggeringPolicy;
92
93 /***
94 * Rolling policy.
95 */
96 private RollingPolicy rollingPolicy;
97
98 /***
99 * Length of current active log file.
100 */
101 private long fileLength = 0;
102
103 /***
104 * Asynchronous action (like compression) from previous rollover.
105 */
106 private Action lastRolloverAsyncAction = null;
107
108
109 /***
110 * Construct a new instance.
111 */
112 public RollingFileAppender() {
113 }
114
115 /***
116 * Prepare instance of use.
117 */
118 public void activateOptions() {
119 if (rollingPolicy == null) {
120 LogLog.warn(
121 "Please set a rolling policy for the RollingFileAppender named '"
122 + getName() + "'");
123
124 return;
125 }
126
127
128
129
130 if (
131 (triggeringPolicy == null) && rollingPolicy instanceof TriggeringPolicy) {
132 triggeringPolicy = (TriggeringPolicy) rollingPolicy;
133 }
134
135 if (triggeringPolicy == null) {
136 LogLog.warn(
137 "Please set a TriggeringPolicy for the RollingFileAppender named '"
138 + getName() + "'");
139
140 return;
141 }
142
143 Exception exception = null;
144
145 synchronized (this) {
146 triggeringPolicy.activateOptions();
147 rollingPolicy.activateOptions();
148
149 try {
150 RolloverDescription rollover =
151 rollingPolicy.initialize(getFile(), getAppend());
152
153 if (rollover != null) {
154 Action syncAction = rollover.getSynchronous();
155
156 if (syncAction != null) {
157 syncAction.execute();
158 }
159
160 setFile(rollover.getActiveFileName());
161 setAppend(rollover.getAppend());
162 lastRolloverAsyncAction = rollover.getAsynchronous();
163
164 if (lastRolloverAsyncAction != null) {
165 Thread runner = new Thread(lastRolloverAsyncAction);
166 runner.start();
167 }
168 }
169
170 File activeFile = new File(getFile());
171
172 if (getAppend()) {
173 fileLength = activeFile.length();
174 } else {
175 fileLength = 0;
176 }
177
178 super.activateOptions();
179 } catch (Exception ex) {
180 exception = ex;
181 }
182 }
183
184 if (exception != null) {
185 LogLog.warn(
186 "Exception while initializing RollingFileAppender named '" + getName()
187 + "'", exception);
188 }
189 }
190
191
192 private QuietWriter createQuietWriter(final Writer writer) {
193 ErrorHandler handler = errorHandler;
194 if (handler == null) {
195 handler = new DefaultErrorHandler(this);
196 }
197 return new QuietWriter(writer, handler);
198 }
199
200
201 /***
202 Implements the usual roll over behaviour.
203
204 <p>If <code>MaxBackupIndex</code> is positive, then files
205 {<code>File.1</code>, ..., <code>File.MaxBackupIndex -1</code>}
206 are renamed to {<code>File.2</code>, ...,
207 <code>File.MaxBackupIndex</code>}. Moreover, <code>File</code> is
208 renamed <code>File.1</code> and closed. A new <code>File</code> is
209 created to receive further log output.
210
211 <p>If <code>MaxBackupIndex</code> is equal to zero, then the
212 <code>File</code> is truncated with no backup files created.
213
214 * @return true if rollover performed.
215 */
216 public boolean rollover() {
217
218
219
220 if (rollingPolicy != null) {
221 Exception exception = null;
222
223 synchronized (this) {
224
225
226
227 if (lastRolloverAsyncAction != null) {
228
229
230
231 lastRolloverAsyncAction.close();
232
233
234
235
236
237 }
238
239 try {
240 RolloverDescription rollover = rollingPolicy.rollover(getFile());
241
242 if (rollover != null) {
243 if (rollover.getActiveFileName().equals(getFile())) {
244 closeWriter();
245
246 boolean success = true;
247
248 if (rollover.getSynchronous() != null) {
249 success = false;
250
251 try {
252 success = rollover.getSynchronous().execute();
253 } catch (Exception ex) {
254 exception = ex;
255 }
256 }
257
258 if (success) {
259 if (rollover.getAppend()) {
260 fileLength = new File(rollover.getActiveFileName()).length();
261 } else {
262 fileLength = 0;
263 }
264
265 if (rollover.getAsynchronous() != null) {
266 lastRolloverAsyncAction = rollover.getAsynchronous();
267 new Thread(lastRolloverAsyncAction).start();
268 }
269
270 setFile(
271 rollover.getActiveFileName(), rollover.getAppend(),
272 bufferedIO, bufferSize);
273 } else {
274 setFile(
275 rollover.getActiveFileName(), true, bufferedIO, bufferSize);
276
277 if (exception == null) {
278 LogLog.warn("Failure in post-close rollover action");
279 } else {
280 LogLog.warn(
281 "Exception in post-close rollover action", exception);
282 }
283 }
284 } else {
285 Writer newWriter =
286 createWriter(
287 new FileOutputStream(
288 rollover.getActiveFileName(), rollover.getAppend()));
289 closeWriter();
290 setFile(rollover.getActiveFileName());
291 this.qw = createQuietWriter(newWriter);
292
293 boolean success = true;
294
295 if (rollover.getSynchronous() != null) {
296 success = false;
297
298 try {
299 success = rollover.getSynchronous().execute();
300 } catch (Exception ex) {
301 exception = ex;
302 }
303 }
304
305 if (success) {
306 if (rollover.getAppend()) {
307 fileLength = new File(rollover.getActiveFileName()).length();
308 } else {
309 fileLength = 0;
310 }
311
312 if (rollover.getAsynchronous() != null) {
313 lastRolloverAsyncAction = rollover.getAsynchronous();
314 new Thread(lastRolloverAsyncAction).start();
315 }
316 }
317
318 writeHeader();
319 }
320
321 return true;
322 }
323 } catch (Exception ex) {
324 exception = ex;
325 }
326 }
327
328 if (exception != null) {
329 LogLog.warn(
330 "Exception during rollover, rollover deferred.", exception);
331 }
332 }
333
334 return false;
335 }
336
337 /***
338 * {@inheritDoc}
339 */
340 protected void subAppend(final LoggingEvent event) {
341
342
343 if (
344 triggeringPolicy.isTriggeringEvent(
345 this, event, getFile(), getFileLength())) {
346
347
348
349
350
351 try {
352 rollover();
353 } catch (Exception ex) {
354 LogLog.warn("Exception during rollover attempt.", ex);
355 }
356 }
357
358 super.subAppend(event);
359 }
360
361 /***
362 * Get rolling policy.
363 * @return rolling policy.
364 */
365 public RollingPolicy getRollingPolicy() {
366 return rollingPolicy;
367 }
368
369 /***
370 * Get triggering policy.
371 * @return triggering policy.
372 */
373 public TriggeringPolicy getTriggeringPolicy() {
374 return triggeringPolicy;
375 }
376
377 /***
378 * Sets the rolling policy.
379 * @param policy rolling policy.
380 */
381 public void setRollingPolicy(final RollingPolicy policy) {
382 rollingPolicy = policy;
383 }
384
385 /***
386 * Set triggering policy.
387 * @param policy triggering policy.
388 */
389 public void setTriggeringPolicy(final TriggeringPolicy policy) {
390 triggeringPolicy = policy;
391 }
392
393 /***
394 * Close appender. Waits for any asynchronous file compression actions to be completed.
395 */
396 public void close() {
397 synchronized (this) {
398 if (lastRolloverAsyncAction != null) {
399 lastRolloverAsyncAction.close();
400 }
401 }
402
403 super.close();
404 }
405
406 /***
407 Returns an OutputStreamWriter when passed an OutputStream. The
408 encoding used will depend on the value of the
409 <code>encoding</code> property. If the encoding value is
410 specified incorrectly the writer will be opened using the default
411 system encoding (an error message will be printed to the loglog.
412 @param os output stream, may not be null.
413 @return new writer.
414 */
415 protected OutputStreamWriter createWriter(final OutputStream os) {
416 return super.createWriter(new CountingOutputStream(os, this));
417 }
418
419 /***
420 * Get byte length of current active log file.
421 * @return byte length of current active log file.
422 */
423 public long getFileLength() {
424 return fileLength;
425 }
426
427 /***
428 * Increments estimated byte length of current active log file.
429 * @param increment additional bytes written to log file.
430 */
431 public synchronized void incrementFileLength(int increment) {
432 fileLength += increment;
433 }
434
435
436
437 /***
438 * {@inheritDoc}
439 */
440 public boolean parseUnrecognizedElement(final Element element,
441 final Properties props) throws Exception {
442 final String nodeName = element.getNodeName();
443 if ("rollingPolicy".equals(nodeName)) {
444 OptionHandler rollingPolicy =
445 org.apache.log4j.extras.DOMConfigurator.parseElement(
446 element, props, RollingPolicy.class);
447 if (rollingPolicy != null) {
448 rollingPolicy.activateOptions();
449 this.setRollingPolicy((RollingPolicy) rollingPolicy);
450 }
451 return true;
452 }
453 if ("triggeringPolicy".equals(nodeName)) {
454 OptionHandler triggerPolicy =
455 org.apache.log4j.extras.DOMConfigurator.parseElement(
456 element, props, TriggeringPolicy.class);
457 if (triggerPolicy != null) {
458 triggerPolicy.activateOptions();
459 this.setTriggeringPolicy((TriggeringPolicy) triggerPolicy);
460 }
461 return true;
462 }
463 return false;
464 }
465
466 /***
467 * Wrapper for OutputStream that will report all write
468 * operations back to this class for file length calculations.
469 */
470 private static class CountingOutputStream extends OutputStream {
471 /***
472 * Wrapped output stream.
473 */
474 private final OutputStream os;
475
476 /***
477 * Rolling file appender to inform of stream writes.
478 */
479 private final RollingFileAppender rfa;
480
481 /***
482 * Constructor.
483 * @param os output stream to wrap.
484 * @param rfa rolling file appender to inform.
485 */
486 public CountingOutputStream(
487 final OutputStream os, final RollingFileAppender rfa) {
488 this.os = os;
489 this.rfa = rfa;
490 }
491
492 /***
493 * {@inheritDoc}
494 */
495 public void close() throws IOException {
496 os.close();
497 }
498
499 /***
500 * {@inheritDoc}
501 */
502 public void flush() throws IOException {
503 os.flush();
504 }
505
506 /***
507 * {@inheritDoc}
508 */
509 public void write(final byte[] b) throws IOException {
510 os.write(b);
511 rfa.incrementFileLength(b.length);
512 }
513
514 /***
515 * {@inheritDoc}
516 */
517 public void write(final byte[] b, final int off, final int len)
518 throws IOException {
519 os.write(b, off, len);
520 rfa.incrementFileLength(len);
521 }
522
523 /***
524 * {@inheritDoc}
525 */
526 public void write(final int b) throws IOException {
527 os.write(b);
528 rfa.incrementFileLength(1);
529 }
530 }
531
532 private static final class DefaultErrorHandler implements ErrorHandler {
533 private final RollingFileAppender appender;
534 public DefaultErrorHandler(final RollingFileAppender appender) {
535 this.appender = appender;
536 }
537 /***@since 1.2*/
538 public void setLogger(Logger logger) {
539
540 }
541 public void error(String message, Exception ioe, int errorCode) {
542 appender.close();
543 LogLog.error("IO failure for appender named "+ appender.getName(), ioe);
544 }
545 public void error(String message) {
546
547 }
548 /***@since 1.2*/
549 public void error(String message, Exception e, int errorCode, LoggingEvent event) {
550
551 }
552 /***@since 1.2*/
553 public void setAppender(Appender appender) {
554
555 }
556 /***@since 1.2*/
557 public void setBackupAppender(Appender appender) {
558
559 }
560
561 public void activateOptions() {
562 }
563
564 }
565
566 }