View Javadoc
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 SimpleLoggerContext#SYSTEM_PREFIX}StatusLogger.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      private static final long serialVersionUID = 2L;
68  
69      private static final String NOT_AVAIL = "?";
70  
71      private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");
72  
73      private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200);
74  
75      private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty(DEFAULT_STATUS_LISTENER_LEVEL);
76  
77      // LOG4J2-1176: normal parameterized message remembers param object, causing memory leaks.
78      private static final StatusLogger STATUS_LOGGER = new StatusLogger(StatusLogger.class.getName(),
79              ParameterizedNoReferenceMessageFactory.INSTANCE);
80  
81      private final SimpleLogger logger;
82  
83      private final Collection<StatusListener> listeners = new CopyOnWriteArrayList<>();
84  
85      @SuppressWarnings("NonSerializableFieldInSerializableClass")
86      // ReentrantReadWriteLock is Serializable
87      private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
88  
89      private final Queue<StatusData> messages = new BoundedQueue<>(MAX_ENTRIES);
90  
91      @SuppressWarnings("NonSerializableFieldInSerializableClass")
92      // ReentrantLock is Serializable
93      private final Lock msgLock = new ReentrantLock();
94  
95      private int listenersLevel;
96  
97      private StatusLogger(final String name, final MessageFactory messageFactory) {
98          super(name, messageFactory);
99          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 }