    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements. See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache license, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License. You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the license for the specific language governing permissions and
    015     * limitations under the license.
    016     */
    017    package org.apache.logging.log4j.status;
    018
    019    import java.io.Closeable;
    020    import java.io.IOException;
    021    import java.util.ArrayList;
    022    import java.util.Collection;
    023    import java.util.List;
    024    import java.util.Queue;
    025    import java.util.concurrent.ConcurrentLinkedQueue;
    026    import java.util.concurrent.CopyOnWriteArrayList;
    027    import java.util.concurrent.locks.Lock;
    028    import java.util.concurrent.locks.ReadWriteLock;
    029    import java.util.concurrent.locks.ReentrantLock;
    030    import java.util.concurrent.locks.ReentrantReadWriteLock;
    031
    032    import org.apache.logging.log4j.Level;
    033    import org.apache.logging.log4j.Marker;
    034    import org.apache.logging.log4j.message.Message;
    035    import org.apache.logging.log4j.simple.SimpleLogger;
    036    import org.apache.logging.log4j.spi.AbstractLogger;
    037    import org.apache.logging.log4j.util.PropertiesUtil;
    038    import org.apache.logging.log4j.util.Strings;
    039
    040    /**
    041     * Records events that occur in the logging system.
    042     */
    043    public final class StatusLogger extends AbstractLogger {
    044
    045        private static final long serialVersionUID = 2L;
    046
    047        /**
    048         * System property that can be configured with the number of entries in the queue. Once the limit
    049         * is reached older entries will be removed as new entries are added.
    050         */
    051        public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries";
    052
    053        private static final String NOT_AVAIL = "?";
    054
    055        private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");
    056
    057        private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200);
    058
    059        private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty("log4j2.StatusLogger.level");
    060
    061        private static final StatusLogger STATUS_LOGGER = new StatusLogger();
    062
    063        private final SimpleLogger logger;
    064
    065        private final Collection<StatusListener> listeners = new CopyOnWriteArrayList<StatusListener>();
    066
    067        @SuppressWarnings("NonSerializableFieldInSerializableClass") // ReentrantReadWriteLock is Serializable
    068        private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
    069
    070        private final Queue<StatusData> messages = new BoundedQueue<StatusData>(MAX_ENTRIES);
    071
    072        @SuppressWarnings("NonSerializableFieldInSerializableClass") // ReentrantLock is Serializable
    073        private final Lock msgLock = new ReentrantLock();
    074
    075        private int listenersLevel;
    076
    077        private StatusLogger() {
    078            this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, Strings.EMPTY, null, PROPS,
    079                System.err);
    080            this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
    081        }
    082
    083        /**
    084         * Retrieve the StatusLogger.
    085         * @return The StatusLogger.
    086         */
    087        public static StatusLogger getLogger() {
    088            return STATUS_LOGGER;
    089        }
    090
    091        public void setLevel(final Level level) {
    092            logger.setLevel(level);
    093        }
    094
    095        /**
    096         * Registers a new listener.
    097         * @param listener The StatusListener to register.
    098         */
    099        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    }