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