X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=srcjar%2Forg%2Fapache%2Flog4j%2Fnet%2FSMTPAppender.java;fp=srcjar%2Forg%2Fapache%2Flog4j%2Fnet%2FSMTPAppender.java;h=9162d07a5307a9d1ad9db26b363c7b976d118494;hb=2d6292c0377bc6b773c6844a45d3f2c5fac352c7;hp=0000000000000000000000000000000000000000;hpb=954af328a2a6a0055572cd1a09ee035301222574;p=jalview.git diff --git a/srcjar/org/apache/log4j/net/SMTPAppender.java b/srcjar/org/apache/log4j/net/SMTPAppender.java new file mode 100644 index 0000000..9162d07 --- /dev/null +++ b/srcjar/org/apache/log4j/net/SMTPAppender.java @@ -0,0 +1,788 @@ +/* + * 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. + +

The number of logging events delivered in this e-mail depend on + the value of BufferSize option. The + SMTPAppender keeps only the last + BufferSize 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 evaluator 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. + +

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 false 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 To option. + */ + public + String getTo() { + return to; + } + + + /** + The SMTPAppender requires a {@link + org.apache.log4j.Layout layout}. */ + public + boolean requiresLayout() { + return true; + } + + /** + * Layout body of email message. + * @since 1.2.16 + */ + protected String formatBody() { + + // Note: this code already owns the monitor for this + // appender. This frees us from needing to synchronize on 'cb'. + + 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); + } + + return sbuf.toString(); + } + + /** + Send the contents of the cyclic buffer as an e-mail message. + */ + protected + void sendBuffer() { + + try { + String s = formatBody(); + 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) { + StringBuffer sbuf = new StringBuffer(s); + 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 EvaluatorClass option. + */ + public + String getEvaluatorClass() { + return evaluator == null ? null : evaluator.getClass().getName(); + } + + /** + Returns value of the From 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 Subject option. + */ + public + String getSubject() { + return subject; + } + + /** + The From 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 Subject 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 BufferSize option takes a positive integer + representing the maximum number of logging events to collect in a + cyclic buffer. When the BufferSize 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 SMTPHost 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 SMTPHost option. + */ + public + String getSMTPHost() { + return smtpHost; + } + + /** + The To 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 BufferSize option. + */ + public + int getBufferSize() { + return bufferSize; + } + + /** + The EvaluatorClass 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 LocationInfo 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). + +

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 LocationInfo 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 SmtpPassword 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 SmtpUsername 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 SmtpDebug 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 event the e-mail triggering event? + +

This method returns true, if the event level + has ERROR level or higher. Otherwise it returns + false. */ + public + boolean isTriggeringEvent(LoggingEvent event) { + return event.getLevel().isGreaterOrEqual(Level.ERROR); + } +}