1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.log4j;
21
22 import org.apache.log4j.helpers.AppenderAttachableImpl;
23 import org.apache.log4j.spi.AppenderAttachable;
24 import org.apache.log4j.spi.LoggingEvent;
25
26 import java.text.MessageFormat;
27
28 import java.util.ArrayList;
29 import java.util.Enumeration;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34
35
36 /***
37 * The AsyncAppender lets users log events asynchronously.
38 * <p/>
39 * <p/>
40 * The AsyncAppender will collect the events sent to it and then dispatch them
41 * to all the appenders that are attached to it. You can attach multiple
42 * appenders to an AsyncAppender.
43 * </p>
44 * <p/>
45 * <p/>
46 * The AsyncAppender uses a separate thread to serve the events in its buffer.
47 * </p>
48 * <p/>
49 * <b>Important note:</b> The <code>AsyncAppender</code> can only be script
50 * configured using the {@link org.apache.log4j.xml.DOMConfigurator}.
51 * </p>
52 *
53 * @author Ceki Gülcü
54 * @author Curt Arnold
55 * @since 0.9.1
56 */
57 public class AsyncAppender extends AppenderSkeleton
58 implements AppenderAttachable {
59 /***
60 * The default buffer size is set to 128 events.
61 */
62 public static final int DEFAULT_BUFFER_SIZE = 128;
63
64 /***
65 * Event buffer, also used as monitor to protect itself and
66 * discardMap from simulatenous modifications.
67 */
68 private final List buffer = new ArrayList();
69
70 /***
71 * Map of DiscardSummary objects keyed by logger name.
72 */
73 private final Map discardMap = new HashMap();
74
75 /***
76 * Buffer size.
77 */
78 private int bufferSize = DEFAULT_BUFFER_SIZE;
79
80 /*** Nested appenders. */
81 AppenderAttachableImpl aai;
82
83 /***
84 * Nested appenders.
85 */
86 private final AppenderAttachableImpl appenders;
87
88 /***
89 * Dispatcher.
90 */
91 private final Thread dispatcher;
92
93 /***
94 * Should location info be included in dispatched messages.
95 */
96 private boolean locationInfo = false;
97
98 /***
99 * Does appender block when buffer is full.
100 */
101 private boolean blocking = true;
102
103 /***
104 * Create new instance.
105 */
106 public AsyncAppender() {
107 appenders = new AppenderAttachableImpl();
108
109
110
111 aai = appenders;
112
113 dispatcher =
114 new Thread(new Dispatcher(this, buffer, discardMap, appenders));
115
116
117
118 dispatcher.setDaemon(true);
119
120
121
122 dispatcher.setName("Dispatcher-" + dispatcher.getName());
123 dispatcher.start();
124 }
125
126 /***
127 * Add appender.
128 *
129 * @param newAppender appender to add, may not be null.
130 */
131 public void addAppender(final Appender newAppender) {
132 synchronized (appenders) {
133 appenders.addAppender(newAppender);
134 }
135 }
136
137 /***
138 * {@inheritDoc}
139 */
140 public void append(final LoggingEvent event) {
141
142
143
144
145 if ((dispatcher == null) || !dispatcher.isAlive() || (bufferSize <= 0)) {
146 synchronized (appenders) {
147 appenders.appendLoopOnAppenders(event);
148 }
149
150 return;
151 }
152
153
154
155 event.getNDC();
156 event.getThreadName();
157
158 event.getMDCCopy();
159 if (locationInfo) {
160 event.getLocationInformation();
161 }
162
163 synchronized (buffer) {
164 while (true) {
165 int previousSize = buffer.size();
166
167 if (previousSize < bufferSize) {
168 buffer.add(event);
169
170
171
172
173
174
175 if (previousSize == 0) {
176 buffer.notifyAll();
177 }
178
179 break;
180 }
181
182
183
184
185
186
187
188
189 boolean discard = true;
190 if (blocking
191 && !Thread.interrupted()
192 && Thread.currentThread() != dispatcher) {
193 try {
194 buffer.wait();
195 discard = false;
196 } catch (InterruptedException e) {
197
198
199
200
201 Thread.currentThread().interrupt();
202 }
203 }
204
205
206
207
208
209 if (discard) {
210 String loggerName = event.getLoggerName();
211 DiscardSummary summary = (DiscardSummary) discardMap.get(loggerName);
212
213 if (summary == null) {
214 summary = new DiscardSummary(event);
215 discardMap.put(loggerName, summary);
216 } else {
217 summary.add(event);
218 }
219
220 break;
221 }
222 }
223 }
224 }
225
226 /***
227 * Close this <code>AsyncAppender</code> by interrupting the dispatcher
228 * thread which will process all pending events before exiting.
229 */
230 public void close() {
231 /***
232 * Set closed flag and notify all threads to check their conditions.
233 * Should result in dispatcher terminating.
234 */
235 synchronized (buffer) {
236 closed = true;
237 buffer.notifyAll();
238 }
239
240 try {
241 dispatcher.join();
242 } catch (InterruptedException e) {
243 Thread.currentThread().interrupt();
244 org.apache.log4j.helpers.LogLog.error(
245 "Got an InterruptedException while waiting for the "
246 + "dispatcher to finish.", e);
247 }
248
249
250
251
252 synchronized (appenders) {
253 Enumeration iter = appenders.getAllAppenders();
254
255 if (iter != null) {
256 while (iter.hasMoreElements()) {
257 Object next = iter.nextElement();
258
259 if (next instanceof Appender) {
260 ((Appender) next).close();
261 }
262 }
263 }
264 }
265 }
266
267 /***
268 * Get iterator over attached appenders.
269 * @return iterator or null if no attached appenders.
270 */
271 public Enumeration getAllAppenders() {
272 synchronized (appenders) {
273 return appenders.getAllAppenders();
274 }
275 }
276
277 /***
278 * Get appender by name.
279 *
280 * @param name name, may not be null.
281 * @return matching appender or null.
282 */
283 public Appender getAppender(final String name) {
284 synchronized (appenders) {
285 return appenders.getAppender(name);
286 }
287 }
288
289 /***
290 * Gets whether the location of the logging request call
291 * should be captured.
292 *
293 * @return the current value of the <b>LocationInfo</b> option.
294 */
295 public boolean getLocationInfo() {
296 return locationInfo;
297 }
298
299 /***
300 * Determines if specified appender is attached.
301 * @param appender appender.
302 * @return true if attached.
303 */
304 public boolean isAttached(final Appender appender) {
305 synchronized (appenders) {
306 return appenders.isAttached(appender);
307 }
308 }
309
310 /***
311 * {@inheritDoc}
312 */
313 public boolean requiresLayout() {
314 return false;
315 }
316
317 /***
318 * Removes and closes all attached appenders.
319 */
320 public void removeAllAppenders() {
321 synchronized (appenders) {
322 appenders.removeAllAppenders();
323 }
324 }
325
326 /***
327 * Removes an appender.
328 * @param appender appender to remove.
329 */
330 public void removeAppender(final Appender appender) {
331 synchronized (appenders) {
332 appenders.removeAppender(appender);
333 }
334 }
335
336 /***
337 * Remove appender by name.
338 * @param name name.
339 */
340 public void removeAppender(final String name) {
341 synchronized (appenders) {
342 appenders.removeAppender(name);
343 }
344 }
345
346 /***
347 * The <b>LocationInfo</b> option takes a boolean value. By default, it is
348 * set to false which means there will be no effort to extract the location
349 * information related to the event. As a result, the event that will be
350 * ultimately logged will likely to contain the wrong location information
351 * (if present in the log format).
352 * <p/>
353 * <p/>
354 * Location information extraction is comparatively very slow and should be
355 * avoided unless performance is not a concern.
356 * </p>
357 * @param flag true if location information should be extracted.
358 */
359 public void setLocationInfo(final boolean flag) {
360 locationInfo = flag;
361 }
362
363 /***
364 * Sets the number of messages allowed in the event buffer
365 * before the calling thread is blocked (if blocking is true)
366 * or until messages are summarized and discarded. Changing
367 * the size will not affect messages already in the buffer.
368 *
369 * @param size buffer size, must be positive.
370 */
371 public void setBufferSize(final int size) {
372
373
374
375
376 if (size < 0) {
377 throw new java.lang.NegativeArraySizeException("size");
378 }
379
380 synchronized (buffer) {
381
382
383
384 bufferSize = (size < 1) ? 1 : size;
385 buffer.notifyAll();
386 }
387 }
388
389 /***
390 * Gets the current buffer size.
391 * @return the current value of the <b>BufferSize</b> option.
392 */
393 public int getBufferSize() {
394 return bufferSize;
395 }
396
397 /***
398 * Sets whether appender should wait if there is no
399 * space available in the event buffer or immediately return.
400 *
401 * @param value true if appender should wait until available space in buffer.
402 */
403 public void setBlocking(final boolean value) {
404 synchronized (buffer) {
405 blocking = value;
406 buffer.notifyAll();
407 }
408 }
409
410 /***
411 * Gets whether appender should block calling thread when buffer is full.
412 * If false, messages will be counted by logger and a summary
413 * message appended after the contents of the buffer have been appended.
414 *
415 * @return true if calling thread will be blocked when buffer is full.
416 */
417 public boolean getBlocking() {
418 return blocking;
419 }
420
421 /***
422 * Summary of discarded logging events for a logger.
423 */
424 private static final class DiscardSummary {
425 /***
426 * First event of the highest severity.
427 */
428 private LoggingEvent maxEvent;
429
430 /***
431 * Total count of messages discarded.
432 */
433 private int count;
434
435 /***
436 * Create new instance.
437 *
438 * @param event event, may not be null.
439 */
440 public DiscardSummary(final LoggingEvent event) {
441 maxEvent = event;
442 count = 1;
443 }
444
445 /***
446 * Add discarded event to summary.
447 *
448 * @param event event, may not be null.
449 */
450 public void add(final LoggingEvent event) {
451 if (event.getLevel().toInt() > maxEvent.getLevel().toInt()) {
452 maxEvent = event;
453 }
454
455 count++;
456 }
457
458 /***
459 * Create event with summary information.
460 *
461 * @return new event.
462 */
463 public LoggingEvent createEvent() {
464 String msg =
465 MessageFormat.format(
466 "Discarded {0} messages due to full event buffer including: {1}",
467 new Object[] { new Integer(count), maxEvent.getMessage() });
468
469 return new LoggingEvent(
470 "org.apache.log4j.AsyncAppender.DONT_REPORT_LOCATION",
471 Logger.getLogger(maxEvent.getLoggerName()),
472 maxEvent.getLevel(),
473 msg,
474 null);
475 }
476 }
477
478 /***
479 * Event dispatcher.
480 */
481 private static class Dispatcher implements Runnable {
482 /***
483 * Parent AsyncAppender.
484 */
485 private final AsyncAppender parent;
486
487 /***
488 * Event buffer.
489 */
490 private final List buffer;
491
492 /***
493 * Map of DiscardSummary keyed by logger name.
494 */
495 private final Map discardMap;
496
497 /***
498 * Wrapped appenders.
499 */
500 private final AppenderAttachableImpl appenders;
501
502 /***
503 * Create new instance of dispatcher.
504 *
505 * @param parent parent AsyncAppender, may not be null.
506 * @param buffer event buffer, may not be null.
507 * @param discardMap discard map, may not be null.
508 * @param appenders appenders, may not be null.
509 */
510 public Dispatcher(
511 final AsyncAppender parent, final List buffer, final Map discardMap,
512 final AppenderAttachableImpl appenders) {
513
514 this.parent = parent;
515 this.buffer = buffer;
516 this.appenders = appenders;
517 this.discardMap = discardMap;
518 }
519
520 /***
521 * {@inheritDoc}
522 */
523 public void run() {
524 boolean isActive = true;
525
526
527
528
529 try {
530
531
532
533 while (isActive) {
534 LoggingEvent[] events = null;
535
536
537
538
539
540 synchronized (buffer) {
541 int bufferSize = buffer.size();
542 isActive = !parent.closed;
543
544 while ((bufferSize == 0) && isActive) {
545 buffer.wait();
546 bufferSize = buffer.size();
547 isActive = !parent.closed;
548 }
549
550 if (bufferSize > 0) {
551 events = new LoggingEvent[bufferSize + discardMap.size()];
552 buffer.toArray(events);
553
554
555
556
557 int index = bufferSize;
558
559 for (
560 Iterator iter = discardMap.values().iterator();
561 iter.hasNext();) {
562 events[index++] = ((DiscardSummary) iter.next()).createEvent();
563 }
564
565
566
567
568 buffer.clear();
569 discardMap.clear();
570
571
572
573 buffer.notifyAll();
574 }
575 }
576
577
578
579
580 if (events != null) {
581 for (int i = 0; i < events.length; i++) {
582 synchronized (appenders) {
583 appenders.appendLoopOnAppenders(events[i]);
584 }
585 }
586 }
587 }
588 } catch (InterruptedException ex) {
589 Thread.currentThread().interrupt();
590 }
591 }
592 }
593 }