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      /**
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     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 }