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  
18  package org.apache.log4j.net;
19  
20  import org.apache.log4j.AppenderSkeleton;
21  import org.apache.log4j.Level;
22  import org.apache.log4j.Layout;
23  import org.apache.log4j.xml.UnrecognizedElementHandler;
24  import org.apache.log4j.helpers.CyclicBuffer;
25  import org.apache.log4j.helpers.OptionConverter;
26  import org.apache.log4j.helpers.LogLog;
27  import org.apache.log4j.spi.LoggingEvent;
28  import org.apache.log4j.spi.ErrorCode;
29  import org.apache.log4j.spi.TriggeringEventEvaluator;
30  import org.apache.log4j.spi.OptionHandler;
31  import org.w3c.dom.Element;
32  
33  import java.util.Properties;
34  import java.util.Date;
35  
36  import javax.mail.Session;
37  import javax.mail.Authenticator;
38  import javax.mail.PasswordAuthentication;
39  import javax.mail.Transport;
40  import javax.mail.Message;
41  import javax.mail.MessagingException;
42  import javax.mail.internet.MimeMessage;
43  import javax.mail.Multipart;
44  import javax.mail.internet.MimeMultipart;
45  import javax.mail.internet.MimeBodyPart;
46  import javax.mail.internet.InternetAddress;
47  import javax.mail.internet.AddressException;
48  
49  /***
50     Send an e-mail when a specific logging event occurs, typically on
51     errors or fatal errors.
52  
53     <p>The number of logging events delivered in this e-mail depend on
54     the value of <b>BufferSize</b> option. The
55     <code>SMTPAppender</code> keeps only the last
56     <code>BufferSize</code> logging events in its cyclic buffer. This
57     keeps memory requirements at a reasonable level while still
58     delivering useful application context.
59  
60     By default, an email message will be sent when an ERROR or higher
61     severity message is appended.  The triggering criteria can be
62     modified by setting the evaluatorClass property with the name
63     of a class implementing TriggeringEventEvaluator, setting the evaluator
64     property with an instance of TriggeringEventEvaluator or
65     nesting a triggeringPolicy element where the specified
66     class implements TriggeringEventEvaluator.
67  
68     @author Ceki G&uuml;lc&uuml;
69     @since 1.0 */
70  public class SMTPAppender extends AppenderSkeleton
71          implements UnrecognizedElementHandler {
72    private String to;
73    /***
74     * Comma separated list of cc recipients.
75     */
76    private String cc;  
77    /***
78     * Comma separated list of bcc recipients.
79     */
80    private String bcc;  
81    private String from;
82    private String subject;
83    private String smtpHost;
84    private String smtpUsername;
85    private String smtpPassword;
86    private boolean smtpDebug = false;
87    private int bufferSize = 512;
88    private boolean locationInfo = false;
89  
90    protected CyclicBuffer cb = new CyclicBuffer(bufferSize);
91    protected Message msg;
92  
93    protected TriggeringEventEvaluator evaluator;
94  
95  
96  
97    /***
98       The default constructor will instantiate the appender with a
99       {@link TriggeringEventEvaluator} that will trigger on events with
100      level ERROR or higher.*/
101   public
102   SMTPAppender() {
103     this(new DefaultEvaluator());
104   }
105 
106 
107   /***
108      Use <code>evaluator</code> passed as parameter as the {@link
109      TriggeringEventEvaluator} for this SMTPAppender.  */
110   public
111   SMTPAppender(TriggeringEventEvaluator evaluator) {
112     this.evaluator = evaluator;
113   }
114 
115 
116   /***
117      Activate the specified options, such as the smtp host, the
118      recipient, from, etc. */
119   public
120   void activateOptions() {
121     Session session = createSession();
122     msg = new MimeMessage(session);
123 
124      try {
125         addressMessage(msg);
126         if(subject != null) {
127 	       msg.setSubject(subject);
128 	    }
129      } catch(MessagingException e) {
130        LogLog.error("Could not activate SMTPAppender options.", e );
131      }
132 
133      if (evaluator instanceof OptionHandler) {
134          ((OptionHandler) evaluator).activateOptions();
135      }
136   }
137   
138   /***
139    *   Address message.
140    *   @param msg message, may not be null.
141    *   @throws MessagingException thrown if error addressing message. 
142    */
143   protected void addressMessage(final Message msg) throws MessagingException {
144        if (from != null) {
145 	 		msg.setFrom(getAddress(from));
146        } else {
147 	 		msg.setFrom();
148 	   }
149 
150        if (to != null && to.length() > 0) {
151              msg.setRecipients(Message.RecipientType.TO, parseAddress(to));
152        }
153 
154       //Add CC receipients if defined.
155 	  if (cc != null && cc.length() > 0) {
156 		msg.setRecipients(Message.RecipientType.CC, parseAddress(cc));
157 	  }
158 
159       //Add BCC receipients if defined.
160 	  if (bcc != null && bcc.length() > 0) {
161 		msg.setRecipients(Message.RecipientType.BCC, parseAddress(bcc));
162 	  }
163   }
164   
165   /***
166    *  Create mail session.
167    *  @return mail session, may not be null.
168    */
169   protected Session createSession() {
170     Properties props = null;
171     try {
172         props = new Properties (System.getProperties());
173     } catch(SecurityException ex) {
174         props = new Properties();
175     }
176     if (smtpHost != null) {
177       props.put("mail.smtp.host", smtpHost);
178     }
179     
180     Authenticator auth = null;
181     if(smtpPassword != null && smtpUsername != null) {
182       props.put("mail.smtp.auth", "true");
183       auth = new Authenticator() {
184         protected PasswordAuthentication getPasswordAuthentication() {
185           return new PasswordAuthentication(smtpUsername, smtpPassword);
186         }
187       };
188     }
189     Session session = Session.getInstance(props, auth);
190     if (smtpDebug) {
191         session.setDebug(smtpDebug);
192     }
193     return session;
194   }
195 
196   /***
197      Perform SMTPAppender specific appending actions, mainly adding
198      the event to a cyclic buffer and checking if the event triggers
199      an e-mail to be sent. */
200   public
201   void append(LoggingEvent event) {
202 
203     if(!checkEntryConditions()) {
204       return;
205     }
206 
207     event.getThreadName();
208     event.getNDC();
209     event.getMDCCopy();
210     if(locationInfo) {
211       event.getLocationInformation();
212     }
213     cb.add(event);
214     if(evaluator.isTriggeringEvent(event)) {
215       sendBuffer();
216     }
217   }
218 
219  /***
220      This method determines if there is a sense in attempting to append.
221 
222      <p>It checks whether there is a set output target and also if
223      there is a set layout. If these checks fail, then the boolean
224      value <code>false</code> is returned. */
225   protected
226   boolean checkEntryConditions() {
227     if(this.msg == null) {
228       errorHandler.error("Message object not configured.");
229       return false;
230     }
231 
232     if(this.evaluator == null) {
233       errorHandler.error("No TriggeringEventEvaluator is set for appender ["+
234 			 name+"].");
235       return false;
236     }
237 
238 
239     if(this.layout == null) {
240       errorHandler.error("No layout set for appender named ["+name+"].");
241       return false;
242     }
243     return true;
244   }
245 
246 
247   synchronized
248   public
249   void close() {
250     this.closed = true;
251   }
252 
253   InternetAddress getAddress(String addressStr) {
254     try {
255       return new InternetAddress(addressStr);
256     } catch(AddressException e) {
257       errorHandler.error("Could not parse address ["+addressStr+"].", e,
258 			 ErrorCode.ADDRESS_PARSE_FAILURE);
259       return null;
260     }
261   }
262 
263   InternetAddress[] parseAddress(String addressStr) {
264     try {
265       return InternetAddress.parse(addressStr, true);
266     } catch(AddressException e) {
267       errorHandler.error("Could not parse address ["+addressStr+"].", e,
268 			 ErrorCode.ADDRESS_PARSE_FAILURE);
269       return null;
270     }
271   }
272 
273   /***
274      Returns value of the <b>To</b> option.
275    */
276   public
277   String getTo() {
278     return to;
279   }
280 
281 
282   /***
283      The <code>SMTPAppender</code> requires a {@link
284      org.apache.log4j.Layout layout}.  */
285   public
286   boolean requiresLayout() {
287     return true;
288   }
289 
290   /***
291      Send the contents of the cyclic buffer as an e-mail message.
292    */
293   protected
294   void sendBuffer() {
295 
296     // Note: this code already owns the monitor for this
297     // appender. This frees us from needing to synchronize on 'cb'.
298     try {
299       MimeBodyPart part = new MimeBodyPart();
300 
301       StringBuffer sbuf = new StringBuffer();
302       String t = layout.getHeader();
303       if(t != null)
304 	sbuf.append(t);
305       int len =  cb.length();
306       for(int i = 0; i < len; i++) {
307 	//sbuf.append(MimeUtility.encodeText(layout.format(cb.get())));
308 	LoggingEvent event = cb.get();
309 	sbuf.append(layout.format(event));
310 	if(layout.ignoresThrowable()) {
311 	  String[] s = event.getThrowableStrRep();
312 	  if (s != null) {
313 	    for(int j = 0; j < s.length; j++) {
314 	      sbuf.append(s[j]);
315 	      sbuf.append(Layout.LINE_SEP);
316 	    }
317 	  }
318 	}
319       }
320       t = layout.getFooter();
321       if(t != null)
322 	sbuf.append(t);
323       part.setContent(sbuf.toString(), layout.getContentType());
324 
325       Multipart mp = new MimeMultipart();
326       mp.addBodyPart(part);
327       msg.setContent(mp);
328 
329       msg.setSentDate(new Date());
330       Transport.send(msg);
331     } catch(Exception e) {
332       LogLog.error("Error occured while sending e-mail notification.", e);
333     }
334   }
335 
336 
337 
338   /***
339      Returns value of the <b>EvaluatorClass</b> option.
340    */
341   public
342   String getEvaluatorClass() {
343     return evaluator == null ? null : evaluator.getClass().getName();
344   }
345 
346   /***
347      Returns value of the <b>From</b> option.
348    */
349   public
350   String getFrom() {
351     return from;
352   }
353 
354   /***
355      Returns value of the <b>Subject</b> option.
356    */
357   public
358   String getSubject() {
359     return subject;
360   }
361 
362   /***
363      The <b>From</b> option takes a string value which should be a
364      e-mail address of the sender.
365    */
366   public
367   void setFrom(String from) {
368     this.from = from;
369   }
370 
371   /***
372      The <b>Subject</b> option takes a string value which should be a
373      the subject of the e-mail message.
374    */
375   public
376   void setSubject(String subject) {
377     this.subject = subject;
378   }
379 
380 
381   /***
382      The <b>BufferSize</b> option takes a positive integer
383      representing the maximum number of logging events to collect in a
384      cyclic buffer. When the <code>BufferSize</code> is reached,
385      oldest events are deleted as new events are added to the
386      buffer. By default the size of the cyclic buffer is 512 events.
387    */
388   public
389   void setBufferSize(int bufferSize) {
390     this.bufferSize = bufferSize;
391     cb.resize(bufferSize);
392   }
393 
394   /***
395      The <b>SMTPHost</b> option takes a string value which should be a
396      the host name of the SMTP server that will send the e-mail message.
397    */
398   public
399   void setSMTPHost(String smtpHost) {
400     this.smtpHost = smtpHost;
401   }
402 
403   /***
404      Returns value of the <b>SMTPHost</b> option.
405    */
406   public
407   String getSMTPHost() {
408     return smtpHost;
409   }
410 
411   /***
412      The <b>To</b> option takes a string value which should be a
413      comma separated list of e-mail address of the recipients.
414    */
415   public
416   void setTo(String to) {
417     this.to = to;
418   }
419 
420 
421 
422   /***
423      Returns value of the <b>BufferSize</b> option.
424    */
425   public
426   int getBufferSize() {
427     return bufferSize;
428   }
429 
430   /***
431      The <b>EvaluatorClass</b> option takes a string value
432      representing the name of the class implementing the {@link
433      TriggeringEventEvaluator} interface. A corresponding object will
434      be instantiated and assigned as the triggering event evaluator
435      for the SMTPAppender.
436    */
437   public
438   void setEvaluatorClass(String value) {
439       evaluator = (TriggeringEventEvaluator)
440                 OptionConverter.instantiateByClassName(value,
441 					   TriggeringEventEvaluator.class,
442 						       evaluator);
443   }
444 
445 
446   /***
447      The <b>LocationInfo</b> option takes a boolean value. By
448      default, it is set to false which means there will be no effort
449      to extract the location information related to the event. As a
450      result, the layout that formats the events as they are sent out
451      in an e-mail is likely to place the wrong location information
452      (if present in the format).
453 
454      <p>Location information extraction is comparatively very slow and
455      should be avoided unless performance is not a concern.
456    */
457   public
458   void setLocationInfo(boolean locationInfo) {
459     this.locationInfo = locationInfo;
460   }
461 
462   /***
463      Returns value of the <b>LocationInfo</b> option.
464    */
465   public
466   boolean getLocationInfo() {
467     return locationInfo;
468   }
469   
470    /***
471       Set the cc recipient addresses.
472       @param addresses recipient addresses as comma separated string, may be null.
473     */
474    public void setCc(final String addresses) {
475      this.cc = addresses;
476    }
477 
478    /***
479       Get the cc recipient addresses.
480       @return recipient addresses as comma separated string, may be null.
481     */
482     public String getCc() {
483      return cc;
484     }
485 
486    /***
487       Set the bcc recipient addresses.
488       @param addresses recipient addresses as comma separated string, may be null.
489     */
490    public void setBcc(final String addresses) {
491      this.bcc = addresses;
492    }
493 
494    /***
495       Get the bcc recipient addresses.
496       @return recipient addresses as comma separated string, may be null.
497     */
498     public String getBcc() {
499      return bcc;
500     }
501 
502   /***
503    * The <b>SmtpPassword</b> option takes a string value which should be the password required to authenticate against
504    * the mail server.
505    * @param password password, may be null.
506    */
507   public void setSMTPPassword(final String password) {
508     this.smtpPassword = password;
509   }
510  
511   /***
512    * The <b>SmtpUsername</b> option takes a string value which should be the username required to authenticate against
513    * the mail server.
514    * @param username user name, may be null.
515    */
516   public void setSMTPUsername(final String username) {
517     this.smtpUsername = username;
518   }
519 
520   /***
521    * Setting the <b>SmtpDebug</b> option to true will cause the mail session to log its server interaction to stdout.
522    * This can be useful when debuging the appender but should not be used during production because username and
523    * password information is included in the output.
524    * @param debug debug flag.
525    */
526   public void setSMTPDebug(final boolean debug) {
527     this.smtpDebug = debug;
528   }
529   
530   /***
531    * Get SMTP password.
532    * @return SMTP password, may be null.
533    */
534   public String getSMTPPassword() {
535     return smtpPassword;
536   }
537  
538   /***
539    * Get SMTP user name.
540    * @return SMTP user name, may be null.
541    */
542   public String getSMTPUsername() {
543     return smtpUsername;
544   }
545 
546   /***
547    * Get SMTP debug.
548    * @return SMTP debug flag.
549    */
550   public boolean getSMTPDebug() {
551     return smtpDebug;
552   }
553 
554     /***
555      * Sets triggering evaluator.
556      * @param trigger triggering event evaluator.
557      * @since 1.2.15
558      */
559   public final void setEvaluator(final TriggeringEventEvaluator trigger) {
560       if (trigger == null) {
561           throw new NullPointerException("trigger");
562       }
563       this.evaluator = trigger;
564   }
565 
566     /***
567      * Get triggering evaluator.
568      * @return triggering event evaluator.
569      * @since 1.2.15
570      */
571   public final TriggeringEventEvaluator getEvaluator() {
572       return evaluator;
573   }
574 
575   /*** {@inheritDoc} */
576   public boolean parseUnrecognizedElement(final Element element,
577                                           final Properties props) throws Exception {
578       if ("triggeringPolicy".equals(element.getNodeName())) {
579           Object triggerPolicy =
580                   org.apache.log4j.xml.DOMConfigurator.parseElement(
581                           element, props, TriggeringEventEvaluator.class);
582           if (triggerPolicy instanceof TriggeringEventEvaluator) {
583               setEvaluator((TriggeringEventEvaluator) triggerPolicy);
584           }
585           return true;
586       }
587 
588       return false;
589   }
590 
591 }
592 
593 class DefaultEvaluator implements TriggeringEventEvaluator {
594   /***
595      Is this <code>event</code> the e-mail triggering event?
596 
597      <p>This method returns <code>true</code>, if the event level
598      has ERROR level or higher. Otherwise it returns
599      <code>false</code>. */
600   public
601   boolean isTriggeringEvent(LoggingEvent event) {
602     return event.getLevel().isGreaterOrEqual(Level.ERROR);
603   }
604 }