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 void setLevel(final Level level) {
78          logger.setLevel(level);
79      }
80  
81      /**
82       * Register a new listener.
83       * @param listener The StatusListener to register.
84       */
85      public void registerListener(final StatusListener listener) {
86          listenersLock.writeLock().lock();
87          try {
88              listeners.add(listener);
89          } finally {
90              listenersLock.writeLock().unlock();
91          }
92      }
93  
94      /**
95       * Remove a StatusListener.
96       * @param listener The StatusListener to remove.
97       */
98      public void removeListener(final StatusListener listener) {
99          listenersLock.writeLock().lock();
100         try {
101             listeners.remove(listener);
102         } finally {
103             listenersLock.writeLock().unlock();
104         }
105     }
106 
107     /**
108      * Returns a thread safe Iterator for the StatusListener.
109      * @return An Iterator for the list of StatusListeners.
110      */
111     public Iterator<StatusListener> getListeners() {
112         return listeners.iterator();
113     }
114 
115     /**
116      * Clears the list of status events and listeners.
117      */
118     public void reset() {
119         listeners.clear();
120         clear();
121     }
122 
123     /**
124      * Returns a List of all events as StatusData objects.
125      * @return The list of StatusData objects.
126      */
127     public List<StatusData> getStatusData() {
128         msgLock.lock();
129         try {
130             return new ArrayList<StatusData>(messages);
131         } finally {
132             msgLock.unlock();
133         }
134     }
135 
136     /**
137      * Clears the list of status events.
138      */
139     public void clear() {
140         msgLock.lock();
141         try {
142             messages.clear();
143         } finally {
144             msgLock.unlock();
145         }
146     }
147 
148 
149     /**
150      * Add an event.
151      * @param marker The Marker
152      * @param fqcn   The fully qualified class name of the <b>caller</b>
153      * @param level  The logging level
154      * @param msg    The message associated with the event.
155      * @param t      A Throwable or null.
156      */
157     @Override
158     public void log(final Marker marker, final String fqcn, final Level level, final Message msg, final Throwable t) {
159         StackTraceElement element = null;
160         if (fqcn != null) {
161             element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
162         }
163         final StatusData data = new StatusData(element, level, msg, t);
164         msgLock.lock();
165         try {
166             messages.add(data);
167         } finally {
168             msgLock.unlock();
169         }
170         if (listeners.size() > 0) {
171             for (final StatusListener listener : listeners) {
172                 listener.log(data);
173             }
174         } else {
175             logger.log(marker, fqcn, level, msg, t);
176         }
177     }
178 
179     private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
180         if (fqcn == null) {
181             return null;
182         }
183         boolean next = false;
184         for (final StackTraceElement element : stackTrace) {
185             if (next) {
186                 return element;
187             }
188             final String className = element.getClassName();
189             if (fqcn.equals(className)) {
190                 next = true;
191             } else if (NOT_AVAIL.equals(className)) {
192                 break;
193             }
194         }
195         return null;
196     }
197 
198     @Override
199     protected boolean isEnabled(final Level level, final Marker marker, final String data) {
200         return isEnabled(level, marker);
201     }
202 
203     @Override
204     protected boolean isEnabled(final Level level, final Marker marker, final String data, final Throwable t) {
205         return isEnabled(level, marker);
206     }
207 
208     @Override
209     protected boolean isEnabled(final Level level, final Marker marker, final String data, final Object... p1) {
210         return isEnabled(level, marker);
211     }
212 
213     @Override
214     protected boolean isEnabled(final Level level, final Marker marker, final Object data, final Throwable t) {
215         return isEnabled(level, marker);
216     }
217 
218     @Override
219     protected boolean isEnabled(final Level level, final Marker marker, final Message data, final Throwable t) {
220         return isEnabled(level, marker);
221     }
222 
223     protected boolean isEnabled(final Level level, final Marker marker) {
224         if (listeners.size() > 0) {
225             return true;
226         }
227         switch (level) {
228             case FATAL:
229                 return logger.isFatalEnabled(marker);
230             case TRACE:
231                 return logger.isTraceEnabled(marker);
232             case DEBUG:
233                 return logger.isDebugEnabled(marker);
234             case INFO:
235                 return logger.isInfoEnabled(marker);
236             case WARN:
237                 return logger.isWarnEnabled(marker);
238             case ERROR:
239                 return logger.isErrorEnabled(marker);
240         }
241         return false;
242     }
243 
244     /**
245      * Queue for status events.
246      * @param <E> Object type to be stored in the queue.
247      */
248     private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
249 
250         private static final long serialVersionUID = -3945953719763255337L;
251 
252         private final int size;
253 
254         public BoundedQueue(final int size) {
255             this.size = size;
256         }
257 
258         @Override
259         public boolean add(final E object) {
260             while (messages.size() > size) {
261                 messages.poll();
262             }
263             return super.add(object);
264         }
265     }
266 }