1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache license, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the license for the specific language governing permissions and
15  * limitations under the license.
16  */

17 package org.apache.logging.log4j.status;
18
19 import java.io.Closeable;
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.List;
24 import java.util.Queue;
25 import java.util.concurrent.ConcurrentLinkedQueue;
26 import java.util.concurrent.CopyOnWriteArrayList;
27 import java.util.concurrent.locks.Lock;
28 import java.util.concurrent.locks.ReadWriteLock;
29 import java.util.concurrent.locks.ReentrantLock;
30 import java.util.concurrent.locks.ReentrantReadWriteLock;
31
32 import org.apache.logging.log4j.Level;
33 import org.apache.logging.log4j.Marker;
34 import org.apache.logging.log4j.message.Message;
35 import org.apache.logging.log4j.message.MessageFactory;
36 import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory;
37 import org.apache.logging.log4j.simple.SimpleLogger;
38 import org.apache.logging.log4j.simple.SimpleLoggerContext;
39 import org.apache.logging.log4j.spi.AbstractLogger;
40 import org.apache.logging.log4j.util.Constants;
41 import org.apache.logging.log4j.util.PropertiesUtil;
42 import org.apache.logging.log4j.util.Strings;
43
44 /**
45  * Records events that occur in the logging system. By default, only error messages are logged to {@link System#err}.
46  * Normally, the Log4j StatusLogger is configured via the root {@code <Configuration status="LEVEL"/>} node in a Log4j
47  * configuration file. However, this can be overridden via a system property named
48  * {@value #DEFAULT_STATUS_LISTENER_LEVEL} and will work with any Log4j provider.
49  *
50  * @see SimpleLogger
51  * @see SimpleLoggerContext
52  */

53 public final class StatusLogger extends AbstractLogger {
54
55     /**
56      * System property that can be configured with the number of entries in the queue. Once the limit is reached older
57      * entries will be removed as new entries are added.
58      */

59     public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries";
60
61     /**
62      * System property that can be configured with the {@link Level} name to use as the default level for
63      * {@link StatusListener}s.
64      */

65     public static final String DEFAULT_STATUS_LISTENER_LEVEL = "log4j2.StatusLogger.level";
66
67     /**
68      * System property that can be configured with a date-time format string to use as the format for timestamps
69      * in the status logger output. See {@link java.text.SimpleDateFormat} for supported formats.
70      * @since 2.11.0
71      */

72     public static final String STATUS_DATE_FORMAT = "log4j2.StatusLogger.DateFormat";
73
74     private static final long serialVersionUID = 2L;
75
76     private static final String NOT_AVAIL = "?";
77
78     private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");
79
80     private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200);
81
82     private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty(DEFAULT_STATUS_LISTENER_LEVEL);
83
84     // LOG4J2-1176: normal parameterized message remembers param object, causing memory leaks.
85     private static final StatusLogger STATUS_LOGGER = new StatusLogger(StatusLogger.class.getName(),
86             ParameterizedNoReferenceMessageFactory.INSTANCE);
87
88     private final SimpleLogger logger;
89
90     private final Collection<StatusListener> listeners = new CopyOnWriteArrayList<>();
91
92     @SuppressWarnings("NonSerializableFieldInSerializableClass")
93     // ReentrantReadWriteLock is Serializable
94     private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
95
96     private final Queue<StatusData> messages = new BoundedQueue<>(MAX_ENTRIES);
97
98     @SuppressWarnings("NonSerializableFieldInSerializableClass")
99     // ReentrantLock is Serializable
100     private final Lock msgLock = new ReentrantLock();
101
102     private int listenersLevel;
103
104     /**
105      * Constructs the singleton instance for the STATUS_LOGGER constant.
106      * <p>
107      * This is now the logger level is set:
108      * </p>
109      * <ol>
110      * <li>If the property {@value Constants#LOG4J2_DEBUG} is {@code "true"}, then use {@link Level#TRACE}, otherwise,</li>
111      * <li>Use {@link Level#ERROR}</li>
112      * </ol>
113      * <p>
114      * This is now the listener level is set:
115      * </p>
116      * <ol>
117      * <li>If the property {@value #DEFAULT_STATUS_LISTENER_LEVEL} is set, then use <em>it</em>, otherwise,</li>
118      * <li>Use {@link Level#WARN}</li>
119      * </ol>
120      * <p>
121      * See:
122      * <ol>
123      * <li>LOG4J2-1813 Provide shorter and more intuitive way to switch on Log4j internal debug logging. If system property
124      * "log4j2.debug" is defined, print all status logging.</li>
125      * <li>LOG4J2-3340 StatusLogger's log Level cannot be changed as advertised.</li>
126      * </ol>
127      * </p>
128      * 
129      * @param name The logger name.
130      * @param messageFactory The message factory.
131      */

