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(String name, 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     /**
145      * Returns a thread safe Iterable for the StatusListener.
146      * 
147      * @return An Iterable for the list of StatusListeners.
148      */
149     public Iterable<StatusListener> getListeners() {
150         return listeners;
151     }
152 
153     /**
154      * Clears the list of status events and listeners.
155      */
156     public void reset() {
157         listenersLock.writeLock().lock();
158         try {
159             for (final StatusListener listener : listeners) {
160                 closeSilently(listener);
161             }
162         } finally {
163             listeners.clear();
164             listenersLock.writeLock().unlock();
165             // note this should certainly come after the unlock to avoid unnecessary nested locking
166             clear();
167         }
168     }
169 
170     private static void closeSilently(final Closeable resource) {
171         try {
172             resource.close();
173         } catch (final IOException ignored) {
174             // ignored
175         }
176     }
177 
178     /**
179      * Returns a List of all events as StatusData objects.
180      * 
181      * @return The list of StatusData objects.
182      */
183     public List<StatusData> getStatusData() {
184         msgLock.lock();
185         try {
186             return new ArrayList<>(messages);
187         } finally {
188             msgLock.unlock();
189         }
190     }
191 
192     /**
193      * Clears the list of status events.
194      */
195     public void clear() {
196         msgLock.lock();
197         try {
198             messages.clear();
199         } finally {
200             msgLock.unlock();
201         }
202     }
203 
204     @Override
205     public Level getLevel() {
206         return logger.getLevel();
207     }
208 
209     /**
210      * Adds an event.
211      * 
212      * @param marker The Marker
213      * @param fqcn The fully qualified class name of the <b>caller</b>
214      * @param level The logging level
215      * @param msg The message associated with the event.
216      * @param t A Throwable or null.
217      */
218     @Override
219     public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg,
220             final Throwable t) {
221         StackTraceElement element = null;
222         if (fqcn != null) {
223             element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
224         }
225         final StatusData data = new StatusData(element, level, msg, t, null);
226         msgLock.lock();
227         try {
228             messages.add(data);
229         } finally {
230             msgLock.unlock();
231         }
232         if (listeners.size() > 0) {
233             for (final StatusListener listener : listeners) {
234                 if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) {
235                     listener.log(data);
236                 }
237             }
238         } else {
239             logger.logMessage(fqcn, level, marker, msg, t);
240         }
241     }
242 
243     private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
244         if (fqcn == null) {
245             return null;
246         }
247         boolean next = false;
248         for (final StackTraceElement element : stackTrace) {
249             final String className = element.getClassName();
250             if (next && !fqcn.equals(className)) {
251                 return element;
252             }
253             if (fqcn.equals(className)) {
254                 next = true;
255             } else if (NOT_AVAIL.equals(className)) {
256                 break;
257             }
258         }
259         return null;
260     }
261 
262     @Override
263     public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
264         return isEnabled(level, marker);
265     }
266 
267     @Override
268     public boolean isEnabled(final Level level, final Marker marker, final String message) {
269         return isEnabled(level, marker);
270     }
271 
272     @Override
273     public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {
274         return isEnabled(level, marker);
275     }
276 
277     @Override
278     public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) {
279         return isEnabled(level, marker);
280     }
281 
282     @Override
283     public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) {
284         return isEnabled(level, marker);
285     }
286 
287     @Override
288     public boolean isEnabled(final Level level, final Marker marker) {
289         if (listeners.size() > 0) {
290             return listenersLevel >= level.intLevel();
291         }
292         return logger.isEnabled(level, marker);
293     }
294 
295     /**
296      * Queues for status events.
297      * 
298      * @param <E> Object type to be stored in the queue.
299      */
300     private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
301 
302         private static final long serialVersionUID = -3945953719763255337L;
303 
304         private final int size;
305 
306         public BoundedQueue(final int size) {
307             this.size = size;
308         }
309 
310         @Override
311         public boolean add(final E object) {
312             super.add(object);
313             while (messages.size() > size) {
314                 messages.poll();
315             }
316             return size > 0;
317         }
318     }
319 }