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