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