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 org.apache.logging.log4j.simple.SimpleLogger;
020    import org.apache.logging.log4j.spi.AbstractLogger;
021    import org.apache.logging.log4j.Level;
022    import org.apache.logging.log4j.Marker;
023    import org.apache.logging.log4j.message.Message;
024    import org.apache.logging.log4j.util.PropertiesUtil;
025    
026    import java.util.ArrayList;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Queue;
030    import java.util.concurrent.ConcurrentLinkedQueue;
031    import java.util.concurrent.CopyOnWriteArrayList;
032    import java.util.concurrent.locks.ReentrantLock;
033    import java.util.concurrent.locks.ReentrantReadWriteLock;
034    
035    /**
036     * Mechanism to record events that occur in the logging system.
037     */
038    public final class StatusLogger extends AbstractLogger {
039    
040        /**
041         * System property that can be configured with the number of entries in the queue. Once the limit
042         * is reached older entries will be removed as new entries are added.
043         */
044        public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries";
045    
046        private static final String NOT_AVAIL = "?";
047    
048        private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");
049    
050        private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200);
051    
052        // private static final String FQCN = AbstractLogger.class.getName();
053    
054        private static final StatusLogger STATUS_LOGGER = new StatusLogger();
055    
056        private final SimpleLogger logger;
057    
058        private final CopyOnWriteArrayList<StatusListener> listeners = new CopyOnWriteArrayList<StatusListener>();
059        private final ReentrantReadWriteLock listenersLock = new ReentrantReadWriteLock();
060    
061        private final Queue<StatusData> messages = new BoundedQueue<StatusData>(MAX_ENTRIES);
062        private final ReentrantLock msgLock = new ReentrantLock();
063    
064        private StatusLogger() {
065            this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, "", null, PROPS,
066                System.err);
067        }
068    
069        /**
070         * Retrieve the StatusLogger.
071         * @return The StatusLogger.
072         */
073        public static StatusLogger getLogger() {
074            return STATUS_LOGGER;
075        }
076    
077        public Level getLevel() {
078            return logger.getLevel();
079        }
080    
081        public void setLevel(final Level level) {
082            logger.setLevel(level);
083        }
084    
085        /**
086         * Register a new listener.
087         * @param listener The StatusListener to register.
088         */
089        public void registerListener(final StatusListener listener) {
090            listenersLock.writeLock().lock();
091            try {
092                listeners.add(listener);
093            } finally {
094                listenersLock.writeLock().unlock();
095            }
096        }
097    
098        /**
099         * Remove a StatusListener.
100         * @param listener The StatusListener to remove.
101         */
102        public void removeListener(final StatusListener listener) {
103            listenersLock.writeLock().lock();
104            try {
105                listeners.remove(listener);
106            } finally {
107                listenersLock.writeLock().unlock();
108            }
109        }
110    
111        /**
112         * Returns a thread safe Iterator for the StatusListener.
113         * @return An Iterator for the list of StatusListeners.
114         */
115        public Iterator<StatusListener> getListeners() {
116            return listeners.iterator();
117        }
118    
119        /**
120         * Clears the list of status events and listeners.
121         */
122        public void reset() {
123            listeners.clear();
124            clear();
125        }
126    
127        /**
128         * Returns a List of all events as StatusData objects.
129         * @return The list of StatusData objects.
130         */
131        public List<StatusData> getStatusData() {
132            msgLock.lock();
133            try {
134                return new ArrayList<StatusData>(messages);
135            } finally {
136                msgLock.unlock();
137            }
138        }
139    
140        /**
141         * Clears the list of status events.
142         */
143        public void clear() {
144            msgLock.lock();
145            try {
146                messages.clear();
147            } finally {
148                msgLock.unlock();
149            }
150        }
151    
152    
153        /**
154         * Add an event.
155         * @param marker The Marker
156         * @param fqcn   The fully qualified class name of the <b>caller</b>
157         * @param level  The logging level
158         * @param msg    The message associated with the event.
159         * @param t      A Throwable or null.
160         */
161        @Override
162        public void log(final Marker marker, final String fqcn, final Level level, final Message msg, final Throwable t) {
163            StackTraceElement element = null;
164            if (fqcn != null) {
165                element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
166            }
167            final StatusData data = new StatusData(element, level, msg, t);
168            msgLock.lock();
169            try {
170                messages.add(data);
171            } finally {
172                msgLock.unlock();
173            }
174            if (listeners.size() > 0) {
175                for (final StatusListener listener : listeners) {
176                    listener.log(data);
177                }
178            } else {
179                logger.log(marker, fqcn, level, msg, t);
180            }
181        }
182    
183        private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
184            if (fqcn == null) {
185                return null;
186            }
187            boolean next = false;
188            for (final StackTraceElement element : stackTrace) {
189                if (next) {
190                    return element;
191                }
192                final String className = element.getClassName();
193                if (fqcn.equals(className)) {
194                    next = true;
195                } else if (NOT_AVAIL.equals(className)) {
196                    break;
197                }
198            }
199            return null;
200        }
201    
202        @Override
203        protected boolean isEnabled(final Level level, final Marker marker, final String data) {
204            return isEnabled(level, marker);
205        }
206    
207        @Override
208        protected boolean isEnabled(final Level level, final Marker marker, final String data, final Throwable t) {
209            return isEnabled(level, marker);
210        }
211    
212        @Override
213        protected boolean isEnabled(final Level level, final Marker marker, final String data, final Object... p1) {
214            return isEnabled(level, marker);
215        }
216    
217        @Override
218        protected boolean isEnabled(final Level level, final Marker marker, final Object data, final Throwable t) {
219            return isEnabled(level, marker);
220        }
221    
222        @Override
223        protected boolean isEnabled(final Level level, final Marker marker, final Message data, final Throwable t) {
224            return isEnabled(level, marker);
225        }
226    
227        @Override
228        public boolean isEnabled(final Level level, final Marker marker) {
229            if (listeners.size() > 0) {
230                return true;
231            }
232            switch (level) {
233                case FATAL:
234                    return logger.isFatalEnabled(marker);
235                case TRACE:
236                    return logger.isTraceEnabled(marker);
237                case DEBUG:
238                    return logger.isDebugEnabled(marker);
239                case INFO:
240                    return logger.isInfoEnabled(marker);
241                case WARN:
242                    return logger.isWarnEnabled(marker);
243                case ERROR:
244                    return logger.isErrorEnabled(marker);
245            }
246            return false;
247        }
248    
249        /**
250         * Queue for status events.
251         * @param <E> Object type to be stored in the queue.
252         */
253        private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
254    
255            private static final long serialVersionUID = -3945953719763255337L;
256    
257            private final int size;
258    
259            public BoundedQueue(final int size) {
260                this.size = size;
261            }
262    
263            @Override
264            public boolean add(final E object) {
265                while (messages.size() > size) {
266                    messages.poll();
267                }
268                return super.add(object);
269            }
270        }
271    }