View Javadoc

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 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&uuml;lc&uuml;
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     //   only set for compatibility
111     aai = appenders;
112 
113     dispatcher =
114       new Thread(new Dispatcher(this, buffer, discardMap, appenders));
115 
116     // It is the user's responsibility to close appenders before
117     // exiting.
118     dispatcher.setDaemon(true);
119 
120     // set the dispatcher priority to lowest possible value
121     //        dispatcher.setPriority(Thread.MIN_PRIORITY);
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     //   if dispatcher thread has died then
143     //      append subsequent events synchronously
144     //   See bug 23021
145     if ((dispatcher == null) || !dispatcher.isAlive() || (bufferSize <= 0)) {
146       synchronized (appenders) {
147         appenders.appendLoopOnAppenders(event);
148       }
149 
150       return;
151     }
152 
153     // Set the NDC and thread name for the calling thread as these
154     // LoggingEvent fields were not set at event creation time.
155     event.getNDC();
156     event.getThreadName();
157     // Get a copy of this thread's MDC.
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           //   if buffer had been empty
172           //       signal all threads waiting on buffer
173           //       to check their conditions.
174           //
175           if (previousSize == 0) {
176             buffer.notifyAll();
177           }
178 
179           break;
180         }
181 
182         //
183         //   Following code is only reachable if buffer is full
184         //
185         //
186         //   if blocking and thread is not already interrupted
187         //      and not the dispatcher then
188         //      wait for a buffer notification
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             //  reset interrupt status so
199             //    calling code can see interrupt on
200             //    their next wait or sleep.
201             Thread.currentThread().interrupt();
202           }
203         }
204 
205         //
206         //   if blocking is false or thread has been interrupted
207         //   add event to discard map.
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     //    close all attached appenders.
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     //   log4j 1.2 would throw exception if size was negative
374     //      and deadlock if size was zero.
375     //
376     if (size < 0) {
377       throw new java.lang.NegativeArraySizeException("size");
378     }
379 
380     synchronized (buffer) {
381       //
382       //   don't let size be zero.
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       //   if interrupted (unlikely), end thread
528       //
529       try {
530         //
531         //   loop until the AsyncAppender is closed.
532         //
533         while (isActive) {
534           LoggingEvent[] events = null;
535 
536           //
537           //   extract pending events while synchronized
538           //       on buffer
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               //   add events due to buffer overflow
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               //    clear buffer and discard map
567               //
568               buffer.clear();
569               discardMap.clear();
570 
571               //
572               //    allow blocked appends to continue
573               buffer.notifyAll();
574             }
575           }
576 
577           //
578           //   process events after lock on buffer is released.
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 }