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 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&uuml;lc&uuml;
    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 }