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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 package smtphandler;
51
52 import java.util.logging.*;
53
54 import java.util.Date;
55 import java.util.Properties;
56
57 import javax.mail.Authenticator;
58 import javax.mail.Message;
59 import javax.mail.Multipart;
60 import javax.mail.Session;
61 import javax.mail.Transport;
62 import javax.mail.internet.AddressException;
63 import javax.mail.internet.InternetAddress;
64 import javax.mail.internet.MimeBodyPart;
65 import javax.mail.internet.MimeMessage;
66 import javax.mail.internet.MimeMultipart;
67
68 /**
69 Send an e-mail when a specific logging record occurs, typically on
70 {@link java.util.logging.Level#WARNING WARNING}
71 or {@link java.util.logging.Level#SEVERE SEVERE}
72
73 <p>The number of {@link LogRecord} objects delivered in this e-mail depend on
74 the value of <b>bufferSize</b> option. The
75 <code>SMTPHandler</code> keeps only the last
76 <code>bufferSize</code> records in its cyclic buffer. This
77 keeps memory requirements at a reasonable level while still
78 delivering useful application context.</p>
79
80 <p>
81 <img src="doc-files/smtphandler-diagram.jpg">
82 </p>
83
84 <p>
85 The code in this class is derived from
86 <a href="http://logging.apache.org/log4j/">log4j</a>'s
87 SMTPAppender class.
88 </p>
89
90 @author Ceki Gülcü (author of log4j's SMTPAppender)
91 @author Sean C. Sullivan
92
93 */
94 public class SMTPHandler extends java.util.logging.Handler
95 {
96 static private final int DEFAULT_ERROR_CODE = 1;
97 static private final Formatter DEFAULT_FORMATTER = new SimpleFormatter();
98 static private final Level DEFAULT_LEVEL = Level.WARNING;
99 static private final int DEFAULT_BUFFER_SIZE = 512;
100 static private final String name = SMTPHandler.class.getName();
101 private String to;
102 private String from;
103 private String subject;
104 private String smtpHost;
105 private String smtpUsername;
106 private String smtpPassword;
107 private int bufferSize = DEFAULT_BUFFER_SIZE;
108 protected CyclicBuffer cb = new CyclicBuffer(bufferSize);
109 protected TriggeringRecordEvaluator evaluator;
110
111 public SMTPHandler()
112 {
113 final String prefix = SMTPHandler.class.getName();
114
115 LogManager manager = LogManager.getLogManager();
116
117 String strLevel = manager.getProperty(prefix + ".level");
118 Level lev;
119 if (strLevel == null)
120 {
121 lev = DEFAULT_LEVEL;
122 }
123 try
124 {
125 lev = Level.parse(strLevel);
126 }
127 catch (Exception ex)
128 {
129 lev = DEFAULT_LEVEL;
130 }
131 setLevel(lev);
132
133 String strFormatterClassName = manager.getProperty(prefix + ".formatter");
134 Formatter f = (Formatter) instantiateByClassName(strFormatterClassName,
135 DEFAULT_FORMATTER);
136 this.setFormatter(f);
137
138
139 setTo(manager.getProperty(prefix + ".to"));
140 setFrom(manager.getProperty(prefix + ".from"));
141 setSmtpHost(manager.getProperty(prefix + ".smtpHost"));
142 setSmtpUsername(manager.getProperty(prefix + ".smtpUsername"));
143 setSmtpPassword(manager.getProperty(prefix + ".smtpPassword"));
144 setSubject(manager.getProperty(prefix + ".subject"));
145
146 String strEvaluatorClassName = manager.getProperty(prefix + ".evaluatorClass");
147 this.setEvaluatorClass(strEvaluatorClassName);
148
149 try
150 {
151 String strBufSize = manager.getProperty(prefix + ".bufferSize");
152 setBufferSize(Integer.parseInt(strBufSize));
153 }
154 catch (Exception ex)
155 {
156 setBufferSize(DEFAULT_BUFFER_SIZE);
157 }
158
159 }
160
161
162 /**
163 Perform SMTPHandler specific appending actions, mainly adding
164 the record to a cyclic buffer and checking if the record triggers
165 an e-mail to be sent. */
166 public synchronized void publish(LogRecord record)
167 {
168 if (record == null)
169 {
170 return;
171 }
172 else if ( ! isLoggable(record))
173 {
174 return;
175 }
176 else if (!checkEntryConditions())
177 {
178 return;
179 }
180
181 cb.add(record);
182
183 if (evaluator.isTriggeringRecord(record))
184 {
185 sendBuffer();
186 }
187 }
188
189 /**
190 This method determines if there is a sense in attempting to append.
191
192 <p>It checks whether there is a "To" address set and also if
193 there is a set evaluator. If these checks fail, then the boolean
194 value <code>false</code> is returned. */
195 protected boolean checkEntryConditions()
196 {
197 if (this.evaluator == null)
198 {
199 reportError(
200 "No TriggeringRecordEvaluator is set for handler ["
201 + name
202 + "].",
203 null,
204 DEFAULT_ERROR_CODE);
205
206 return false;
207 }
208
209 if (getTo() == null)
210 {
211 reportError(
212 "No 'To' email address set for handler ["
213 + name
214 + "].",
215 null,
216 DEFAULT_ERROR_CODE);
217 return false;
218
219 }
220
221 return true;
222 }
223
224 public synchronized void close()
225 {
226
227 }
228
229 protected InternetAddress getAddress(String addressStr)
230 {
231 try
232 {
233 return new InternetAddress(addressStr);
234 }
235 catch (AddressException e)
236 {
237 reportError("Could not parse address [" + addressStr + "].",
238 e,
239 DEFAULT_ERROR_CODE);
240
241 return null;
242 }
243 }
244
245 protected InternetAddress[] parseAddress(String addressStr)
246 {
247 try
248 {
249 return InternetAddress.parse(addressStr, true);
250 }
251 catch (AddressException e)
252 {
253 reportError("Could not parse address [" + addressStr + "].",
254 e,
255 DEFAULT_ERROR_CODE);
256
257 return new InternetAddress[0];
258 }
259 }
260
261 /**
262 Returns value of the <b>To</b> option.
263 */
264 public String getTo()
265 {
266 return to;
267 }
268
269 /**
270 Send the contents of the cyclic buffer as an e-mail message.
271 */
272 protected void sendBuffer()
273 {
274
275
276
277 Properties props = new Properties(System.getProperties());
278
279 if (smtpHost != null)
280 {
281 props.put("mail.smtp.host", smtpHost);
282 }
283
284 Authenticator auth = null;
285
286 if (this.getSmtpUsername() != null)
287 {
288 auth = new UsernamePasswordAuthenticator(
289 this.getSmtpUsername(),
290 this.getSmtpPassword());
291 props.put("mail.smtp.user", this.getSmtpUsername());
292 props.put("mail.smtp.auth", "true");
293 }
294
295 Session session = Session.getInstance(props, auth);
296
297 session.setDebug(true);
298
299 MimeMessage msg = new MimeMessage(session);
300
301 try
302 {
303 if (from != null)
304 {
305 msg.setFrom(getAddress(from));
306 }
307 else
308 {
309 msg.setFrom();
310 }
311
312 msg.setRecipients(Message.RecipientType.TO, parseAddress(to));
313
314 if (subject != null)
315 {
316 msg.setSubject(subject);
317 }
318
319 MimeBodyPart part = new MimeBodyPart();
320
321 StringBuffer sbuf = new StringBuffer();
322
323 Formatter f = getFormatter();
324
325 String head = f.getHead(this);
326
327 if (head != null)
328 {
329 sbuf.append(head);
330 }
331
332 int len = cb.length();
333
334 for (int i = 0; i < len; i++)
335 {
336 LogRecord record = cb.get();
337 sbuf.append(getFormatter().format(record));
338 }
339
340 String tail = getFormatter().getTail(this);
341
342 if (tail != null)
343 {
344 sbuf.append(tail);
345 }
346
347 part.setContent(sbuf.toString(), getEmailContentType());
348
349 Multipart mp = new MimeMultipart();
350 mp.addBodyPart(part);
351 msg.setContent(mp);
352
353 msg.setSentDate(new Date());
354
355 Transport.send(msg);
356 }
357 catch (Exception ex)
358 {
359 this.reportError("sendBuffer",
360 ex,
361 DEFAULT_ERROR_CODE);
362 }
363 }
364
365 protected String getEmailContentType()
366 {
367 return "text/plain";
368 }
369
370 /**
371 Returns value of the <b>EvaluatorClass</b> option.
372 */
373 public String getEvaluatorClass()
374 {
375 return (evaluator == null) ? null : evaluator.getClass().getName();
376 }
377
378 /**
379 Returns value of the <b>From</b> option.
380 */
381 public String getFrom()
382 {
383 return from;
384 }
385
386 /**
387 Returns value of the <b>Subject</b> option.
388 */
389 public String getSubject()
390 {
391 return subject;
392 }
393
394 /**
395 Returns value of the <b>SmtpHost</b> option.
396 */
397 public String getSmtpHost()
398 {
399 return smtpHost;
400 }
401
402 public void flush()
403 {
404
405 }
406
407 /**
408 Returns value of the <b>bufferSize</b> option.
409 */
410 public int getBufferSize()
411 {
412 return bufferSize;
413 }
414
415 /**
416 The <b>from</b> option takes a string value which should be a
417 e-mail address of the sender.
418 */
419 public void setFrom(String from)
420 {
421 this.from = from;
422 }
423
424 /**
425 The <b>to</b> option takes a string value which should be a
426 e-mail address of the sender.
427 */
428 public void setTo(String to)
429 {
430 this.to = to;
431 }
432
433 /**
434 The <b>subject</b> option takes a string value which should be a
435 the subject of the e-mail message.
436 */
437 public void setSubject(String subject)
438 {
439 this.subject = subject;
440 }
441
442 /**
443 The <b>bufferSize</b> option takes a positive integer
444 representing the maximum number of logging records to collect in a
445 cyclic buffer. When the <code>bufferSize</code> is reached,
446 oldest records are deleted as new records are added to the
447 buffer. By default the size of the cyclic buffer is 512 records.
448 */
449 public void setBufferSize(int bufferSize)
450 {
451 this.bufferSize = bufferSize;
452 cb.resize(bufferSize);
453 }
454
455 /**
456 The <b>smtpHost</b> option takes a string value which should be a
457 the host name of the SMTP server that will send the e-mail message.
458 */
459 public void setSmtpHost(String smtpHost)
460 {
461 this.smtpHost = smtpHost;
462 }
463
464 /**
465 The <b>evaluatorClass</b> option takes a string value
466 representing the name of the class implementing the {@link
467 TriggeringRecordEvaluator} interface. A corresponding object will
468 be instantiated and assigned as the triggering record evaluator
469 for the SMTPHandler.
470 */
471 public void setEvaluatorClass(String value)
472 {
473 evaluator =
474 (TriggeringRecordEvaluator) instantiateByClassName(value, new DefaultEvaluator());
475 }
476
477 static Object instantiateByClassName(String strClassName, Object defaultObj)
478 {
479 Object result;
480
481 try
482 {
483 ClassLoader loader = Thread.currentThread().getContextClassLoader();
484 Class clazz = loader.loadClass(strClassName);
485 result = clazz.newInstance();
486 }
487 catch (Exception ex)
488 {
489 result = defaultObj;
490 }
491 return result;
492
493 }
494 public String toString()
495 {
496 StringBuffer sb = new StringBuffer();
497 sb.append("SmtpHost=");
498 sb.append(String.valueOf(this.getSmtpHost()));
499 sb.append("\nFrom=");
500 sb.append(String.valueOf(this.getFrom()));
501 sb.append("\nTo=");
502 sb.append(String.valueOf(this.getTo()));
503 sb.append("\nSubject=");
504 sb.append(String.valueOf(this.getSubject()));
505 sb.append("\nFormatter=");
506 sb.append(String.valueOf(this.getFormatter()));
507 sb.append("\nLevel=");
508 sb.append(String.valueOf(this.getLevel()));
509 sb.append("\nBufferSize=");
510 sb.append(String.valueOf(this.getBufferSize()));
511 return sb.toString();
512 }
513
514
515 public String getSmtpPassword()
516 {
517 return smtpPassword;
518 }
519
520
521 public void setSmtpPassword(String value)
522 {
523 this.smtpPassword = value;
524 }
525
526
527 public String getSmtpUsername()
528 {
529 return smtpUsername;
530 }
531
532
533 public void setSmtpUsername(String value)
534 {
535 this.smtpUsername = value;
536 }
537
538 }
539
540 class DefaultEvaluator implements TriggeringRecordEvaluator
541 {
542 /**
543
544 Should this <code>record</code> trigger an email message to
545 be sent?
546
547 <p>This method returns <code>true</code>, if the record level
548 has WARNING level or higher. Otherwise it returns
549 <code>false</code>.
550
551 */
552 public boolean isTriggeringRecord(final LogRecord record)
553 {
554 boolean result = false;
555
556 if (record == null)
557 {
558 result = false;
559 }
560 else if (record.getLevel() == null)
561 {
562 result = false;
563 }
564 else
565 {
566 result = record.getLevel().intValue() >= java.util.logging.Level.WARNING.intValue();
567 }
568
569 return result;
570 }
571
572
573 }