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