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