All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.log4j.net.SMTPAppender Maven / Gradle / Ivy

There is a newer version: 1.2.25
Show newest version
/*
 * 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); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy