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