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