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 }