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