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.logging.log4j.status;
    18
    19  import java.io.Closeable;
    20  import java.io.IOException;
    21  import java.util.ArrayList;
    22  import java.util.Collection;
    23  import java.util.List;
    24  import java.util.Queue;
    25  import java.util.concurrent.ConcurrentLinkedQueue;
    26  import java.util.concurrent.CopyOnWriteArrayList;
    27  import java.util.concurrent.locks.Lock;
    28  import java.util.concurrent.locks.ReadWriteLock;
    29  import java.util.concurrent.locks.ReentrantLock;
    30  import java.util.concurrent.locks.ReentrantReadWriteLock;
    31
    32  import org.apache.logging.log4j.Level;
    33  import org.apache.logging.log4j.Marker;
    34  import org.apache.logging.log4j.message.Message;
    35  import org.apache.logging.log4j.simple.SimpleLogger;
    36  import org.apache.logging.log4j.spi.AbstractLogger;
    37  import org.apache.logging.log4j.util.PropertiesUtil;
    38  import org.apache.logging.log4j.util.Strings;
    39
    40  /**
    41   * Records events that occur in the logging system.
    42   */
    43  public final class StatusLogger extends AbstractLogger {
    44
    45      private static final long serialVersionUID = 2L;
    46
    47      /**
    48       * System property that can be configured with the number of entries in the queue. Once the limit
    49       * is reached older entries will be removed as new entries are added.
    50       */
    51      public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries";
    52
    53      private static final String NOT_AVAIL = "?";
    54
    55      private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");
    56
    57      private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200);
    58
    59      private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty("log4j2.StatusLogger.level");
    60
    61      private static final StatusLogger STATUS_LOGGER = new StatusLogger();
    62
    63      private final SimpleLogger logger;
    64
    65      private final Collection<StatusListener> listeners = new CopyOnWriteArrayList<StatusListener>();
    66
    67      @SuppressWarnings("NonSerializableFieldInSerializableClass") // ReentrantReadWriteLock is Serializable
    68      private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
    69
    70      private final Queue<StatusData> messages = new BoundedQueue<StatusData>(MAX_ENTRIES);
    71
    72      @SuppressWarnings("NonSerializableFieldInSerializableClass") // ReentrantLock is Serializable
    73      private final Lock msgLock = new ReentrantLock();
    74
    75      private int listenersLevel;
    76
    77      private StatusLogger() {
    78          this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, Strings.EMPTY, null, PROPS,
    79              System.err);
    80          this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
    81      }
    82
    83      /**
    84       * Retrieve the StatusLogger.
    85       * @return The StatusLogger.
    86       */
    87      public static StatusLogger getLogger() {
    88          return STATUS_LOGGER;
    89      }
    90
    91      public void setLevel(final Level level) {
    92          logger.setLevel(level);
    93      }
    94
    95      /**
    96       * Registers a new listener.
    97       * @param listener The StatusListener to register.
    98       */
    99      public void registerListener(final StatusListener listener) {
    100         listenersLock.writeLock().lock();
    101         try {
    102             listeners.add(listener);
    103             final Level lvl = listener.getStatusLevel();
    104             if (listenersLevel < lvl.intLevel()) {
    105                 listenersLevel = lvl.intLevel();
    106             }
    107         } finally {
    108             listenersLock.writeLock().unlock();
    109         }
    110     }
    111
    112     /**
    113      * Removes a StatusListener.
    114      * @param listener The StatusListener to remove.
    115      */
    116     public void removeListener(final StatusListener listener) {
    117         closeSilently(listener);
    118         listenersLock.writeLock().lock();
    119         try {
    120             listeners.remove(listener);
    121             int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
    122             for (final StatusListener statusListener : listeners) {
    123                 final int level = statusListener.getStatusLevel().intLevel();
    124                 if (lowest < level) {
    125                     lowest = level;
    126                 }
    127             }
    128             listenersLevel = lowest;
    129         } finally {
    130             listenersLock.writeLock().unlock();
    131         }
    132     }
    133
    134     /**
    135      * Returns a thread safe Iterable for the StatusListener.
    136      * @return An Iterable for the list of StatusListeners.
    137      */
    138     public Iterable<StatusListener> getListeners() {
    139         return listeners;
    140     }
    141
    142     /**
    143      * Clears the list of status events and listeners.
    144      */
    145     public void reset() {
    146         listenersLock.writeLock().lock();
    147         try {
    148             for (final StatusListener listener : listeners) {
    149                 closeSilently(listener);
    150             }
    151         } finally {
    152             listeners.clear();
    153             listenersLock.writeLock().unlock();
    154             // note this should certainly come after the unlock to avoid unnecessary nested locking
    155             clear();
    156         }
    157     }
    158
    159     private static void closeSilently(final Closeable resource) {
    160         try {
    161             resource.close();
    162         } catch (final IOException ignored) {
    163         }
    164     }
    165
    166     /**
    167      * Returns a List of all events as StatusData objects.
    168      * @return The list of StatusData objects.
    169      */
    170     public List<StatusData> getStatusData() {
    171         msgLock.lock();
    172         try {
    173             return new ArrayList<StatusData>(messages);
    174         } finally {
    175             msgLock.unlock();
    176         }
    177     }
    178
    179     /**
    180      * Clears the list of status events.
    181      */
    182     public void clear() {
    183         msgLock.lock();
    184         try {
    185             messages.clear();
    186         } finally {
    187             msgLock.unlock();
    188         }
    189     }
    190
    191     @Override
    192     public Level getLevel() {
    193         return logger.getLevel();
    194     }
    195
    196     /**
    197      * Adds an event.
    198      * @param marker The Marker
    199      * @param fqcn   The fully qualified class name of the <b>caller</b>
    200      * @param level  The logging level
    201      * @param msg    The message associated with the event.
    202      * @param t      A Throwable or null.
    203      */
    204     @Override
    205     public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg, final Throwable t) {
    206         StackTraceElement element = null;
    207         if (fqcn != null) {
    208             element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
    209         }
    210         final StatusData data = new StatusData(element, level, msg, t);
    211         msgLock.lock();
    212         try {
    213             messages.add(data);
    214         } finally {
    215             msgLock.unlock();
    216         }
    217         if (listeners.size() > 0) {
    218             for (final StatusListener listener : listeners) {
    219                 if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) {
    220                     listener.log(data);
    221                 }
    222             }
    223         } else {
    224             logger.logMessage(fqcn, level, marker, msg, t);
    225         }
    226     }
    227
    228     private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
    229         if (fqcn == null) {
    230             return null;
    231         }
    232         boolean next = false;
    233         for (final StackTraceElement element : stackTrace) {
    234             final String className = element.getClassName();
    235             if (next && !fqcn.equals(className)) {
    236                 return element;
    237             }
    238             if (fqcn.equals(className)) {
    239                 next = true;
    240             } else if (NOT_AVAIL.equals(className)) {
    241                 break;
    242             }
    243         }
    244         return null;
    245     }
    246
    247     @Override
    248     public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
    249         return isEnabled(level, marker);
    250     }
    251
    252     @Override
    253     public boolean isEnabled(final Level level, final Marker marker, final String message) {
    254         return isEnabled(level, marker);
    255     }
    256
    257     @Override
    258     public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {
    259         return isEnabled(level, marker);
    260     }
    261
    262     @Override
    263     public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) {
    264         return isEnabled(level, marker);
    265     }
    266
    267     @Override
    268     public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) {
    269         return isEnabled(level, marker);
    270     }
    271
    272     @Override
    273     public boolean isEnabled(final Level level, final Marker marker) {
    274         if (listeners.size() > 0) {
    275             return listenersLevel >= level.intLevel();
    276         }
    277         return logger.isEnabled(level, marker);
    278     }
    279
    280     /**
    281      * Queues for status events.
    282      * @param <E> Object type to be stored in the queue.
    283      */
    284     private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
    285
    286         private static final long serialVersionUID = -3945953719763255337L;
    287
    288         private final int size;
    289
    290         public BoundedQueue(final int size) {
    291             this.size = size;
    292         }
    293
    294         @Override
    295         public boolean add(final E object) {
    296             while (messages.size() > size) {
    297                 messages.poll();
    298             }
    299             return super.add(object);
    300         }
    301     }
    302 }