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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 // Contibutors: Aaron Greenhouse <aarong@cs.cmu.edu>
19 // Thomas Tuft Muller <ttm@online.no>
20 package org.apache.log4j;
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;
30 import org.apache.log4j.helpers.AppenderAttachableImpl;
31 import org.apache.log4j.spi.AppenderAttachable;
32 import org.apache.log4j.spi.LoggingEvent;
36 * The AsyncAppender lets users log events asynchronously.
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.
45 * The AsyncAppender uses a separate thread to serve the events in its buffer.
48 * <b>Important note:</b> The <code>AsyncAppender</code> can only be script
49 * configured using the {@link org.apache.log4j.xml.DOMConfigurator}.
52 * @author Ceki Gülcü
56 public class AsyncAppender extends AppenderSkeleton
57 implements AppenderAttachable {
59 * The default buffer size is set to 128 events.
61 public static final int DEFAULT_BUFFER_SIZE = 128;
64 * Event buffer, also used as monitor to protect itself and
65 * discardMap from simulatenous modifications.
67 private final List buffer = new ArrayList();
70 * Map of DiscardSummary objects keyed by logger name.
72 private final Map discardMap = new HashMap();
77 private int bufferSize = DEFAULT_BUFFER_SIZE;
79 /** Nested appenders. */
80 AppenderAttachableImpl aai;
85 private final AppenderAttachableImpl appenders;
90 private final Thread dispatcher;
93 * Should location info be included in dispatched messages.
95 private boolean locationInfo = false;
98 * Does appender block when buffer is full.
100 private boolean blocking = true;
103 * Create new instance.
105 public AsyncAppender() {
106 appenders = new AppenderAttachableImpl();
109 // only set for compatibility
113 new Thread(new Dispatcher(this, buffer, discardMap, appenders));
115 // It is the user's responsibility to close appenders before
117 dispatcher.setDaemon(true);
119 // set the dispatcher priority to lowest possible value
120 // dispatcher.setPriority(Thread.MIN_PRIORITY);
121 dispatcher.setName("AsyncAppender-Dispatcher-" + dispatcher.getName());
128 * @param newAppender appender to add, may not be null.
130 public void addAppender(final Appender newAppender) {
131 synchronized (appenders) {
132 appenders.addAppender(newAppender);
139 public void append(final LoggingEvent event) {
141 // if dispatcher thread has died then
142 // append subsequent events synchronously
144 if ((dispatcher == null) || !dispatcher.isAlive() || (bufferSize <= 0)) {
145 synchronized (appenders) {
146 appenders.appendLoopOnAppenders(event);
152 // Set the NDC and thread name for the calling thread as these
153 // LoggingEvent fields were not set at event creation time.
155 event.getThreadName();
156 // Get a copy of this thread's MDC.
159 event.getLocationInformation();
161 event.getRenderedMessage();
162 event.getThrowableStrRep();
164 synchronized (buffer) {
166 int previousSize = buffer.size();
168 if (previousSize < bufferSize) {
172 // if buffer had been empty
173 // signal all threads waiting on buffer
174 // to check their conditions.
176 if (previousSize == 0) {
184 // Following code is only reachable if buffer is full
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;
192 && !Thread.interrupted()
193 && Thread.currentThread() != dispatcher) {
197 } catch (InterruptedException e) {
199 // reset interrupt status so
200 // calling code can see interrupt on
201 // their next wait or sleep.
202 Thread.currentThread().interrupt();
207 // if blocking is false or thread has been interrupted
208 // add event to discard map.
211 String loggerName = event.getLoggerName();
212 DiscardSummary summary = (DiscardSummary) discardMap.get(loggerName);
214 if (summary == null) {
215 summary = new DiscardSummary(event);
216 discardMap.put(loggerName, summary);
228 * Close this <code>AsyncAppender</code> by interrupting the dispatcher
229 * thread which will process all pending events before exiting.
231 public void close() {
233 * Set closed flag and notify all threads to check their conditions.
234 * Should result in dispatcher terminating.
236 synchronized (buffer) {
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);
251 // close all attached appenders.
253 synchronized (appenders) {
254 Enumeration iter = appenders.getAllAppenders();
257 while (iter.hasMoreElements()) {
258 Object next = iter.nextElement();
260 if (next instanceof Appender) {
261 ((Appender) next).close();
269 * Get iterator over attached appenders.
270 * @return iterator or null if no attached appenders.
272 public Enumeration getAllAppenders() {
273 synchronized (appenders) {
274 return appenders.getAllAppenders();
279 * Get appender by name.
281 * @param name name, may not be null.
282 * @return matching appender or null.
284 public Appender getAppender(final String name) {
285 synchronized (appenders) {
286 return appenders.getAppender(name);
291 * Gets whether the location of the logging request call
292 * should be captured.
294 * @return the current value of the <b>LocationInfo</b> option.
296 public boolean getLocationInfo() {
301 * Determines if specified appender is attached.
302 * @param appender appender.
303 * @return true if attached.
305 public boolean isAttached(final Appender appender) {
306 synchronized (appenders) {
307 return appenders.isAttached(appender);
314 public boolean requiresLayout() {
319 * Removes and closes all attached appenders.
321 public void removeAllAppenders() {
322 synchronized (appenders) {
323 appenders.removeAllAppenders();
328 * Removes an appender.
329 * @param appender appender to remove.
331 public void removeAppender(final Appender appender) {
332 synchronized (appenders) {
333 appenders.removeAppender(appender);
338 * Remove appender by name.
341 public void removeAppender(final String name) {
342 synchronized (appenders) {
343 appenders.removeAppender(name);
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).
355 * Location information extraction is comparatively very slow and should be
356 * avoided unless performance is not a concern.
358 * @param flag true if location information should be extracted.
360 public void setLocationInfo(final boolean flag) {
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.
370 * @param size buffer size, must be positive.
372 public void setBufferSize(final int size) {
374 // log4j 1.2 would throw exception if size was negative
375 // and deadlock if size was zero.
378 throw new java.lang.NegativeArraySizeException("size");
381 synchronized (buffer) {
383 // don't let size be zero.
385 bufferSize = (size < 1) ? 1 : size;
391 * Gets the current buffer size.
392 * @return the current value of the <b>BufferSize</b> option.
394 public int getBufferSize() {
399 * Sets whether appender should wait if there is no
400 * space available in the event buffer or immediately return.
403 * @param value true if appender should wait until available space in buffer.
405 public void setBlocking(final boolean value) {
406 synchronized (buffer) {
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.
418 * @return true if calling thread will be blocked when buffer is full.
420 public boolean getBlocking() {
425 * Summary of discarded logging events for a logger.
427 private static final class DiscardSummary {
429 * First event of the highest severity.
431 private LoggingEvent maxEvent;
434 * Total count of messages discarded.
439 * Create new instance.
441 * @param event event, may not be null.
443 public DiscardSummary(final LoggingEvent event) {
449 * Add discarded event to summary.
451 * @param event event, may not be null.
453 public void add(final LoggingEvent event) {
454 if (event.getLevel().toInt() > maxEvent.getLevel().toInt()) {
462 * Create event with summary information.
466 public LoggingEvent createEvent() {
468 MessageFormat.format(
469 "Discarded {0} messages due to full event buffer including: {1}",
470 new Object[] { new Integer(count), maxEvent.getMessage() });
472 return new LoggingEvent(
473 "org.apache.log4j.AsyncAppender.DONT_REPORT_LOCATION",
474 Logger.getLogger(maxEvent.getLoggerName()),
484 private static class Dispatcher implements Runnable {
486 * Parent AsyncAppender.
488 private final AsyncAppender parent;
493 private final List buffer;
496 * Map of DiscardSummary keyed by logger name.
498 private final Map discardMap;
503 private final AppenderAttachableImpl appenders;
506 * Create new instance of dispatcher.
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.
514 final AsyncAppender parent, final List buffer, final Map discardMap,
515 final AppenderAttachableImpl appenders) {
517 this.parent = parent;
518 this.buffer = buffer;
519 this.appenders = appenders;
520 this.discardMap = discardMap;
527 boolean isActive = true;
530 // if interrupted (unlikely), end thread
534 // loop until the AsyncAppender is closed.
537 LoggingEvent[] events = null;
540 // extract pending events while synchronized
543 synchronized (buffer) {
544 int bufferSize = buffer.size();
545 isActive = !parent.closed;
547 while ((bufferSize == 0) && isActive) {
549 bufferSize = buffer.size();
550 isActive = !parent.closed;
553 if (bufferSize > 0) {
554 events = new LoggingEvent[bufferSize + discardMap.size()];
555 buffer.toArray(events);
558 // add events due to buffer overflow
560 int index = bufferSize;
563 Iterator iter = discardMap.values().iterator();
565 events[index++] = ((DiscardSummary) iter.next()).createEvent();
569 // clear buffer and discard map
575 // allow blocked appends to continue
581 // process events after lock on buffer is released.
583 if (events != null) {
584 for (int i = 0; i < events.length; i++) {
585 synchronized (appenders) {
586 appenders.appendLoopOnAppenders(events[i]);
591 } catch (InterruptedException ex) {
592 Thread.currentThread().interrupt();