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