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.message.MessageFactory;
036import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory;
037import org.apache.logging.log4j.simple.SimpleLogger;
038import org.apache.logging.log4j.spi.AbstractLogger;
039import org.apache.logging.log4j.util.PropertiesUtil;
040import org.apache.logging.log4j.util.Strings;
041
042/**
043 * Records events that occur in the logging system.
044 */
045public final class StatusLogger extends AbstractLogger {
046
047    /**
048     * System property that can be configured with the number of entries in the queue. Once the limit is reached older
049     * 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 long serialVersionUID = 2L;
054
055    private static final String NOT_AVAIL = "?";
056
057    private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");
058
059    private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200);
060
061    private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty("log4j2.StatusLogger.level");
062
063    // LOG4J2-1176: normal parameterized message remembers param object, causing memory leaks.
064    private static final StatusLogger STATUS_LOGGER = new StatusLogger(StatusLogger.class.getName(),
065            ParameterizedNoReferenceMessageFactory.INSTANCE);
066
067    private final SimpleLogger logger;
068
069    private final Collection<StatusListener> listeners = new CopyOnWriteArrayList<>();
070
071    @SuppressWarnings("NonSerializableFieldInSerializableClass")
072    // ReentrantReadWriteLock is Serializable
073    private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
074
075    private final Queue<StatusData> messages = new BoundedQueue<>(MAX_ENTRIES);
076
077    @SuppressWarnings("NonSerializableFieldInSerializableClass")
078    // ReentrantLock is Serializable
079    private final Lock msgLock = new ReentrantLock();
080
081    private int listenersLevel;
082
083    private StatusLogger(final String name, final MessageFactory messageFactory) {
084        super(name, messageFactory);
085        this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, Strings.EMPTY,
086                messageFactory, PROPS, System.err);
087        this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
088    }
089
090    /**
091     * Retrieve the StatusLogger.
092     *
093     * @return The StatusLogger.
094     */
095    public static StatusLogger getLogger() {
096        return STATUS_LOGGER;
097    }
098
099    public void setLevel(final Level level) {
100        logger.setLevel(level);
101    }
102
103    /**
104     * Registers a new listener.
105     *
106     * @param listener The StatusListener to register.
107     */
108    public void registerListener(final StatusListener listener) {
109        listenersLock.writeLock().lock();
110        try {
111            listeners.add(listener);
112            final Level lvl = listener.getStatusLevel();
113            if (listenersLevel < lvl.intLevel()) {
114                listenersLevel = lvl.intLevel();
115            }
116        } finally {
117            listenersLock.writeLock().unlock();
118        }
119    }
120
121    /**
122     * Removes a StatusListener.
123     *
124     * @param listener The StatusListener to remove.
125     */
126    public void removeListener(final StatusListener listener) {
127        closeSilently(listener);
128        listenersLock.writeLock().lock();
129        try {
130            listeners.remove(listener);
131            int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
132            for (final StatusListener statusListener : listeners) {
133                final int level = statusListener.getStatusLevel().intLevel();
134                if (lowest < level) {
135                    lowest = level;
136                }
137            }
138            listenersLevel = lowest;
139        } finally {
140            listenersLock.writeLock().unlock();
141        }
142    }
143
144    public void updateListenerLevel(final Level status) {
145        if (status.intLevel() > listenersLevel) {
146            listenersLevel = status.intLevel();
147        }
148    }
149
150    /**
151     * Returns a thread safe Iterable for the StatusListener.
152     *
153     * @return An Iterable for the list of StatusListeners.
154     */
155    public Iterable<StatusListener> getListeners() {
156        return listeners;
157    }
158
159    /**
160     * Clears the list of status events and listeners.
161     */
162    public void reset() {
163        listenersLock.writeLock().lock();
164        try {
165            for (final StatusListener listener : listeners) {
166                closeSilently(listener);
167            }
168        } finally {
169            listeners.clear();
170            listenersLock.writeLock().unlock();
171            // note this should certainly come after the unlock to avoid unnecessary nested locking
172            clear();
173        }
174    }
175
176    private static void closeSilently(final Closeable resource) {
177        try {
178            resource.close();
179        } catch (final IOException ignored) {
180            // ignored
181        }
182    }
183
184    /**
185     * Returns a List of all events as StatusData objects.
186     *
187     * @return The list of StatusData objects.
188     */
189    public List<StatusData> getStatusData() {
190        msgLock.lock();
191        try {
192            return new ArrayList<>(messages);
193        } finally {
194            msgLock.unlock();
195        }
196    }
197
198    /**
199     * Clears the list of status events.
200     */
201    public void clear() {
202        msgLock.lock();
203        try {
204            messages.clear();
205        } finally {
206            msgLock.unlock();
207        }
208    }
209
210    @Override
211    public Level getLevel() {
212        return logger.getLevel();
213    }
214
215    /**
216     * Adds an event.
217     *
218     * @param marker The Marker
219     * @param fqcn The fully qualified class name of the <b>caller</b>
220     * @param level The logging level
221     * @param msg The message associated with the event.
222     * @param t A Throwable or null.
223     */
224    @Override
225    public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg,
226            final Throwable t) {
227        StackTraceElement element = null;
228        if (fqcn != null) {
229            element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
230        }
231        final StatusData data = new StatusData(element, level, msg, t, null);
232        msgLock.lock();
233        try {
234            messages.add(data);
235        } finally {
236            msgLock.unlock();
237        }
238        if (listeners.size() > 0) {
239            for (final StatusListener listener : listeners) {
240                if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) {
241                    listener.log(data);
242                }
243            }
244        } else {
245            logger.logMessage(fqcn, level, marker, msg, t);
246        }
247    }
248
249    private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
250        if (fqcn == null) {
251            return null;
252        }
253        boolean next = false;
254        for (final StackTraceElement element : stackTrace) {
255            final String className = element.getClassName();
256            if (next && !fqcn.equals(className)) {
257                return element;
258            }
259            if (fqcn.equals(className)) {
260                next = true;
261            } else if (NOT_AVAIL.equals(className)) {
262                break;
263            }
264        }
265        return null;
266    }
267
268    @Override
269    public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
270        return isEnabled(level, marker);
271    }
272
273    @Override
274    public boolean isEnabled(final Level level, final Marker marker, final String message) {
275        return isEnabled(level, marker);
276    }
277
278    @Override
279    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {
280        return isEnabled(level, marker);
281    }
282
283    @Override
284    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) {
285        return isEnabled(level, marker);
286    }
287
288    @Override
289    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
290            final Object p1) {
291        return isEnabled(level, marker);
292    }
293
294    @Override
295    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
296            final Object p1, final Object p2) {
297        return isEnabled(level, marker);
298    }
299
300    @Override
301    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
302            final Object p1, final Object p2, final Object p3) {
303        return isEnabled(level, marker);
304    }
305
306    @Override
307    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
308            final Object p1, final Object p2, final Object p3,
309            final Object p4) {
310        return isEnabled(level, marker);
311    }
312
313    @Override
314    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
315            final Object p1, final Object p2, final Object p3,
316            final Object p4, final Object p5) {
317        return isEnabled(level, marker);
318    }
319
320    @Override
321    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
322            final Object p1, final Object p2, final Object p3,
323            final Object p4, final Object p5, final Object p6) {
324        return isEnabled(level, marker);
325    }
326
327    @Override
328    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
329            final Object p1, final Object p2, final Object p3,
330            final Object p4, final Object p5, final Object p6,
331            final Object p7) {
332        return isEnabled(level, marker);
333    }
334
335    @Override
336    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
337            final Object p1, final Object p2, final Object p3,
338            final Object p4, final Object p5, final Object p6,
339            final Object p7, final Object p8) {
340        return isEnabled(level, marker);
341    }
342
343    @Override
344    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
345            final Object p1, final Object p2, final Object p3,
346            final Object p4, final Object p5, final Object p6,
347            final Object p7, final Object p8, final Object p9) {
348        return isEnabled(level, marker);
349    }
350
351    @Override
352    public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) {
353        return isEnabled(level, marker);
354    }
355
356    @Override
357    public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) {
358        return isEnabled(level, marker);
359    }
360
361    @Override
362    public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) {
363        return isEnabled(level, marker);
364    }
365
366    @Override
367    public boolean isEnabled(final Level level, final Marker marker) {
368        if (listeners.size() > 0) {
369            return listenersLevel >= level.intLevel();
370        }
371        return logger.isEnabled(level, marker);
372    }
373
374    /**
375     * Queues for status events.
376     *
377     * @param <E> Object type to be stored in the queue.
378     */
379    private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
380
381        private static final long serialVersionUID = -3945953719763255337L;
382
383        private final int size;
384
385        BoundedQueue(final int size) {
386            this.size = size;
387        }
388
389        @Override
390        public boolean add(final E object) {
391            super.add(object);
392            while (messages.size() > size) {
393                messages.poll();
394            }
395            return size > 0;
396        }
397    }
398}