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()) {
273            logger.logMessage(fqcn, level, marker, msg, t);
274        } else {
275            if (listeners.size() > 0) {
276                for (final StatusListener listener : listeners) {
277                    if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) {
278                        listener.log(data);
279                    }
280                }
281            } else {
282                logger.logMessage(fqcn, level, marker, msg, t);
283            }
284        }
285    }
286
287    private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
288        if (fqcn == null) {
289            return null;
290        }
291        boolean next = false;
292        for (final StackTraceElement element : stackTrace) {
293            final String className = element.getClassName();
294            if (next && !fqcn.equals(className)) {
295                return element;
296            }
297            if (fqcn.equals(className)) {
298                next = true;
299            } else if (NOT_AVAIL.equals(className)) {
300                break;
301            }
302        }
303        return null;
304    }
305
306    @Override
307    public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
308        return isEnabled(level, marker);
309    }
310
311    @Override
312    public boolean isEnabled(final Level level, final Marker marker, final String message) {
313        return isEnabled(level, marker);
314    }
315
316    @Override
317    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {
318        return isEnabled(level, marker);
319    }
320
321    @Override
322    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) {
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) {
329        return isEnabled(level, marker);
330    }
331
332    @Override
333    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
334            final Object p1, final Object p2) {
335        return isEnabled(level, marker);
336    }
337
338    @Override
339    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
340            final Object p1, final Object p2, final Object p3) {
341        return isEnabled(level, marker);
342    }
343
344    @Override
345    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
346            final Object p1, final Object p2, final Object p3,
347            final Object p4) {
348        return isEnabled(level, marker);
349    }
350
351    @Override
352    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
353            final Object p1, final Object p2, final Object p3,
354            final Object p4, final Object p5) {
355        return isEnabled(level, marker);
356    }
357
358    @Override
359    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
360            final Object p1, final Object p2, final Object p3,
361            final Object p4, final Object p5, final Object p6) {
362        return isEnabled(level, marker);
363    }
364
365    @Override
366    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
367            final Object p1, final Object p2, final Object p3,
368            final Object p4, final Object p5, final Object p6,
369            final Object p7) {
370        return isEnabled(level, marker);
371    }
372
373    @Override
374    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
375            final Object p1, final Object p2, final Object p3,
376            final Object p4, final Object p5, final Object p6,
377            final Object p7, final Object p8) {
378        return isEnabled(level, marker);
379    }
380
381    @Override
382    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
383            final Object p1, final Object p2, final Object p3,
384            final Object p4, final Object p5, final Object p6,
385            final Object p7, final Object p8, final Object p9) {
386        return isEnabled(level, marker);
387    }
388
389    @Override
390    public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) {
391        return isEnabled(level, marker);
392    }
393
394    @Override
395    public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) {
396        return isEnabled(level, marker);
397    }
398
399    @Override
400    public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) {
401        return isEnabled(level, marker);
402    }
403
404    @Override
405    public boolean isEnabled(final Level level, final Marker marker) {
406        // LOG4J2-1813 if system property "log4j2.debug" is defined, all status logging is enabled
407        if (isDebugPropertyEnabled()) {
408            return true;
409        }
410        if (listeners.size() > 0) {
411            return listenersLevel >= level.intLevel();
412        }
413        return logger.isEnabled(level, marker);
414    }
415
416    /**
417     * Queues for status events.
418     *
419     * @param <E> Object type to be stored in the queue.
420     */
421    private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
422
423        private static final long serialVersionUID = -3945953719763255337L;
424
425        private final int size;
426
427        BoundedQueue(final int size) {
428            this.size = size;
429        }
430
431        @Override
432        public boolean add(final E object) {
433            super.add(object);
434            while (messages.size() > size) {
435                messages.poll();
436            }
437            return size > 0;
438        }
439    }
440}