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.PropertiesUtil;
41  import org.apache.logging.log4j.util.Strings;
42  
43  /**
44   * Records events that occur in the logging system. By default, only error messages are logged to {@link System#err}.
45   * Normally, the Log4j StatusLogger is configured via the root {@code <Configuration status="LEVEL"/>} node in a Log4j
46   * configuration file. However, this can be overridden via a system property named
47   * "{@value SimpleLoggerContext#SYSTEM_PREFIX}StatusLogger.level" and will work with any Log4j provider.
48   *
49   * @see SimpleLogger
50   * @see SimpleLoggerContext
51   */
52  public final class StatusLogger extends AbstractLogger {
53  
54      /**
55       * System property that can be configured with the number of entries in the queue. Once the limit is reached older
56       * entries will be removed as new entries are added.
57       */
58      public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries";
59  
60      /**
61       * System property that can be configured with the {@link Level} name to use as the default level for
62       * {@link StatusListener}s.
63       */
64      public static final String DEFAULT_STATUS_LISTENER_LEVEL = "log4j2.StatusLogger.level";
65  
66      private static final long serialVersionUID = 2L;
67  
68      private static final String NOT_AVAIL = "?";
69  
70      private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");
71  
72      private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200);
73  
74      private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty(DEFAULT_STATUS_LISTENER_LEVEL);
75  
76      // LOG4J2-1176: normal parameterized message remembers param object, causing memory leaks.
77      private static final StatusLogger STATUS_LOGGER = new StatusLogger(StatusLogger.class.getName(),
78              ParameterizedNoReferenceMessageFactory.INSTANCE);
79  
80      private final SimpleLogger logger;
81  
82      private final Collection<StatusListener> listeners = new CopyOnWriteArrayList<>();
83  
84      @SuppressWarnings("NonSerializableFieldInSerializableClass")
85      // ReentrantReadWriteLock is Serializable
86      private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
87  
88      private final Queue<StatusData> messages = new BoundedQueue<>(MAX_ENTRIES);
89  
90      @SuppressWarnings("NonSerializableFieldInSerializableClass")
91      // ReentrantLock is Serializable
92      private final Lock msgLock = new ReentrantLock();
93  
94      private int listenersLevel;
95  
96      private StatusLogger(final String name, final MessageFactory messageFactory) {
97          super(name, messageFactory);
98          this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, Strings.EMPTY,
99                  messageFactory, PROPS, System.err);
100         this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
101     }
102 
103     /**
104      * Retrieve the StatusLogger.
105      *
106      * @return The StatusLogger.
107      */
108     public static StatusLogger getLogger() {
109         return STATUS_LOGGER;
110     }
111 
112     public void setLevel(final Level level) {
113         logger.setLevel(level);
114     }
115 
116     /**
117      * Registers a new listener.
118      *
119      * @param listener The StatusListener to register.
120      */
121     public void registerListener(final StatusListener listener) {
122         listenersLock.writeLock().lock();
123         try {
124             listeners.add(listener);
125             final Level lvl = listener.getStatusLevel();
126             if (listenersLevel < lvl.intLevel()) {
127                 listenersLevel = lvl.intLevel();
128             }
129         } finally {
130             listenersLock.writeLock().unlock();
131         }
132     }
133 
134     /**
135      * Removes a StatusListener.
136      *
137      * @param listener The StatusListener to remove.
138      */
139     public void removeListener(final StatusListener listener) {
140         closeSilently(listener);
141         listenersLock.writeLock().lock();
142         try {
143             listeners.remove(listener);
144             int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
145             for (final StatusListener statusListener : listeners) {
146                 final int level = statusListener.getStatusLevel().intLevel();
147                 if (lowest < level) {
148                     lowest = level;
149                 }
150             }
151             listenersLevel = lowest;
152         } finally {
153             listenersLock.writeLock().unlock();
154         }
155     }
156 
157     public void updateListenerLevel(final Level status) {
158         if (status.intLevel() > listenersLevel) {
159             listenersLevel = status.intLevel();
160         }
161     }
162 
163     /**
164      * Returns a thread safe Iterable for the StatusListener.
165      *
166      * @return An Iterable for the list of StatusListeners.
167      */
168     public Iterable<StatusListener> getListeners() {
169         return listeners;
170     }
171 
172     /**
173      * Clears the list of status events and listeners.
174      */
175     public void reset() {
176         listenersLock.writeLock().lock();
177         try {
178             for (final StatusListener listener : listeners) {
179                 closeSilently(listener);
180             }
181         } finally {
182             listeners.clear();
183             listenersLock.writeLock().unlock();
184             // note this should certainly come after the unlock to avoid unnecessary nested locking
185             clear();
186         }
187     }
188 
189     private static void closeSilently(final Closeable resource) {
190         try {
191             resource.close();
192         } catch (final IOException ignored) {
193             // ignored
194         }
195     }
196 
197     /**
198      * Returns a List of all events as StatusData objects.
199      *
200      * @return The list of StatusData objects.
201      */
202     public List<StatusData> getStatusData() {
203         msgLock.lock();
204         try {
205             return new ArrayList<>(messages);
206         } finally {
207             msgLock.unlock();
208         }
209     }
210 
211     /**
212      * Clears the list of status events.
213      */
214     public void clear() {
215         msgLock.lock();
216         try {
217             messages.clear();
218         } finally {
219             msgLock.unlock();
220         }
221     }
222 
223     @Override
224     public Level getLevel() {
225         return logger.getLevel();
226     }
227 
228     /**
229      * Adds an event.
230      *
231      * @param marker The Marker
232      * @param fqcn The fully qualified class name of the <b>caller</b>
233      * @param level The logging level
234      * @param msg The message associated with the event.
235      * @param t A Throwable or null.
236      */
237     @Override
238     public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg,
239             final Throwable t) {
240         StackTraceElement element = null;
241         if (fqcn != null) {
242             element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
243         }
244         final StatusData data = new StatusData(element, level, msg, t, null);
245         msgLock.lock();
246         try {
247             messages.add(data);
248         } finally {
249             msgLock.unlock();
250         }
251         if (listeners.size() > 0) {
252             for (final StatusListener listener : listeners) {
253                 if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) {
254                     listener.log(data);
255                 }
256             }
257         } else {
258             logger.logMessage(fqcn, level, marker, msg, t);
259         }
260     }
261 
262     private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
263         if (fqcn == null) {
264             return null;
265         }
266         boolean next = false;
267         for (final StackTraceElement element : stackTrace) {
268             final String className = element.getClassName();
269             if (next && !fqcn.equals(className)) {
270                 return element;
271             }
272             if (fqcn.equals(className)) {
273                 next = true;
274             } else if (NOT_AVAIL.equals(className)) {
275                 break;
276             }
277         }
278         return null;
279     }
280 
281     @Override
282     public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
283         return isEnabled(level, marker);
284     }
285 
286     @Override
287     public boolean isEnabled(final Level level, final Marker marker, final String message) {
288         return isEnabled(level, marker);
289     }
290 
291     @Override
292     public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {
293         return isEnabled(level, marker);
294     }
295 
296     @Override
297     public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) {
298         return isEnabled(level, marker);
299     }
300 
301     @Override
302     public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
303             final Object p1) {
304         return isEnabled(level, marker);
305     }
306 
307     @Override
308     public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
309             final Object p1, final Object p2) {
310         return isEnabled(level, marker);
311     }
312 
313     @Override
314     public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
315             final Object p1, final Object p2, final Object p3) {
316         return isEnabled(level, marker);
317     }
318 
319     @Override
320     public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
321             final Object p1, final Object p2, final Object p3,
322             final Object p4) {
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, final Object p2, final Object p3,
329             final Object p4, final Object p5) {
330         return isEnabled(level, marker);
331     }
332 
333     @Override
334     public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
335             final Object p1, final Object p2, final Object p3,
336             final Object p4, final Object p5, final Object p6) {
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, final Object p5, final Object p6,
344             final Object p7) {
345         return isEnabled(level, marker);
346     }
347 
348     @Override
349     public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
350             final Object p1, final Object p2, final Object p3,
351             final Object p4, final Object p5, final Object p6,
352             final Object p7, final Object p8) {
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, final Object p8, final Object p9) {
361         return isEnabled(level, marker);
362     }
363 
364     @Override
365     public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) {
366         return isEnabled(level, marker);
367     }
368 
369     @Override
370     public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) {
371         return isEnabled(level, marker);
372     }
373 
374     @Override
375     public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) {
376         return isEnabled(level, marker);
377     }
378 
379     @Override
380     public boolean isEnabled(final Level level, final Marker marker) {
381         if (listeners.size() > 0) {
382             return listenersLevel >= level.intLevel();
383         }
384         return logger.isEnabled(level, marker);
385     }
386 
387     /**
388      * Queues for status events.
389      *
390      * @param <E> Object type to be stored in the queue.
391      */
392     private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
393 
394         private static final long serialVersionUID = -3945953719763255337L;
395 
396         private final int size;
397 
398         BoundedQueue(final int size) {
399             this.size = size;
400         }
401 
402         @Override
403         public boolean add(final E object) {
404             super.add(object);
405             while (messages.size() > size) {
406                 messages.poll();
407             }
408             return size > 0;
409         }
410     }
411 }