1
2
3
4
5
6
7
8
9
10
11
12
13
14 package ch.qos.logback.core.net;
15
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Date;
19 import java.util.List;
20 import java.util.Properties;
21
22 import javax.mail.Message;
23 import javax.mail.MessagingException;
24 import javax.mail.Multipart;
25 import javax.mail.Session;
26 import javax.mail.Transport;
27 import javax.mail.internet.AddressException;
28 import javax.mail.internet.InternetAddress;
29 import javax.mail.internet.MimeBodyPart;
30 import javax.mail.internet.MimeMessage;
31 import javax.mail.internet.MimeMultipart;
32
33 import ch.qos.logback.core.AppenderBase;
34 import ch.qos.logback.core.CoreConstants;
35 import ch.qos.logback.core.Layout;
36 import ch.qos.logback.core.boolex.EvaluationException;
37 import ch.qos.logback.core.boolex.EventEvaluator;
38 import ch.qos.logback.core.helpers.CyclicBuffer;
39 import ch.qos.logback.core.pattern.PatternLayoutBase;
40 import ch.qos.logback.core.sift.DefaultDiscriminator;
41 import ch.qos.logback.core.sift.Discriminator;
42 import ch.qos.logback.core.spi.CyclicBufferTracker;
43 import ch.qos.logback.core.spi.CyclicBufferTrackerImpl;
44 import ch.qos.logback.core.util.ContentTypeUtil;
45 import ch.qos.logback.core.util.OptionHelper;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 public abstract class SMTPAppenderBase<E> extends AppenderBase<E> {
62
63 static InternetAddress[] EMPTY_IA_ARRAY = new InternetAddress[0];
64
65 static final int MAX_DELAY_BETWEEN_STATUS_MESSAGES = 1228800 * CoreConstants.MILLIS_IN_ONE_SECOND;
66
67 long lastTrackerStatusPrint = 0;
68 int delayBetweenStatusMessages = 300 * CoreConstants.MILLIS_IN_ONE_SECOND;
69
70 protected Layout<E> subjectLayout;
71 protected Layout<E> layout;
72
73 private List<PatternLayoutBase<E>> toPatternLayoutList = new ArrayList<PatternLayoutBase<E>>();
74 private String from;
75 private String subjectStr = null;
76 private String smtpHost;
77 private int smtpPort = 25;
78 private boolean starttls = false;
79 private boolean ssl = false;
80
81 String username;
82 String password;
83 String localhost;
84
85 boolean asynchronousSending = true;
86
87 private String charsetEncoding = "UTF-8";
88
89 protected MimeMessage mimeMsg;
90
91 protected EventEvaluator<E> eventEvaluator;
92
93 protected Discriminator<E> discriminator = new DefaultDiscriminator<E>();
94 protected CyclicBufferTracker<E> cbTracker;
95
96 private int errorCount = 0;
97
98
99
100
101
102
103
104
105
106 abstract protected Layout<E> makeSubjectLayout(String subjectStr);
107
108
109
110
111 public void start() {
112
113 if (cbTracker == null) {
114 cbTracker = new CyclicBufferTrackerImpl<E>();
115 }
116
117 Properties props = new Properties(OptionHelper.getSystemProperties());
118 if (smtpHost != null) {
119 props.put("mail.smtp.host", smtpHost);
120 }
121 props.put("mail.smtp.port", Integer.toString(smtpPort));
122
123 if(localhost != null) {
124 props.put("mail.smtp.localhost", localhost);
125 }
126
127 LoginAuthenticator loginAuthenticator = null;
128
129 if (username != null) {
130 loginAuthenticator = new LoginAuthenticator(username, password);
131 props.put("mail.smtp.auth", "true");
132 }
133
134 if (isSTARTTLS() && isSSL()) {
135 addError("Both SSL and StartTLS cannot be enabled simultaneously");
136 } else {
137 if (isSTARTTLS()) {
138
139 props.put("mail.smtp.starttls.enable", "true");
140 }
141 if (isSSL()) {
142 String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
143 props.put("mail.smtp.socketFactory.port", Integer.toString(smtpPort));
144 props.put("mail.smtp.socketFactory.class", SSL_FACTORY);
145 props.put("mail.smtp.socketFactory.fallback", "true");
146 }
147 }
148
149
150
151 Session session = Session.getInstance(props, loginAuthenticator);
152 mimeMsg = new MimeMessage(session);
153
154 try {
155 if (from != null) {
156 mimeMsg.setFrom(getAddress(from));
157 } else {
158 mimeMsg.setFrom();
159 }
160
161 subjectLayout = makeSubjectLayout(subjectStr);
162
163 started = true;
164
165 } catch (MessagingException e) {
166 addError("Could not activate SMTPAppender options.", e);
167 }
168 }
169
170
171
172
173
174 protected void append(E eventObject) {
175
176 if (!checkEntryConditions()) {
177 return;
178 }
179
180 String key = discriminator.getDiscriminatingValue(eventObject);
181 long now = System.currentTimeMillis();
182 final CyclicBuffer<E> cb = cbTracker.getOrCreate(key, now);
183 subAppend(cb, eventObject);
184
185 cb.asList();
186
187 try {
188 if (eventEvaluator.evaluate(eventObject)) {
189
190 CyclicBuffer<E> cbClone = new CyclicBuffer<E>(cb);
191
192 cb.clear();
193
194
195 SenderRunnable senderRunnable = new SenderRunnable(cbClone, eventObject);
196 context.getExecutorService().execute(senderRunnable);
197 }
198 } catch (EvaluationException ex) {
199 errorCount++;
200 if (errorCount < CoreConstants.MAX_ERROR_COUNT) {
201 addError("SMTPAppender's EventEvaluator threw an Exception-", ex);
202 }
203 }
204
205
206 if (isEventMarkedForBufferRemoval(eventObject)) {
207 cbTracker.removeBuffer(key);
208 }
209
210 cbTracker.clearStaleBuffers(now);
211
212 if (lastTrackerStatusPrint + delayBetweenStatusMessages < now) {
213 addInfo("SMTPAppender [" + name + "] is tracking [" + cbTracker.size() + "] buffers");
214 lastTrackerStatusPrint = now;
215
216 if (delayBetweenStatusMessages < MAX_DELAY_BETWEEN_STATUS_MESSAGES) {
217 delayBetweenStatusMessages *= 4;
218 }
219 }
220 }
221
222 abstract protected boolean isEventMarkedForBufferRemoval(E eventObject);
223
224 abstract protected void subAppend(CyclicBuffer<E> cb, E eventObject);
225
226
227
228
229
230
231
232
233
234 public boolean checkEntryConditions() {
235 if (!this.started) {
236 addError("Attempting to append to a non-started appender: "
237 + this.getName());
238 return false;
239 }
240
241 if (this.mimeMsg == null) {
242 addError("Message object not configured.");
243 return false;
244 }
245
246 if (this.eventEvaluator == null) {
247 addError("No EventEvaluator is set for appender [" + name + "].");
248 return false;
249 }
250
251 if (this.layout == null) {
252 addError("No layout set for appender named ["
253 + name
254 + "]. For more information, please visit http://logback.qos.ch/codes.html#smtp_no_layout");
255 return false;
256 }
257 return true;
258 }
259
260 synchronized public void stop() {
261 this.started = false;
262 }
263
264 InternetAddress getAddress(String addressStr) {
265 try {
266 return new InternetAddress(addressStr);
267 } catch (AddressException e) {
268 addError("Could not parse address [" + addressStr + "].", e);
269 return null;
270 }
271 }
272
273 private List<InternetAddress> parseAddress(E event) {
274 int len = toPatternLayoutList.size();
275
276 List<InternetAddress> iaList = new ArrayList<InternetAddress>();
277
278 for (int i = 0; i < len; i++) {
279 try {
280 PatternLayoutBase<E> emailPL = toPatternLayoutList.get(i);
281 String email = emailPL.doLayout(event);
282 if (email == null || email.length() == 0) {
283 continue;
284 }
285 InternetAddress[] tmp = InternetAddress.parse(email, true);
286 iaList.addAll(Arrays.asList(tmp));
287 } catch (AddressException e) {
288 addError("Could not parse email address for [" + toPatternLayoutList.get(i) + "] for event [" + event + "]", e);
289 return iaList;
290 }
291 }
292
293 return iaList;
294 }
295
296
297
298
299 public List<PatternLayoutBase<E>> getToList() {
300 return toPatternLayoutList;
301 }
302
303
304
305
306 protected void sendBuffer(CyclicBuffer<E> cb, E lastEventObject) {
307
308
309
310 try {
311 MimeBodyPart part = new MimeBodyPart();
312
313 StringBuffer sbuf = new StringBuffer();
314
315 String header = layout.getFileHeader();
316 if (header != null) {
317 sbuf.append(header);
318 }
319 String presentationHeader = layout.getPresentationHeader();
320 if (presentationHeader != null) {
321 sbuf.append(presentationHeader);
322 }
323 fillBuffer(cb, sbuf);
324 String presentationFooter = layout.getPresentationFooter();
325 if (presentationFooter != null) {
326 sbuf.append(presentationFooter);
327 }
328 String footer = layout.getFileFooter();
329 if (footer != null) {
330 sbuf.append(footer);
331 }
332
333 String subjectStr = "Undefined subject";
334 if (subjectLayout != null) {
335 subjectStr = subjectLayout.doLayout(lastEventObject);
336 }
337 mimeMsg.setSubject(subjectStr, charsetEncoding);
338
339 List<InternetAddress> destinationAddresses = parseAddress(lastEventObject);
340 if (destinationAddresses.isEmpty()) {
341 addInfo("Empty destination address. Aborting email transmission");
342 return;
343 }
344
345 InternetAddress[] toAddressArray = destinationAddresses.toArray(EMPTY_IA_ARRAY);
346 mimeMsg.setRecipients(Message.RecipientType.TO, toAddressArray);
347
348 String contentType = layout.getContentType();
349
350 if (ContentTypeUtil.isTextual(contentType)) {
351 part.setText(sbuf.toString(), charsetEncoding, ContentTypeUtil
352 .getSubType(contentType));
353 } else {
354 part.setContent(sbuf.toString(), layout.getContentType());
355 }
356
357 Multipart mp = new MimeMultipart();
358 mp.addBodyPart(part);
359 mimeMsg.setContent(mp);
360
361 mimeMsg.setSentDate(new Date());
362 Transport.send(mimeMsg);
363 addInfo("Sent out SMTP message \""+subjectStr+"\" to "+Arrays.toString(toAddressArray));
364 } catch (Exception e) {
365 addError("Error occurred while sending e-mail notification.", e);
366 }
367 }
368
369 abstract protected void fillBuffer(CyclicBuffer<E> cb, StringBuffer sbuf);
370
371
372
373
374 public String getFrom() {
375 return from;
376 }
377
378
379
380
381 public String getSubject() {
382 return subjectStr;
383 }
384
385
386
387
388
389 public void setFrom(String from) {
390 this.from = from;
391 }
392
393
394
395
396
397 public void setSubject(String subject) {
398 this.subjectStr = subject;
399 }
400
401
402
403
404
405
406 public void setSMTPHost(String smtpHost) {
407 setSmtpHost(smtpHost);
408 }
409
410
411
412
413
414 public void setSmtpHost(String smtpHost) {
415 this.smtpHost = smtpHost;
416 }
417
418
419
420
421 public String getSMTPHost() {
422 return getSmtpHost();
423 }
424
425
426
427
428 public String getSmtpHost() {
429 return smtpHost;
430 }
431
432
433
434
435
436
437 public void setSMTPPort(int port) {
438 setSmtpPort(port);
439 }
440
441
442
443
444
445
446 public void setSmtpPort(int port) {
447 this.smtpPort = port;
448 }
449
450
451
452
453
454
455 public int getSMTPPort() {
456 return getSmtpPort();
457 }
458
459
460
461
462
463
464 public int getSmtpPort() {
465 return smtpPort;
466 }
467
468 public String getLocalhost() {
469 return localhost;
470 }
471
472
473
474
475
476
477
478
479
480
481
482 public void setLocalhost(String localhost) {
483 this.localhost = localhost;
484 }
485
486 public CyclicBufferTracker<E> getCyclicBufferTracker() {
487 return cbTracker;
488 }
489
490 public void setCyclicBufferTracker(CyclicBufferTracker<E> cbTracker) {
491 this.cbTracker = cbTracker;
492 }
493
494 public Discriminator<E> getDiscriminator() {
495 return discriminator;
496 }
497
498 public void setDiscriminator(Discriminator<E> discriminator) {
499 this.discriminator = discriminator;
500 }
501
502 public void addTo(String to) {
503 if (to == null || to.length() == 0) {
504 throw new IllegalArgumentException("Null or empty <to> property");
505 }
506 PatternLayoutBase plb = makeNewToPatternLayout(to.trim());
507 plb.setContext(context);
508 plb.start();
509 this.toPatternLayoutList.add(plb);
510 }
511
512 abstract protected PatternLayoutBase<E> makeNewToPatternLayout(String toPattern);
513
514 public List<String> getToAsListOfString() {
515 List<String> toList = new ArrayList<String>();
516 for (PatternLayoutBase plb : toPatternLayoutList) {
517 toList.add(plb.getPattern());
518 }
519 return toList;
520 }
521
522
523 public Message getMessage() {
524 return mimeMsg;
525 }
526
527
528
529 public void setMessage(MimeMessage msg) {
530 this.mimeMsg = msg;
531 }
532
533 public boolean isSTARTTLS() {
534 return starttls;
535 }
536
537 public void setSTARTTLS(boolean startTLS) {
538 this.starttls = startTLS;
539 }
540
541 public boolean isSSL() {
542 return ssl;
543 }
544
545 public void setSSL(boolean ssl) {
546 this.ssl = ssl;
547 }
548
549
550
551
552
553
554
555 public void setEvaluator(EventEvaluator<E> eventEvaluator) {
556 this.eventEvaluator = eventEvaluator;
557 }
558
559 public String getUsername() {
560 return username;
561 }
562
563 public void setUsername(String username) {
564 this.username = username;
565 }
566
567 public String getPassword() {
568 return password;
569 }
570
571 public void setPassword(String password) {
572 this.password = password;
573 }
574
575
576
577
578
579 public String getCharsetEncoding() {
580 return charsetEncoding;
581 }
582
583
584
585
586
587
588
589 public void setCharsetEncoding(String charsetEncoding) {
590 this.charsetEncoding = charsetEncoding;
591 }
592
593 public Layout<E> getLayout() {
594 return layout;
595 }
596
597 public void setLayout(Layout<E> layout) {
598 this.layout = layout;
599 }
600
601 class SenderRunnable implements Runnable {
602
603 final CyclicBuffer<E> cyclicBuffer;
604 final E e;
605
606 SenderRunnable(CyclicBuffer<E> cyclicBuffer, E e) {
607 this.cyclicBuffer = cyclicBuffer;
608 this.e = e;
609 }
610 public void run() {
611 sendBuffer(cyclicBuffer, e);
612 }
613 }
614 }