| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.log4j.net; |
| |
| import org.apache.log4j.AppenderSkeleton; |
| import org.apache.log4j.Layout; |
| import org.apache.log4j.Level; |
| import org.apache.log4j.helpers.CyclicBuffer; |
| import org.apache.log4j.helpers.LogLog; |
| import org.apache.log4j.helpers.OptionConverter; |
| import org.apache.log4j.spi.ErrorCode; |
| import org.apache.log4j.spi.LoggingEvent; |
| import org.apache.log4j.spi.OptionHandler; |
| import org.apache.log4j.spi.TriggeringEventEvaluator; |
| import org.apache.log4j.xml.UnrecognizedElementHandler; |
| import org.w3c.dom.Element; |
| |
| import javax.mail.Authenticator; |
| import javax.mail.Message; |
| import javax.mail.MessagingException; |
| import javax.mail.Multipart; |
| import javax.mail.PasswordAuthentication; |
| import javax.mail.Session; |
| import javax.mail.Transport; |
| import javax.mail.internet.AddressException; |
| import javax.mail.internet.InternetAddress; |
| import javax.mail.internet.InternetHeaders; |
| import javax.mail.internet.MimeBodyPart; |
| import javax.mail.internet.MimeMessage; |
| import javax.mail.internet.MimeMultipart; |
| import javax.mail.internet.MimeUtility; |
| import java.io.ByteArrayOutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.UnsupportedEncodingException; |
| import java.io.Writer; |
| import java.util.Date; |
| import java.util.Properties; |
| |
| /** |
| Send an e-mail when a specific logging event occurs, typically on |
| errors or fatal errors. |
| |
| <p>The number of logging events delivered in this e-mail depend on |
| the value of <b>BufferSize</b> option. The |
| <code>SMTPAppender</code> keeps only the last |
| <code>BufferSize</code> logging events in its cyclic buffer. This |
| keeps memory requirements at a reasonable level while still |
| delivering useful application context. |
| |
| By default, an email message will be sent when an ERROR or higher |
| severity message is appended. The triggering criteria can be |
| modified by setting the evaluatorClass property with the name |
| of a class implementing TriggeringEventEvaluator, setting the evaluator |
| property with an instance of TriggeringEventEvaluator or |
| nesting a triggeringPolicy element where the specified |
| class implements TriggeringEventEvaluator. |
| |
| This class has implemented UnrecognizedElementHandler since 1.2.15. |
| |
| Since 1.2.16, SMTP over SSL is supported by setting SMTPProtocol to "smpts". |
| |
| @author Ceki Gülcü |
| @since 1.0 */ |
| public class SMTPAppender extends AppenderSkeleton |
| implements UnrecognizedElementHandler { |
| private String to; |
| /** |
| * Comma separated list of cc recipients. |
| */ |
| private String cc; |
| /** |
| * Comma separated list of bcc recipients. |
| */ |
| private String bcc; |
| private String from; |
| /** |
| * Comma separated list of replyTo addresses. |
| */ |
| private String replyTo; |
| private String subject; |
| private String smtpHost; |
| private String smtpUsername; |
| private String smtpPassword; |
| private String smtpProtocol; |
| private int smtpPort = -1; |
| private boolean smtpDebug = false; |
| private int bufferSize = 512; |
| private boolean locationInfo = false; |
| private boolean sendOnClose = false; |
| |
| protected CyclicBuffer cb = new CyclicBuffer(bufferSize); |
| protected Message msg; |
| |
| protected TriggeringEventEvaluator evaluator; |
| |
| |
| |
| /** |
| The default constructor will instantiate the appender with a |
| {@link TriggeringEventEvaluator} that will trigger on events with |
| level ERROR or higher.*/ |
| public |
| SMTPAppender() { |
| this(new DefaultEvaluator()); |
| } |
| |
| |
| /** |
| Use <code>evaluator</code> passed as parameter as the {@link |
| TriggeringEventEvaluator} for this SMTPAppender. */ |
| public |
| SMTPAppender(TriggeringEventEvaluator evaluator) { |
| this.evaluator = evaluator; |
| } |
| |
| |
| /** |
| Activate the specified options, such as the smtp host, the |
| recipient, from, etc. */ |
| public |
| void activateOptions() { |
| Session session = createSession(); |
| msg = new MimeMessage(session); |
| |
| try { |
| addressMessage(msg); |
| if(subject != null) { |
| try { |
| msg.setSubject(MimeUtility.encodeText(subject, "UTF-8", null)); |
| } catch(UnsupportedEncodingException ex) { |
| LogLog.error("Unable to encode SMTP subject", ex); |
| } |
| } |
| } catch(MessagingException e) { |
| LogLog.error("Could not activate SMTPAppender options.", e ); |
| } |
| |
| if (evaluator instanceof OptionHandler) { |
| ((OptionHandler) evaluator).activateOptions(); |
| } |
| } |
| |
| /** |
| * Address message. |
| * @param msg message, may not be null. |
| * @throws MessagingException thrown if error addressing message. |
| * @since 1.2.14 |
| */ |
| protected void addressMessage(final Message msg) throws MessagingException { |
| if (from != null) { |
| msg.setFrom(getAddress(from)); |
| } else { |
| msg.setFrom(); |
| } |
| |
| //Add ReplyTo addresses if defined. |
| if (replyTo != null && replyTo.length() > 0) { |
| msg.setReplyTo(parseAddress(replyTo)); |
| } |
| |
| if (to != null && to.length() > 0) { |
| msg.setRecipients(Message.RecipientType.TO, parseAddress(to)); |
| } |
| |
| //Add CC receipients if defined. |
| if (cc != null && cc.length() > 0) { |
| msg.setRecipients(Message.RecipientType.CC, parseAddress(cc)); |
| } |
| |
| //Add BCC receipients if defined. |
| if (bcc != null && bcc.length() > 0) { |
| msg.setRecipients(Message.RecipientType.BCC, parseAddress(bcc)); |
| } |
| } |
| |
| /** |
| * Create mail session. |
| * @return mail session, may not be null. |
| * @since 1.2.14 |
| */ |
| protected Session createSession() { |
| Properties props = null; |
| try { |
| props = new Properties (System.getProperties()); |
| } catch(SecurityException ex) { |
| props = new Properties(); |
| } |
| |
| String prefix = "mail.smtp"; |
| if (smtpProtocol != null) { |
| props.put("mail.transport.protocol", smtpProtocol); |
| prefix = "mail." + smtpProtocol; |
| } |
| if (smtpHost != null) { |
| props.put(prefix + ".host", smtpHost); |
| } |
| if (smtpPort > 0) { |
| props.put(prefix + ".port", String.valueOf(smtpPort)); |
| } |
| |
| Authenticator auth = null; |
| if(smtpPassword != null && smtpUsername != null) { |
| props.put(prefix + ".auth", "true"); |
| auth = new Authenticator() { |
| protected PasswordAuthentication getPasswordAuthentication() { |
| return new PasswordAuthentication(smtpUsername, smtpPassword); |
| } |
| }; |
| } |
| Session session = Session.getInstance(props, auth); |
| if (smtpProtocol != null) { |
| session.setProtocolForAddress("rfc822", smtpProtocol); |
| } |
| if (smtpDebug) { |
| session.setDebug(smtpDebug); |
| } |
| return session; |
| } |
| |
| /** |
| Perform SMTPAppender specific appending actions, mainly adding |
| the event to a cyclic buffer and checking if the event triggers |
| an e-mail to be sent. */ |
| public |
| void append(LoggingEvent event) { |
| |
| if(!checkEntryConditions()) { |
| return; |
| } |
| |
| event.getThreadName(); |
| event.getNDC(); |
| event.getMDCCopy(); |
| if(locationInfo) { |
| event.getLocationInformation(); |
| } |
| event.getRenderedMessage(); |
| event.getThrowableStrRep(); |
| cb.add(event); |
| if(evaluator.isTriggeringEvent(event)) { |
| sendBuffer(); |
| } |
| } |
| |
| /** |
| This method determines if there is a sense in attempting to append. |
| |
| <p>It checks whether there is a set output target and also if |
| there is a set layout. If these checks fail, then the boolean |
| value <code>false</code> is returned. */ |
| protected |
| boolean checkEntryConditions() { |
| if(this.msg == null) { |
| errorHandler.error("Message object not configured."); |
| return false; |
| } |
| |
| if(this.evaluator == null) { |
| errorHandler.error("No TriggeringEventEvaluator is set for appender ["+ |
| name+"]."); |
| return false; |
| } |
| |
| |
| if(this.layout == null) { |
| errorHandler.error("No layout set for appender named ["+name+"]."); |
| return false; |
| } |
| return true; |
| } |
| |
| |
| synchronized |
| public |
| void close() { |
| this.closed = true; |
| if (sendOnClose && cb.length() > 0) { |
| sendBuffer(); |
| } |
| } |
| |
| InternetAddress getAddress(String addressStr) { |
| try { |
| return new InternetAddress(addressStr); |
| } catch(AddressException e) { |
| errorHandler.error("Could not parse address ["+addressStr+"].", e, |
| ErrorCode.ADDRESS_PARSE_FAILURE); |
| return null; |
| } |
| } |
| |
| InternetAddress[] parseAddress(String addressStr) { |
| try { |
| return InternetAddress.parse(addressStr, true); |
| } catch(AddressException e) { |
| errorHandler.error("Could not parse address ["+addressStr+"].", e, |
| ErrorCode.ADDRESS_PARSE_FAILURE); |
| return null; |
| } |
| } |
| |
| /** |
| Returns value of the <b>To</b> option. |
| */ |
| public |
| String getTo() { |
| return to; |
| } |
| |
| |
| /** |
| The <code>SMTPAppender</code> requires a {@link |
| org.apache.log4j.Layout layout}. */ |
| public |
| boolean requiresLayout() { |
| return true; |
| } |
| |
| /** |
| Send the contents of the cyclic buffer as an e-mail message. |
| */ |
| protected |
| void sendBuffer() { |
| |
| // Note: this code already owns the monitor for this |
| // appender. This frees us from needing to synchronize on 'cb'. |
| try { |
| StringBuffer sbuf = new StringBuffer(); |
| String t = layout.getHeader(); |
| if(t != null) |
| sbuf.append(t); |
| int len = cb.length(); |
| for(int i = 0; i < len; i++) { |
| //sbuf.append(MimeUtility.encodeText(layout.format(cb.get()))); |
| LoggingEvent event = cb.get(); |
| sbuf.append(layout.format(event)); |
| if(layout.ignoresThrowable()) { |
| String[] s = event.getThrowableStrRep(); |
| if (s != null) { |
| for(int j = 0; j < s.length; j++) { |
| sbuf.append(s[j]); |
| sbuf.append(Layout.LINE_SEP); |
| } |
| } |
| } |
| } |
| t = layout.getFooter(); |
| if(t != null) { |
| sbuf.append(t); |
| } |
| |
| String s = sbuf.toString(); |
| boolean allAscii = true; |
| for(int i = 0; i < s.length() && allAscii; i++) { |
| allAscii = s.charAt(i) <= 0x7F; |
| } |
| MimeBodyPart part; |
| if (allAscii) { |
| part = new MimeBodyPart(); |
| part.setContent(s, layout.getContentType()); |
| } else { |
| try { |
| ByteArrayOutputStream os = new ByteArrayOutputStream(); |
| Writer writer = new OutputStreamWriter( |
| MimeUtility.encode(os, "quoted-printable"), "UTF-8"); |
| writer.write(s); |
| writer.close(); |
| InternetHeaders headers = new InternetHeaders(); |
| headers.setHeader("Content-Type", layout.getContentType() + "; charset=UTF-8"); |
| headers.setHeader("Content-Transfer-Encoding", "quoted-printable"); |
| part = new MimeBodyPart(headers, os.toByteArray()); |
| } catch(Exception ex) { |
| for (int i = 0; i < sbuf.length(); i++) { |
| if (sbuf.charAt(i) >= 0x80) { |
| sbuf.setCharAt(i, '?'); |
| } |
| } |
| part = new MimeBodyPart(); |
| part.setContent(sbuf.toString(), layout.getContentType()); |
| } |
| } |
| |
| |
| |
| Multipart mp = new MimeMultipart(); |
| mp.addBodyPart(part); |
| msg.setContent(mp); |
| |
| msg.setSentDate(new Date()); |
| Transport.send(msg); |
| } catch(MessagingException e) { |
| LogLog.error("Error occured while sending e-mail notification.", e); |
| } catch(RuntimeException e) { |
| LogLog.error("Error occured while sending e-mail notification.", e); |
| } |
| } |
| |
| |
| |
| /** |
| Returns value of the <b>EvaluatorClass</b> option. |
| */ |
| public |
| String getEvaluatorClass() { |
| return evaluator == null ? null : evaluator.getClass().getName(); |
| } |
| |
| /** |
| Returns value of the <b>From</b> option. |
| */ |
| public |
| String getFrom() { |
| return from; |
| } |
| |
| /** |
| Get the reply addresses. |
| @return reply addresses as comma separated string, may be null. |
| @since 1.2.16 |
| */ |
| public |
| String getReplyTo() { |
| return replyTo; |
| } |
| |
| /** |
| Returns value of the <b>Subject</b> option. |
| */ |
| public |
| String getSubject() { |
| return subject; |
| } |
| |
| /** |
| The <b>From</b> option takes a string value which should be a |
| e-mail address of the sender. |
| */ |
| public |
| void setFrom(String from) { |
| this.from = from; |
| } |
| |
| /** |
| Set the e-mail addresses to which replies should be directed. |
| @param addresses reply addresses as comma separated string, may be null. |
| @since 1.2.16 |
| */ |
| public |
| void setReplyTo(final String addresses) { |
| this.replyTo = addresses; |
| } |
| |
| |
| /** |
| The <b>Subject</b> option takes a string value which should be a |
| the subject of the e-mail message. |
| */ |
| public |
| void setSubject(String subject) { |
| this.subject = subject; |
| } |
| |
| |
| /** |
| The <b>BufferSize</b> option takes a positive integer |
| representing the maximum number of logging events to collect in a |
| cyclic buffer. When the <code>BufferSize</code> is reached, |
| oldest events are deleted as new events are added to the |
| buffer. By default the size of the cyclic buffer is 512 events. |
| */ |
| public |
| void setBufferSize(int bufferSize) { |
| this.bufferSize = bufferSize; |
| cb.resize(bufferSize); |
| } |
| |
| /** |
| The <b>SMTPHost</b> option takes a string value which should be a |
| the host name of the SMTP server that will send the e-mail message. |
| */ |
| public |
| void setSMTPHost(String smtpHost) { |
| this.smtpHost = smtpHost; |
| } |
| |
| /** |
| Returns value of the <b>SMTPHost</b> option. |
| */ |
| public |
| String getSMTPHost() { |
| return smtpHost; |
| } |
| |
| /** |
| The <b>To</b> option takes a string value which should be a |
| comma separated list of e-mail address of the recipients. |
| */ |
| public |
| void setTo(String to) { |
| this.to = to; |
| } |
| |
| |
| |
| /** |
| Returns value of the <b>BufferSize</b> option. |
| */ |
| public |
| int getBufferSize() { |
| return bufferSize; |
| } |
| |
| /** |
| The <b>EvaluatorClass</b> option takes a string value |
| representing the name of the class implementing the {@link |
| TriggeringEventEvaluator} interface. A corresponding object will |
| be instantiated and assigned as the triggering event evaluator |
| for the SMTPAppender. |
| */ |
| public |
| void setEvaluatorClass(String value) { |
| evaluator = (TriggeringEventEvaluator) |
| OptionConverter.instantiateByClassName(value, |
| TriggeringEventEvaluator.class, |
| evaluator); |
| } |
| |
| |
| /** |
| The <b>LocationInfo</b> option takes a boolean value. By |
| default, it is set to false which means there will be no effort |
| to extract the location information related to the event. As a |
| result, the layout that formats the events as they are sent out |
| in an e-mail is likely to place the wrong location information |
| (if present in the format). |
| |
| <p>Location information extraction is comparatively very slow and |
| should be avoided unless performance is not a concern. |
| */ |
| public |
| void setLocationInfo(boolean locationInfo) { |
| this.locationInfo = locationInfo; |
| } |
| |
| /** |
| Returns value of the <b>LocationInfo</b> option. |
| */ |
| public |
| boolean getLocationInfo() { |
| return locationInfo; |
| } |
| |
| /** |
| Set the cc recipient addresses. |
| @param addresses recipient addresses as comma separated string, may be null. |
| @since 1.2.14 |
| */ |
| public void setCc(final String addresses) { |
| this.cc = addresses; |
| } |
| |
| /** |
| Get the cc recipient addresses. |
| @return recipient addresses as comma separated string, may be null. |
| @since 1.2.14 |
| */ |
| public String getCc() { |
| return cc; |
| } |
| |
| /** |
| Set the bcc recipient addresses. |
| @param addresses recipient addresses as comma separated string, may be null. |
| @since 1.2.14 |
| */ |
| public void setBcc(final String addresses) { |
| this.bcc = addresses; |
| } |
| |
| /** |
| Get the bcc recipient addresses. |
| @return recipient addresses as comma separated string, may be null. |
| @since 1.2.14 |
| */ |
| public String getBcc() { |
| return bcc; |
| } |
| |
| /** |
| * The <b>SmtpPassword</b> option takes a string value which should be the password required to authenticate against |
| * the mail server. |
| * @param password password, may be null. |
| * @since 1.2.14 |
| */ |
| public void setSMTPPassword(final String password) { |
| this.smtpPassword = password; |
| } |
| |
| /** |
| * The <b>SmtpUsername</b> option takes a string value which should be the username required to authenticate against |
| * the mail server. |
| * @param username user name, may be null. |
| * @since 1.2.14 |
| */ |
| public void setSMTPUsername(final String username) { |
| this.smtpUsername = username; |
| } |
| |
| /** |
| * Setting the <b>SmtpDebug</b> option to true will cause the mail session to log its server interaction to stdout. |
| * This can be useful when debuging the appender but should not be used during production because username and |
| * password information is included in the output. |
| * @param debug debug flag. |
| * @since 1.2.14 |
| */ |
| public void setSMTPDebug(final boolean debug) { |
| this.smtpDebug = debug; |
| } |
| |
| /** |
| * Get SMTP password. |
| * @return SMTP password, may be null. |
| * @since 1.2.14 |
| */ |
| public String getSMTPPassword() { |
| return smtpPassword; |
| } |
| |
| /** |
| * Get SMTP user name. |
| * @return SMTP user name, may be null. |
| * @since 1.2.14 |
| */ |
| public String getSMTPUsername() { |
| return smtpUsername; |
| } |
| |
| /** |
| * Get SMTP debug. |
| * @return SMTP debug flag. |
| * @since 1.2.14 |
| */ |
| public boolean getSMTPDebug() { |
| return smtpDebug; |
| } |
| |
| /** |
| * Sets triggering evaluator. |
| * @param trigger triggering event evaluator. |
| * @since 1.2.15 |
| */ |
| public final void setEvaluator(final TriggeringEventEvaluator trigger) { |
| if (trigger == null) { |
| throw new NullPointerException("trigger"); |
| } |
| this.evaluator = trigger; |
| } |
| |
| /** |
| * Get triggering evaluator. |
| * @return triggering event evaluator. |
| * @since 1.2.15 |
| */ |
| public final TriggeringEventEvaluator getEvaluator() { |
| return evaluator; |
| } |
| |
| /** {@inheritDoc} |
| * @since 1.2.15 |
| */ |
| public boolean parseUnrecognizedElement(final Element element, |
| final Properties props) throws Exception { |
| if ("triggeringPolicy".equals(element.getNodeName())) { |
| Object triggerPolicy = |
| org.apache.log4j.xml.DOMConfigurator.parseElement( |
| element, props, TriggeringEventEvaluator.class); |
| if (triggerPolicy instanceof TriggeringEventEvaluator) { |
| setEvaluator((TriggeringEventEvaluator) triggerPolicy); |
| } |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Get transport protocol. |
| * Typically null or "smtps". |
| * |
| * @return transport protocol, may be null. |
| * @since 1.2.16 |
| */ |
| public final String getSMTPProtocol() { |
| return smtpProtocol; |
| } |
| |
| /** |
| * Set transport protocol. |
| * Typically null or "smtps". |
| * |
| * @param val transport protocol, may be null. |
| * @since 1.2.16 |
| */ |
| public final void setSMTPProtocol(final String val) { |
| smtpProtocol = val; |
| } |
| |
| /** |
| * Get port. |
| * |
| * @return port, negative values indicate use of default ports for protocol. |
| * @since 1.2.16 |
| */ |
| public final int getSMTPPort() { |
| return smtpPort; |
| } |
| |
| /** |
| * Set port. |
| * |
| * @param val port, negative values indicate use of default ports for protocol. |
| * @since 1.2.16 |
| */ |
| public final void setSMTPPort(final int val) { |
| smtpPort = val; |
| } |
| |
| /** |
| * Get sendOnClose. |
| * |
| * @return if true all buffered logging events will be sent when the appender is closed. |
| * @since 1.2.16 |
| */ |
| public final boolean getSendOnClose() { |
| return sendOnClose; |
| } |
| |
| /** |
| * Set sendOnClose. |
| * |
| * @param val if true all buffered logging events will be sent when appender is closed. |
| * @since 1.2.16 |
| */ |
| public final void setSendOnClose(final boolean val) { |
| sendOnClose = val; |
| } |
| |
| } |
| |
| class DefaultEvaluator implements TriggeringEventEvaluator { |
| /** |
| Is this <code>event</code> the e-mail triggering event? |
| |
| <p>This method returns <code>true</code>, if the event level |
| has ERROR level or higher. Otherwise it returns |
| <code>false</code>. */ |
| public |
| boolean isTriggeringEvent(LoggingEvent event) { |
| return event.getLevel().isGreaterOrEqual(Level.ERROR); |
| } |
| } |