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.io.Closeable;
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.List;
024import java.util.Queue;
025import java.util.concurrent.ConcurrentLinkedQueue;
026import java.util.concurrent.CopyOnWriteArrayList;
027import java.util.concurrent.locks.Lock;
028import java.util.concurrent.locks.ReadWriteLock;
029import java.util.concurrent.locks.ReentrantLock;
030import java.util.concurrent.locks.ReentrantReadWriteLock;
031
032import org.apache.logging.log4j.Level;
033import org.apache.logging.log4j.Marker;
034import org.apache.logging.log4j.message.Message;
035import org.apache.logging.log4j.simple.SimpleLogger;
036import org.apache.logging.log4j.spi.AbstractLogger;
037import org.apache.logging.log4j.util.PropertiesUtil;
038import org.apache.logging.log4j.util.Strings;
039
040/**
041 * Mechanism to record events that occur in the logging system.
042 */
043public 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    private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
067
068    private final Queue<StatusData> messages = new BoundedQueue<StatusData>(MAX_ENTRIES);
069    private final Lock msgLock = new ReentrantLock();
070
071    private int listenersLevel;
072
073    private StatusLogger() {
074        this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, Strings.EMPTY, null, PROPS,
075            System.err);
076        this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
077    }
078
079    /**
080     * Retrieve the StatusLogger.
081     * @return The StatusLogger.
082     */
083    public static StatusLogger getLogger() {
084        return STATUS_LOGGER;
085    }
086
087    public void setLevel(final Level level) {
088        logger.setLevel(level);
089    }
090
091    /**
092     * Register a new listener.
093     * @param listener The StatusListener to register.
094     */
095    public void registerListener(final StatusListener listener) {
096        listenersLock.writeLock().lock();
097        try {
098            listeners.add(listener);
099            final Level lvl = listener.getStatusLevel();
100            if (listenersLevel < lvl.intLevel()) {
101                listenersLevel = lvl.intLevel();
102            }
103        } finally {
104            listenersLock.writeLock().unlock();
105        }
106    }
107
108    /**
109     * Remove a StatusListener.
110     * @param listener The StatusListener to remove.
111     */
112    public void removeListener(final StatusListener listener) {
113        closeSilently(listener);
114        listenersLock.writeLock().lock();
115        try {
116            listeners.remove(listener);
117            int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
118            for (final StatusListener l : listeners) {
119                final 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 Iterable for the StatusListener.
132     * @return An Iterable for the list of StatusListeners.
133     */
134    public Iterable<StatusListener> getListeners() {
135        return listeners;
136    }
137
138    /**
139     * Clears the list of status events and listeners.
140     */
141    public void reset() {
142        listenersLock.writeLock().lock();
143        try {
144            for (final StatusListener listener : listeners) {
145                closeSilently(listener);
146            }
147        } finally {
148            listeners.clear();
149            listenersLock.writeLock().unlock();
150            // note this should certainly come after the unlock to avoid unnecessary nested locking
151            clear();
152        }
153    }
154
155    private static void closeSilently(final Closeable resource) {
156        try {
157            resource.close();
158        } catch (final IOException ignored) {
159        }
160    }
161
162    /**
163     * Returns a List of all events as StatusData objects.
164     * @return The list of StatusData objects.
165     */
166    public List<StatusData> getStatusData() {
167        msgLock.lock();
168        try {
169            return new ArrayList<StatusData>(messages);
170        } finally {
171            msgLock.unlock();
172        }
173    }
174
175    /**
176     * Clears the list of status events.
177     */
178    public void clear() {
179        msgLock.lock();
180        try {
181            messages.clear();
182        } finally {
183            msgLock.unlock();
184        }
185    }
186
187    @Override
188    public Level getLevel() {
189        return logger.getLevel();
190    }
191
192    /**
193     * Add an event.
194     * @param marker The Marker
195     * @param fqcn   The fully qualified class name of the <b>caller</b>
196     * @param level  The logging level
197     * @param msg    The message associated with the event.
198     * @param t      A Throwable or null.
199     */
200    @Override
201    public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg, final Throwable t) {
202        StackTraceElement element = null;
203        if (fqcn != null) {
204            element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
205        }
206        final StatusData data = new StatusData(element, level, msg, t);
207        msgLock.lock();
208        try {
209            messages.add(data);
210        } finally {
211            msgLock.unlock();
212        }
213        if (listeners.size() > 0) {
214            for (final StatusListener listener : listeners) {
215                if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) {
216                    listener.log(data);
217                }
218            }
219        } else {
220            logger.logMessage(fqcn, level, marker, msg, t);
221        }
222    }
223
224    private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
225        if (fqcn == null) {
226            return null;
227        }
228        boolean next = false;
229        for (final StackTraceElement element : stackTrace) {
230            final String className = element.getClassName();
231            if (next && !fqcn.equals(className)) {
232                return element;
233            }
234            if (fqcn.equals(className)) {
235                next = true;
236            } else if (NOT_AVAIL.equals(className)) {
237                break;
238            }
239        }
240        return null;
241    }
242
243    @Override
244    public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
245        return isEnabled(level, marker);
246    }
247
248    @Override
249    public boolean isEnabled(final Level level, final Marker marker, final String message) {
250        return isEnabled(level, marker);
251    }
252
253    @Override
254    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {
255        return isEnabled(level, marker);
256    }
257
258    @Override
259    public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) {
260        return isEnabled(level, marker);
261    }
262
263    @Override
264    public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) {
265        return isEnabled(level, marker);
266    }
267
268    @Override
269    public boolean isEnabled(final Level level, final Marker marker) {
270        if (listeners.size() > 0) {
271            return listenersLevel >= level.intLevel();
272        }
273        return logger.isEnabled(level, marker);
274    }
275
276    /**
277     * Queue for status events.
278     * @param <E> Object type to be stored in the queue.
279     */
280    private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
281
282        private static final long serialVersionUID = -3945953719763255337L;
283
284        private final int size;
285
286        public BoundedQueue(final int size) {
287            this.size = size;
288        }
289
290        @Override
291        public boolean add(final E object) {
292            while (messages.size() > size) {
293                messages.poll();
294            }
295            return super.add(object);
296        }
297    }
298}