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 }