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