1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
118 if (_threadLogs.containsKey(thread)) {
119
120 appenderLogs = (Map)_threadLogs.get(thread);
121 }
122 else {
123
124 appenderLogs = new ConcurrentHashMap();
125 _threadLogs.put(thread,appenderLogs);
126 }
127
128
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
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
152 Map appenderLogs = (Map)_threadLogs.get(thread);
153
154
155 if (appenderLogs==null) {
156
157 logger.error("Can't find logs for thread: " + thread.getName());
158 return;
159 }
160
161
162 ThreadEventLog eventLog = (ThreadEventLog)appenderLogs.get(appender);
163
164 if (eventLog==null) {
165
166 logger.error("Can't find thread log for appender: " + appender.getClass().getName());
167 return;
168 }
169
170
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
220 int numToRemove = (int)((float)_maximumReportedBugs * .2);
221 if (numToRemove==0)
222 numToRemove=1;
223
224
225 List values = new ArrayList(_reportedBugs.values());
226 Collections.sort(values);
227
228
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
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
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
287 trackReport(report);
288
289
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
304 _threadLogs.remove(appThread);
305 }
306 }
307
308
309 try {
310 Thread.sleep(sleep);
311 }
312 catch (InterruptedException e) {
313
314 logger.warn("BugAppenderController monitor interrupted");
315 }
316 }
317 }
318
319 }