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()) {
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 }