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