View Javadoc

1   /*
2   Copyright (c) 2005, CodeSmarts
3    All rights reserved.
4   
5   Redistribution and use in source and binary forms, with or without
6   modification, are permitted provided that the following conditions are
7   met:
8   	* 	Redistributions of source code must retain the above copyright
9   notice, this list of conditions and the following disclaimer.
10  	* 	Redistributions in binary form must reproduce the above
11  copyright notice, this list of conditions and the following disclaimer
12  in the documentation and/or other materials provided with the
13  distribution.
14  	* 	Neither the name of the CodeSmarts nor the names of its
15  contributors may be used to endorse or promote products derived from
16  this software without specific prior written permission.
17  
18  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
22  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30  
31  package net.codesmarts.log4j;
32  
33  import java.util.ArrayList;
34  import java.util.Collections;
35  import java.util.Iterator;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  import org.apache.log4j.Logger;
40  import org.apache.log4j.spi.LoggingEvent;
41  import EDU.oswego.cs.dl.util.concurrent.BoundedBuffer;
42  import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
43  import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
44  
45  /***
46   * Controller singleton that coordinates bugreport appenders and bug reports
47   * @author Fred McCann
48   */
49  public class BugReportController implements Runnable {        
50      /***
51       * logger
52       */
53      private static Logger logger = Logger.getLogger(BugReportController.class);
54      
55      /***
56       * Instance of controller
57       */
58      private static BugReportController _instance = new BugReportController();
59  
60      /***
61       * Hash to remember already reported bugs
62       */
63      private Map _reportedBugs = null;    
64      
65      /***
66       * Maximum number of items allowed in the repoted bugs hash. This 
67       * is fixed at 5000, which I hope is plenty of distinct bugs! This
68       * should translate to roughly 340,000 bytes in memory when filled to
69       * capacity. If this limit is approached, reports will get evicted from the list.
70       */
71      private final static int _maximumReportedBugs = 5000;
72          
73      /***
74       * Sleep in milliseconds
75       */
76      private final int sleep = 500;
77  
78      /***
79       * Map of watched threads and associated logs
80       */    
81      private Map _threadLogs = null;
82      
83      /***
84       * Pool of threads to file bug reports
85       */
86      private PooledExecutor _runnerPool = null;
87      
88      /***
89       * Constructor
90       */
91      protected BugReportController() {
92          _threadLogs = new ConcurrentHashMap();        
93          _reportedBugs = new ConcurrentHashMap();        
94  
95          _runnerPool = new PooledExecutor(new BoundedBuffer(10), 10);
96          _runnerPool.setMinimumPoolSize(2);
97          _runnerPool.setKeepAliveTime(3000);
98          _runnerPool.abortWhenBlocked();
99      }
100 
101     /***
102      * Get a reference to the BugReportController
103      */
104     public static BugReportController getInstance() {
105         return _instance;
106     }
107     
108     /***
109      * Register a thread and an appender with the controller. 
110      * The thread will be watched by the controller.
111      * @param thread Thread to watch
112      * @param appender Appender writing logs for this thread
113      */
114     public void registerLog(Thread thread, BugReportAppender appender) {                
115         Map appenderLogs = null;
116 
117         // See if this thread is already being watched
118         if (_threadLogs.containsKey(thread)) {
119             // Retrieve existing log map
120             appenderLogs = (Map)_threadLogs.get(thread);
121         }
122         else {
123            // Make new log map
124             appenderLogs = new ConcurrentHashMap();
125             _threadLogs.put(thread,appenderLogs);
126         }
127         
128         // Add new log for specified appender
129         appenderLogs.put(appender, new ThreadEventLog(
130                 appender.getThresholdPriority(),
131                 appender.getThresholdSize(),
132                 appender.getMaxSize(),
133                 appender.getHashMethod()));             
134 
135         if (_threadLogs.size()==1) {
136             // Start Monitor
137             Thread monitor = new Thread(this);
138             monitor.setName("BugReport Monitor");
139             monitor.setPriority(Thread.MIN_PRIORITY);
140             monitor.start();            
141         }            
142     }
143     
144     /***
145      * Log an event by a specific appender for a specific thread
146      * @param thread the watched thread
147      * @param appender Appender writing logs
148      * @param event the event to track
149      */
150     public void addEvent(Thread thread, BugReportAppender appender, LoggingEvent event) {
151         // Get the specific log for this appender        
152         Map appenderLogs = (Map)_threadLogs.get(thread);
153         
154         // Get all logs associated with this thread
155         if (appenderLogs==null) {
156             // no reason for this to ever happen!
157             logger.error("Can't find logs for thread: " + thread.getName());
158             return;
159         }
160         
161         // Get the specific log for this appender
162         ThreadEventLog eventLog = (ThreadEventLog)appenderLogs.get(appender);
163         
164         if (eventLog==null) {
165             // no reason for this to ever happen either!
166             logger.error("Can't find thread log for appender: " + appender.getClass().getName());
167             return;
168         }    
169         
170         // Add event
171         eventLog.addEvent(event);
172     }
173     
174     /***
175      * Track bug report in a hash so we track which reports have been filed already
176      * @param report
177      */
178     private void trackReport(BugReport report) {
179         if (_reportedBugs.size() == _maximumReportedBugs)
180             pruneReportedBugs();
181         
182         if (!_reportedBugs.containsKey(report.getKey()))
183             _reportedBugs.put(report.getKey(),new Integer(1));
184         else {
185             Integer count = (Integer)_reportedBugs.get(report.getKey());
186             count = new Integer(count.intValue()+1);
187             _reportedBugs.put(report.getKey(),count);
188         }
189     }
190         
191     /***
192      * Returns true if this is a new bug report
193      * @param report
194      * @return
195      */
196     public boolean isNewBugReport(BugReport report) {
197         return (getBugCount(report)==1);
198     }
199     
200     /***
201      * Return the number of times this bug has been seen
202      * @param report
203      * @return
204      */
205     public int getBugCount(BugReport report) {
206         if (report==null)
207             return 0;        
208         else if (!_reportedBugs.containsKey(report.getKey()))
209             return 0;
210         else
211             return ((Integer)_reportedBugs.get(report.getKey())).intValue();
212     }
213     
214     /***
215      * evict recorded bugs from tracking. This is to prevent this structure
216      * from growing arbitrarily large. This removes least common bugs reports
217      */
218     private void pruneReportedBugs() {
219         // Calulate number of items to evict (20%)
220         int numToRemove = (int)((float)_maximumReportedBugs * .2);           
221         if (numToRemove==0)
222             numToRemove=1;
223         
224         // Get a list of bug frequencies
225         List values = new ArrayList(_reportedBugs.values());
226         Collections.sort(values);
227         
228         // Pick the number of each frequency to remove
229         int[] valueMap = new int[numToRemove+1];
230         
231         for (int x=0; x<=numToRemove; x++)
232             valueMap[x]=0;
233         
234         int count = numToRemove;
235         Iterator i = values.iterator();
236         
237         while (i.hasNext() && count>0) {
238             count--;
239             int v = ((Integer)i.next()).intValue();
240             valueMap[v]++;
241         }
242         
243         // traverse the bug list and remove items of lower frequencies (least common bugs)
244         i = _reportedBugs.keySet().iterator();
245         count = numToRemove;
246         while (i.hasNext() && count>0) {
247             String key = (String)i.next();
248             int v = ((Integer)_reportedBugs.get(key)).intValue();
249             
250             if (v<=numToRemove && valueMap[v]>0) {
251                 valueMap[v]--;
252                 _reportedBugs.remove(key);                    
253             }
254         }                       
255     }
256     
257     /***
258      * Main Loop
259      * @see java.lang.Runnable#run()
260      */
261     public void run() {
262         while (_threadLogs.size()>0) {                     
263 	        // Check the state of watched threads
264 	        Set threads = _threadLogs.keySet();
265 	        
266 	        Iterator i = threads.iterator();        
267 	        while (i.hasNext()) {
268 	            boolean reportType = ThreadEventLog.INCREMENTAL_REPORT;
269 	            
270 	            Thread appThread = (Thread)i.next();
271 	            
272 	            Map appenderLogs = (Map)_threadLogs.get(appThread);
273 	            Set appenders = appenderLogs.keySet();
274 	                        
275 	            if (appThread==null || !appThread.isAlive())
276 	                reportType = ThreadEventLog.FULL_REPORT;            
277 	            
278 	            Iterator a = appenders.iterator();
279 			    while (a.hasNext()) {
280 			        BugReportAppender appender = (BugReportAppender)a.next();
281 			        ThreadEventLog log = (ThreadEventLog)appenderLogs.get(appender);
282 			        
283 			        if (log.isTriggered()) {
284 			            BugReport report = log.generateBugReport(reportType); 
285 		                
286 			            // Track that we've seen this bug report
287 			            trackReport(report);
288 
289 			            // File Bug Report
290 		                if (report!=null && (isNewBugReport(report) || appender.isReportDuplicates())) {
291 			                try {
292 			                    _runnerPool.execute(new BugReportRunner(appender,report));
293 			                }
294 			                catch (InterruptedException e){
295 			                    logger.error("Interrupted while trying to file report to appender: " + 
296 			                            appender.toString());
297 			                }			                
298 			            }
299 			        }
300 			    }
301 			   
302 			    if (reportType = ThreadEventLog.FULL_REPORT) {
303 			        // Remove thread from the map
304 			        _threadLogs.remove(appThread);
305 			    }
306 	        }
307 	        
308 		    // Sleep for a while before checking threads again    
309 	        try {
310 		        Thread.sleep(sleep);
311 	        }
312 	        catch (InterruptedException e) {
313 	            // ignore it
314 	            logger.warn("BugAppenderController monitor interrupted");
315 	        }        
316         }        
317     }
318 
319 }