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