132     private StatusLogger(final String name, final MessageFactory messageFactory) {
133         super(name, messageFactory);
134         final String dateFormat = PROPS.getStringProperty(STATUS_DATE_FORMAT, Strings.EMPTY);
135         final boolean showDateTime = !Strings.isEmpty(dateFormat);
136         final Level loggerLevel = isDebugPropertyEnabled() ? Level.TRACE : Level.ERROR;
137         this.logger = new SimpleLogger("StatusLogger", loggerLevel, falsetrue, showDateTime, false, dateFormat, messageFactory, PROPS, System.err);
138         this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
139     }
140
141     // LOG4J2-1813 if system property "log4j2.debug" is defined, print all status logging
142     private boolean isDebugPropertyEnabled() {
143         return PropertiesUtil.getProperties().getBooleanProperty(Constants.LOG4J2_DEBUG, falsetrue);
144     }
145
146     /**
147      * Retrieve the StatusLogger.
148      *
149      * @return The StatusLogger.
150      */

151     public static StatusLogger getLogger() {
152         return STATUS_LOGGER;
153     }
154
155     public void setLevel(final Level level) {
156         logger.setLevel(level);
157     }
158
159     /**
160      * Registers a new listener.
161      *
162      * @param listener The StatusListener to register.
163      */

164     public void registerListener(final StatusListener listener) {
165         listenersLock.writeLock().lock();
166         try {
167             listeners.add(listener);
168             final Level lvl = listener.getStatusLevel();
169             if (listenersLevel < lvl.intLevel()) {
170                 listenersLevel = lvl.intLevel();
171             }
172         } finally {
173             listenersLock.writeLock().unlock();
174         }
175     }
176
177     /**
178      * Removes a StatusListener.
179      *
180      * @param listener The StatusListener to remove.
181      */

182     public void removeListener(final StatusListener listener) {
183         closeSilently(listener);
184         listenersLock.writeLock().lock();
185         try {
186             listeners.remove(listener);
187             int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
188             for (final StatusListener statusListener : listeners) {
189                 final int level = statusListener.getStatusLevel().intLevel();
190                 if (lowest < level) {
191                     lowest = level;
192                 }
193             }
194             listenersLevel = lowest;
195         } finally {
196             listenersLock.writeLock().unlock();
197         }
198     }
199
200     public void updateListenerLevel(final Level status) {
201         if (status.intLevel() > listenersLevel) {
202             listenersLevel = status.intLevel();
203         }
204     }
205
206     /**
207      * Returns a thread safe Iterable for the StatusListener.
208      *
209      * @return An Iterable for the list of StatusListeners.
210      */

211     public Iterable<StatusListener> getListeners() {
212         return listeners;
213     }
214
215     /**
216      * Clears the list of status events and listeners.
217      */

218     public void reset() {
219         listenersLock.writeLock().lock();
220         try {
221             for (final StatusListener listener : listeners) {
222                 closeSilently(listener);
223             }
224         } finally {
225             listeners.clear();
226             listenersLock.writeLock().unlock();
227             // note this should certainly come after the unlock to avoid unnecessary nested locking
228             clear();
229         }
230     }
231
232     private static void closeSilently(final Closeable resource) {
233         try {
234             resource.close();
235         } catch (final IOException ignored) {
236             // ignored
237         }
238     }
239
240     /**
241      * Returns a List of all events as StatusData objects.
242      *
243      * @return The list of StatusData objects.
244      */

245     public List<StatusData> getStatusData() {
246         msgLock.lock();
247         try {
248             return new ArrayList<>(messages);
249         } finally {
250             msgLock.unlock();
251         }
252     }
253
254     /**
255      * Clears the list of status events.
256      */

257     public void clear() {
258         msgLock.lock();
259         try {
260             messages.clear();
261         } finally {
262             msgLock.unlock();
263         }
264     }
265
266     @Override
267     public Level getLevel() {
268         return logger.getLevel();
269     }
270
271     /**
272      * Adds an event.
273      *
274      * @param marker The Marker
275      * @param fqcn The fully qualified class name of the <b>caller</b>
276      * @param level The logging level
277      * @param msg The message associated with the event.
278      * @param t A Throwable or null.
279      */

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

440     private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
441
442         private static final long serialVersionUID = -3945953719763255337L;
443
444         private final int size;
445
446         BoundedQueue(final int size) {
447             this.size = size;
448         }
449
450         @Override
451         public boolean add(final E object) {
452             super.add(object);
453             while (messages.size() > size) {
454                 messages.poll();
455             }
456             return size > 0;
457         }
458     }
459 }
460