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