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  package org.apache.log4j.chainsaw;
    18
    19  import org.apache.log4j.AppenderSkeleton;
    20  import org.apache.log4j.LogManager;
    21  import org.apache.log4j.helpers.Constants;
    22  import org.apache.log4j.rule.ExpressionRule;
    23  import org.apache.log4j.rule.Rule;
    24  import org.apache.log4j.spi.LoggerRepositoryEx;
    25  import org.apache.log4j.spi.LoggingEvent;
    26  import org.apache.log4j.spi.LoggingEventFieldResolver;
    27
    28  import javax.swing.event.EventListenerList;
    29  import java.beans.PropertyChangeListener;
    30  import java.beans.PropertyChangeSupport;
    31  import java.util.*;
    32
    33  /**
    34   * A handler class that either extends a particular appender hierarchy or can be
    35   * bound into the Log4j appender framework, and queues events, to be later
    36   * dispatched to registered/interested parties.
    37   *
    38   * @author Scott Deboy &lt;sdeboy@apache.org&gt;
    39   * @author Paul Smith &lt;psmith@apache.org&gt;
    40   */
    41  public class ChainsawAppenderHandler extends AppenderSkeleton {
    42      private static final String DEFAULT_IDENTIFIER = "Unknown";
    43      private final Object mutex = new Object();
    44      private int sleepInterval = 1000;
    45      private EventListenerList listenerList = new EventListenerList();
    46      private double dataRate = 0.0;
    47      private String identifierExpression;
    48      private final LoggingEventFieldResolver resolver = LoggingEventFieldResolver
    49          .getInstance();
    50      private PropertyChangeSupport propertySupport = new PropertyChangeSupport(
    51          this);
    52      private Map<String, Rule> customExpressionRules = new HashMap<>();
    53
    54      /**
    55       * NOTE: This variable needs to be physically located LAST, because
    56       * of the initialization sequence, the WorkQueue constructor starts a thread
    57       * which ends up needing some reference to fields created in ChainsawAppenderHandler (outer instance)
    58       * which may not have been created yet.  Becomes a race condition, and therefore
    59       * this field initialization should be kept last.
    60       */
    61      private WorkQueue worker = new WorkQueue();
    62
    63      public ChainsawAppenderHandler(final ChainsawAppender appender) {
    64          super(true);
    65          appender.setAppender(this);
    66      }
    67
    68      public ChainsawAppenderHandler() {
    69          super(true);
    70      }
    71
    72      public void setIdentifierExpression(String identifierExpression) {
    73          synchronized (mutex) {
    74              this.identifierExpression = identifierExpression;
    75              mutex.notify();
    76          }
    77      }
    78
    79      public String getIdentifierExpression() {
    80          return identifierExpression;
    81      }
    82
    83      public void addCustomEventBatchListener(String identifier,
    84                                              EventBatchListener l) throws IllegalArgumentException {
    85          customExpressionRules.put(identifier, ExpressionRule.getRule(identifier));
    86          listenerList.add(EventBatchListener.class, l);
    87      }
    88
    89      public void addEventBatchListener(EventBatchListener l) {
    90          listenerList.add(EventBatchListener.class, l);
    91      }
    92
    93      public void removeEventBatchListener(EventBatchListener l) {
    94          listenerList.remove(EventBatchListener.class, l);
    95      }
    96
    97      public void append(LoggingEvent event) {
    98          worker.enqueue(event);
    99      }
    100
    101     public void close() {
    102     }
    103
    104     public boolean requiresLayout() {
    105         return false;
    106     }
    107
    108     public int getQueueInterval() {
    109         return sleepInterval;
    110     }
    111
    112     public void setQueueInterval(int interval) {
    113         sleepInterval = interval;
    114     }
    115
    116     /**
    117      * Determines an appropriate title for the Tab for the Tab Pane by locating a
    118      * the hostname property
    119      *
    120      * @param e
    121      * @return identifier
    122      */
    123     String getTabIdentifier(LoggingEvent e) {
    124         String ident = resolver.applyFields(identifierExpression, e);
    125         return ((ident != null) ? ident : DEFAULT_IDENTIFIER);
    126     }
    127
    128     /**
    129      * Exposes the current Data rate calculated. This is periodically updated by
    130      * an internal Thread as is the number of events that have been processed, and
    131      * dispatched to all listeners since the last sample period divided by the
    132      * number of seconds since the last sample period.
    133      * <p>
    134      * This method fires a PropertyChange event so listeners can monitor the rate
    135      *
    136      * @return double # of events processed per second
    137      */
    138     public double getDataRate() {
    139         return dataRate;
    140     }
    141
    142     /**
    143      * @param dataRate
    144      */
    145     void setDataRate(double dataRate) {
    146         double oldValue = this.dataRate;
    147         this.dataRate = dataRate;
    148         propertySupport.firePropertyChange("dataRate", oldValue,
    149             this.dataRate);
    150     }
    151
    152     /**
    153      * @param listener
    154      */
    155     public synchronized void addPropertyChangeListener(
    156         PropertyChangeListener listener) {
    157         propertySupport.addPropertyChangeListener(listener);
    158     }
    159
    160     /**
    161      * @param propertyName
    162      * @param listener
    163      */
    164     public synchronized void addPropertyChangeListener(String propertyName,
    165                                                        PropertyChangeListener listener) {
    166         propertySupport.addPropertyChangeListener(propertyName, listener);
    167     }
    168
    169     /**
    170      * @param listener
    171      */
    172     public synchronized void removePropertyChangeListener(
    173         PropertyChangeListener listener) {
    174         propertySupport.removePropertyChangeListener(listener);
    175     }
    176
    177     /**
    178      * @param propertyName
    179      * @param listener
    180      */
    181     public synchronized void removePropertyChangeListener(String propertyName,
    182                                                           PropertyChangeListener listener) {
    183         propertySupport.removePropertyChangeListener(propertyName, listener);
    184     }
    185
    186     /**
    187      * Queue of Events are placed in here, which are picked up by an asychronous
    188      * thread. The WorkerThread looks for events once a second and processes all
    189      * events accumulated during that time..
    190      */
    191     class WorkQueue {
    192         final ArrayList<LoggingEvent> queue = new ArrayList<>();
    193         Thread workerThread;
    194
    195         protected WorkQueue() {
    196             workerThread = new WorkerThread();
    197             workerThread.start();
    198         }
    199
    200         public final void enqueue(LoggingEvent event) {
    201             synchronized (mutex) {
    202                 queue.add(event);
    203                 mutex.notify();
    204             }
    205         }
    206
    207         public final void stop() {
    208             synchronized (mutex) {
    209                 workerThread.interrupt();
    210             }
    211         }
    212
    213         /**
    214          * The worker thread converts each queued event to a vector and forwards the
    215          * vector on to the UI.
    216          */
    217         private class WorkerThread extends Thread {
    218             public WorkerThread() {
    219                 super("Chainsaw-WorkerThread");
    220                 setDaemon(true);
    221                 setPriority(Thread.NORM_PRIORITY - 1);
    222             }
    223
    224             public void run() {
    225                 List<LoggingEvent> innerList = new ArrayList<>();
    226                 while (true) {
    227                     long timeStart = System.currentTimeMillis();
    228                     synchronized (mutex) {
    229                         try {
    230                             while ((queue.size() == 0) || (identifierExpression == null)) {
    231                                 setDataRate(0);
    232                                 mutex.wait();
    233                             }
    234                             if (queue.size() > 0) {
    235                                 innerList.addAll(queue);
    236                                 queue.clear();
    237                             }
    238                         } catch (InterruptedException ie) {
    239                         }
    240                     }
    241                     int size = innerList.size();
    242                     if (size > 0) {
    243                         Iterator<LoggingEvent> iter = innerList.iterator();
    244                         ChainsawEventBatch eventBatch = new ChainsawEventBatch();
    245                         while (iter.hasNext()) {
    246                             LoggingEvent e = iter.next();
    247                             // attempt to set the host name (without port), from
    248                             // remoteSourceInfo
    249                             // if 'hostname' property not provided
    250                             if (e.getProperty(Constants.HOSTNAME_KEY) == null) {
    251                                 String remoteHost = e
    252                                     .getProperty(ChainsawConstants.LOG4J_REMOTEHOST_KEY);
    253                                 if (remoteHost != null) {
    254                                     int colonIndex = remoteHost.indexOf(":");
    255                                     if (colonIndex == -1) {
    256                                         colonIndex = remoteHost.length();
    257                                     }
    258                                     e.setProperty(Constants.HOSTNAME_KEY, remoteHost.substring(0,
    259                                         colonIndex));
    260                                 }
    261                             }
    262                             for (Object o : customExpressionRules.entrySet()) {
    263                                 Map.Entry entry = (Map.Entry) o;
    264                                 Rule rule = (Rule) entry.getValue();
    265                                 if (rule.evaluate(e, null)) {
    266                                     eventBatch.addEvent((String) entry.getKey(), e);
    267                                 }
    268                             }
    269                             eventBatch.addEvent(getTabIdentifier(e), e);
    270                         }
    271                         dispatchEventBatch(eventBatch);
    272                         innerList.clear();
    273                     }
    274                     if (getQueueInterval() > 1000) {
    275                         try {
    276                             synchronized (this) {
    277                                 wait(getQueueInterval());
    278                             }
    279                         } catch (InterruptedException ie) {
    280                         }
    281                     } else {
    282                         Thread.yield();
    283                     }
    284                     if (size == 0) {
    285                         setDataRate(0.0);
    286                     } else {
    287                         long timeEnd = System.currentTimeMillis();
    288                         long diffInSeconds = (timeEnd - timeStart) / 1000;
    289                         double rate = (((double) size) / diffInSeconds);
    290                         setDataRate(rate);
    291                     }
    292                 }
    293             }
    294
    295             /**
    296              * Dispatches the event batches contents to all the interested parties by
    297              * iterating over each identifier and dispatching the
    298              * ChainsawEventBatchEntry object to each listener that is interested.
    299              *
    300              * @param eventBatch
    301              */
    302             private void dispatchEventBatch(ChainsawEventBatch eventBatch) {
    303                 EventBatchListener[] listeners = listenerList
    304                     .getListeners(EventBatchListener.class);
    305                 for (Iterator<String> iter = eventBatch.identifierIterator(); iter.hasNext(); ) {
    306                     String identifier = iter.next();
    307                     List<LoggingEvent> eventList = null;
    308                     for (EventBatchListener listener : listeners) {
    309                         if ((listener.getInterestedIdentifier() == null)
    310                             || listener.getInterestedIdentifier().equals(identifier)) {
    311                             if (eventList == null) {
    312                                 eventList = eventBatch.entrySet(identifier);
    313                             }
    314                             listener.receiveEventBatch(identifier, eventList);
    315                         }
    316                     }
    317                 }
    318             }
    319         }
    320     }
    321 }