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.util.ArrayList;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Queue;
23  import java.util.concurrent.ConcurrentLinkedQueue;
24  import java.util.concurrent.CopyOnWriteArrayList;
25  import java.util.concurrent.locks.ReentrantLock;
26  import java.util.concurrent.locks.ReentrantReadWriteLock;
27  
28  import org.apache.logging.log4j.Level;
29  import org.apache.logging.log4j.Marker;
30  import org.apache.logging.log4j.message.Message;
31  import org.apache.logging.log4j.simple.SimpleLogger;
32  import org.apache.logging.log4j.spi.AbstractLogger;
33  import org.apache.logging.log4j.util.PropertiesUtil;
34  
35  /**
36   * Mechanism to record events that occur in the logging system.
37   */
38  public final class StatusLogger extends AbstractLogger {
39  
40      private static final long serialVersionUID = 1L;
41  
42      /**
43       * System property that can be configured with the number of entries in the queue. Once the limit
44       * is reached older entries will be removed as new entries are added.
45       */
46      public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries";
47  
48      private static final String NOT_AVAIL = "?";
49  
50      private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");
51  
52      private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200);
53  
54      private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty("log4j2.StatusLogger.level");
55  
56      // private static final String FQCN = AbstractLogger.class.getName();
57  
58      private static final StatusLogger STATUS_LOGGER = new StatusLogger();
59  
60      private final SimpleLogger logger;
61  
62      private final CopyOnWriteArrayList<StatusListener> listeners = new CopyOnWriteArrayList<StatusListener>();
63      private final ReentrantReadWriteLock listenersLock = new ReentrantReadWriteLock();
64  
65      private final Queue<StatusData> messages = new BoundedQueue<StatusData>(MAX_ENTRIES);
66      private final ReentrantLock msgLock = new ReentrantLock();
67  
68      private int listenersLevel;
69  
70      private StatusLogger() {
71          this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, "", null, PROPS,
72              System.err);
73          this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
74      }
75  
76      /**
77       * Retrieve the StatusLogger.
78       * @return The StatusLogger.
79       */
80      public static StatusLogger getLogger() {
81          return STATUS_LOGGER;
82      }
83  
84      public Level getLevel() {
85          return logger.getLevel();
86      }
87  
88      public void setLevel(final Level level) {
89          logger.setLevel(level);
90      }
91  
92      /**
93       * Register a new listener.
94       * @param listener The StatusListener to register.
95       */
96      public void registerListener(final StatusListener listener) {
97          listenersLock.writeLock().lock();
98          try {
99              listeners.add(listener);
100             Level lvl = listener.getStatusLevel();
101             if (listenersLevel < lvl.intLevel()) {
102                 listenersLevel = lvl.intLevel();
103             }
104         } finally {
105             listenersLock.writeLock().unlock();
106         }
107     }
108 
109     /**
110      * Remove a StatusListener.
111      * @param listener The StatusListener to remove.
112      */
113     public void removeListener(final StatusListener 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 Iterator for the StatusListener.
132      * @return An Iterator for the list of StatusListeners.
133      */
134     public Iterator<StatusListener> getListeners() {
135         return listeners.iterator();
136     }
137 
138     /**
139      * Clears the list of status events and listeners.
140      */
141     public void reset() {
142         listeners.clear();
143         clear();
144     }
145 
146     /**
147      * Returns a List of all events as StatusData objects.
148      * @return The list of StatusData objects.
149      */
150     public List<StatusData> getStatusData() {
151         msgLock.lock();
152         try {
153             return new ArrayList<StatusData>(messages);
154         } finally {
155             msgLock.unlock();
156         }
157     }
158 
159     /**
160      * Clears the list of status events.
161      */
162     public void clear() {
163         msgLock.lock();
164         try {
165             messages.clear();
166         } finally {
167             msgLock.unlock();
168         }
169     }
170 
171 
172     /**
173      * Add an event.
174      * @param marker The Marker
175      * @param fqcn   The fully qualified class name of the <b>caller</b>
176      * @param level  The logging level
177      * @param msg    The message associated with the event.
178      * @param t      A Throwable or null.
179      */
180     @Override
181     public void log(final Marker marker, final String fqcn, final Level level, final Message msg, final Throwable t) {
182         StackTraceElement element = null;
183         if (fqcn != null) {
184             element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
185         }
186         final StatusData data = new StatusData(element, level, msg, t);
187         msgLock.lock();
188         try {
189             messages.add(data);
190         } finally {
191             msgLock.unlock();
192         }
193         if (listeners.size() > 0) {
194             for (final StatusListener listener : listeners) {
195                 if (data.getLevel().isAtLeastAsSpecificAs(listener.getStatusLevel())) {
196                     listener.log(data);
197                 }
198             }
199         } else {
200             logger.log(marker, fqcn, level, msg, t);
201         }
202     }
203 
204     private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
205         if (fqcn == null) {
206             return null;
207         }
208         boolean next = false;
209         for (final StackTraceElement element : stackTrace) {
210             if (next) {
211                 return element;
212             }
213             final String className = element.getClassName();
214             if (fqcn.equals(className)) {
215                 next = true;
216             } else if (NOT_AVAIL.equals(className)) {
217                 break;
218             }
219         }
220         return null;
221     }
222 
223     @Override
224     protected boolean isEnabled(final Level level, final Marker marker, final String data) {
225         return isEnabled(level, marker);
226     }
227 
228     @Override
229     protected boolean isEnabled(final Level level, final Marker marker, final String data, final Throwable t) {
230         return isEnabled(level, marker);
231     }
232 
233     @Override
234     protected boolean isEnabled(final Level level, final Marker marker, final String data, final Object... p1) {
235         return isEnabled(level, marker);
236     }
237 
238     @Override
239     protected boolean isEnabled(final Level level, final Marker marker, final Object data, final Throwable t) {
240         return isEnabled(level, marker);
241     }
242 
243     @Override
244     protected boolean isEnabled(final Level level, final Marker marker, final Message data, final Throwable t) {
245         return isEnabled(level, marker);
246     }
247 
248     @Override
249     public boolean isEnabled(final Level level, final Marker marker) {
250         if (listeners.size() > 0) {
251             return listenersLevel >= level.intLevel();
252         }
253 
254         switch (level.getStandardLevel()) {
255             case FATAL:
256                 return logger.isFatalEnabled(marker);
257             case TRACE:
258                 return logger.isTraceEnabled(marker);
259             case DEBUG:
260                 return logger.isDebugEnabled(marker);
261             case INFO:
262                 return logger.isInfoEnabled(marker);
263             case WARN:
264                 return logger.isWarnEnabled(marker);
265             case ERROR:
266                 return logger.isErrorEnabled(marker);
267             default:
268                 return false;
269         }
270     }
271 
272     /**
273      * Queue for status events.
274      * @param <E> Object type to be stored in the queue.
275      */
276     private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
277 
278         private static final long serialVersionUID = -3945953719763255337L;
279 
280         private final int size;
281 
282         public BoundedQueue(final int size) {
283             this.size = size;
284         }
285 
286         @Override
287         public boolean add(final E object) {
288             while (messages.size() > size) {
289                 messages.poll();
290             }
291             return super.add(object);
292         }
293     }
294 }