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