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