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    }