Logback: the reliable, generic, fast and flexible logging framework. Copyright (C) 1999-2015, QOS.ch. All rights reserved. This program and the accompanying materials are dual-licensed under either the terms of the Eclipse Public License v1.0 as published by the Eclipse Foundation or (per the licensee's choosing) under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation.
/** * Logback: the reliable, generic, fast and flexible logging framework. * Copyright (C) 1999-2015, QOS.ch. All rights reserved. * * This program and the accompanying materials are dual-licensed under * either the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation * * or (per the licensee's choosing) * * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. */
package ch.qos.logback.core.net; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Properties; import javax.mail.Message; import javax.mail.Multipart; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.naming.Context; import javax.naming.InitialContext; import ch.qos.logback.core.AppenderBase; import ch.qos.logback.core.CoreConstants; import ch.qos.logback.core.Layout; import ch.qos.logback.core.boolex.EvaluationException; import ch.qos.logback.core.boolex.EventEvaluator; import ch.qos.logback.core.helpers.CyclicBuffer; import ch.qos.logback.core.pattern.PatternLayoutBase; import ch.qos.logback.core.sift.DefaultDiscriminator; import ch.qos.logback.core.sift.Discriminator; import ch.qos.logback.core.spi.CyclicBufferTracker; import ch.qos.logback.core.util.ContentTypeUtil; import ch.qos.logback.core.util.OptionHelper; // Contributors: // Andrey Rybin charset encoding support http://jira.qos.ch/browse/LBCORE-69
An abstract class that provides support for sending events to an email address.

See http://logback.qos.ch/manual/appenders.html#SMTPAppender for further documentation.
Author:Ceki Gülcü, Sébastien Pennec
/** * An abstract class that provides support for sending events to an email * address. * <p/> * <p/> * See http://logback.qos.ch/manual/appenders.html#SMTPAppender for further * documentation. * * @author Ceki G&uuml;lc&uuml; * @author S&eacute;bastien Pennec */
public abstract class SMTPAppenderBase<E> extends AppenderBase<E> { static InternetAddress[] EMPTY_IA_ARRAY = new InternetAddress[0]; // ~ 14 days static final long MAX_DELAY_BETWEEN_STATUS_MESSAGES = 1228800 * CoreConstants.MILLIS_IN_ONE_SECOND; long lastTrackerStatusPrint = 0; long delayBetweenStatusMessages = 300 * CoreConstants.MILLIS_IN_ONE_SECOND; protected Layout<E> subjectLayout; protected Layout<E> layout; private List<PatternLayoutBase<E>> toPatternLayoutList = new ArrayList<PatternLayoutBase<E>>(); private String from; private String subjectStr = null; private String smtpHost; private int smtpPort = 25; private boolean starttls = false; private boolean ssl = false; private boolean sessionViaJNDI = false; private String jndiLocation = CoreConstants.JNDI_COMP_PREFIX + "/mail/Session"; String username; String password; String localhost; boolean asynchronousSending = true; private String charsetEncoding = "UTF-8"; protected Session session; protected EventEvaluator<E> eventEvaluator; protected Discriminator<E> discriminator = new DefaultDiscriminator<E>(); protected CyclicBufferTracker<E> cbTracker; private int errorCount = 0;
return a layout for the subject string as appropriate for the module. If the subjectStr parameter is null, then a default value for subjectStr should be used.
Params:
  • subjectStr –
Returns:a layout as appropriate for the module
/** * return a layout for the subject string as appropriate for the module. If the * subjectStr parameter is null, then a default value for subjectStr should be * used. * * @param subjectStr * @return a layout as appropriate for the module */
abstract protected Layout<E> makeSubjectLayout(String subjectStr);
Start the appender
/** * Start the appender */
public void start() { if (cbTracker == null) { cbTracker = new CyclicBufferTracker<E>(); } if (sessionViaJNDI) session = lookupSessionInJNDI(); else session = buildSessionFromProperties(); if (session == null) { addError("Failed to obtain javax.mail.Session. Cannot start."); return; } subjectLayout = makeSubjectLayout(subjectStr); started = true; } private Session lookupSessionInJNDI() { addInfo("Looking up javax.mail.Session at JNDI location [" + jndiLocation + "]"); try { Context initialContext = new InitialContext(); Object obj = initialContext.lookup(jndiLocation); return (Session) obj; } catch (Exception e) { addError("Failed to obtain javax.mail.Session from JNDI location [" + jndiLocation + "]"); return null; } } private Session buildSessionFromProperties() { Properties props = new Properties(OptionHelper.getSystemProperties()); if (smtpHost != null) { props.put("mail.smtp.host", smtpHost); } props.put("mail.smtp.port", Integer.toString(smtpPort)); if (localhost != null) { props.put("mail.smtp.localhost", localhost); } LoginAuthenticator loginAuthenticator = null; if (username != null) { loginAuthenticator = new LoginAuthenticator(username, password); props.put("mail.smtp.auth", "true"); } if (isSTARTTLS() && isSSL()) { addError("Both SSL and StartTLS cannot be enabled simultaneously"); } else { if (isSTARTTLS()) { // see also http://jira.qos.ch/browse/LBCORE-225 props.put("mail.smtp.starttls.enable", "true"); } if (isSSL()) { String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"; props.put("mail.smtp.socketFactory.port", Integer.toString(smtpPort)); props.put("mail.smtp.socketFactory.class", SSL_FACTORY); props.put("mail.smtp.socketFactory.fallback", "true"); } } // props.put("mail.debug", "true"); return Session.getInstance(props, loginAuthenticator); }
Perform SMTPAppender specific appending actions, delegating some of them to a subclass and checking if the event triggers an e-mail to be sent.
/** * Perform SMTPAppender specific appending actions, delegating some of them to * a subclass and checking if the event triggers an e-mail to be sent. */
protected void append(E eventObject) { if (!checkEntryConditions()) { return; } String key = discriminator.getDiscriminatingValue(eventObject); long now = System.currentTimeMillis(); final CyclicBuffer<E> cb = cbTracker.getOrCreate(key, now); subAppend(cb, eventObject); try { if (eventEvaluator.evaluate(eventObject)) { // clone the CyclicBuffer before sending out asynchronously CyclicBuffer<E> cbClone = new CyclicBuffer<E>(cb); // see http://jira.qos.ch/browse/LBCLASSIC-221 cb.clear(); if (asynchronousSending) { // perform actual sending asynchronously SenderRunnable senderRunnable = new SenderRunnable(cbClone, eventObject); context.getExecutorService().execute(senderRunnable); } else { // synchronous sending sendBuffer(cbClone, eventObject); } } } catch (EvaluationException ex) { errorCount++; if (errorCount < CoreConstants.MAX_ERROR_COUNT) { addError("SMTPAppender's EventEvaluator threw an Exception-", ex); } } // immediately remove the buffer if asked by the user if (eventMarksEndOfLife(eventObject)) { cbTracker.endOfLife(key); } cbTracker.removeStaleComponents(now); if (lastTrackerStatusPrint + delayBetweenStatusMessages < now) { addInfo("SMTPAppender [" + name + "] is tracking [" + cbTracker.getComponentCount() + "] buffers"); lastTrackerStatusPrint = now; // quadruple 'delay' assuming less than max delay if (delayBetweenStatusMessages < MAX_DELAY_BETWEEN_STATUS_MESSAGES) { delayBetweenStatusMessages *= 4; } } } abstract protected boolean eventMarksEndOfLife(E eventObject); abstract protected void subAppend(CyclicBuffer<E> cb, E eventObject);
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.
/** * This method determines if there is a sense in attempting to append. * <p/> * <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. */
public boolean checkEntryConditions() { if (!this.started) { addError("Attempting to append to a non-started appender: " + this.getName()); return false; } if (this.eventEvaluator == null) { addError("No EventEvaluator is set for appender [" + name + "]."); return false; } if (this.layout == null) { addError("No layout set for appender named [" + name + "]. For more information, please visit http://logback.qos.ch/codes.html#smtp_no_layout"); return false; } return true; } synchronized public void stop() { this.started = false; } InternetAddress getAddress(String addressStr) { try { return new InternetAddress(addressStr); } catch (AddressException e) { addError("Could not parse address [" + addressStr + "].", e); return null; } } private List<InternetAddress> parseAddress(E event) { int len = toPatternLayoutList.size(); List<InternetAddress> iaList = new ArrayList<InternetAddress>(); for (int i = 0; i < len; i++) { try { PatternLayoutBase<E> emailPL = toPatternLayoutList.get(i); String emailAdrr = emailPL.doLayout(event); if (emailAdrr == null || emailAdrr.length() == 0) { continue; } InternetAddress[] tmp = InternetAddress.parse(emailAdrr, true); iaList.addAll(Arrays.asList(tmp)); } catch (AddressException e) { addError("Could not parse email address for [" + toPatternLayoutList.get(i) + "] for event [" + event + "]", e); return iaList; } } return iaList; }
Returns value of the toList option.
/** * Returns value of the <b>toList</b> option. */
public List<PatternLayoutBase<E>> getToList() { return toPatternLayoutList; }
Send the contents of the cyclic buffer as an e-mail message.
/** * Send the contents of the cyclic buffer as an e-mail message. */
protected void sendBuffer(CyclicBuffer<E> cb, E lastEventObject) { // Note: this code already owns the monitor for this // appender. This frees us from needing to synchronize on 'cb'. try { MimeBodyPart part = new MimeBodyPart(); StringBuffer sbuf = new StringBuffer(); String header = layout.getFileHeader(); if (header != null) { sbuf.append(header); } String presentationHeader = layout.getPresentationHeader(); if (presentationHeader != null) { sbuf.append(presentationHeader); } fillBuffer(cb, sbuf); String presentationFooter = layout.getPresentationFooter(); if (presentationFooter != null) { sbuf.append(presentationFooter); } String footer = layout.getFileFooter(); if (footer != null) { sbuf.append(footer); } String subjectStr = "Undefined subject"; if (subjectLayout != null) { subjectStr = subjectLayout.doLayout(lastEventObject); // The subject must not contain new-line characters, which cause // an SMTP error (LOGBACK-865). Truncate the string at the first // new-line character. int newLinePos = (subjectStr != null) ? subjectStr.indexOf('\n') : -1; if (newLinePos > -1) { subjectStr = subjectStr.substring(0, newLinePos); } } MimeMessage mimeMsg = new MimeMessage(session); if (from != null) { mimeMsg.setFrom(getAddress(from)); } else { mimeMsg.setFrom(); } mimeMsg.setSubject(subjectStr, charsetEncoding); List<InternetAddress> destinationAddresses = parseAddress(lastEventObject); if (destinationAddresses.isEmpty()) { addInfo("Empty destination address. Aborting email transmission"); return; } InternetAddress[] toAddressArray = destinationAddresses.toArray(EMPTY_IA_ARRAY); mimeMsg.setRecipients(Message.RecipientType.TO, toAddressArray); String contentType = layout.getContentType(); if (ContentTypeUtil.isTextual(contentType)) { part.setText(sbuf.toString(), charsetEncoding, ContentTypeUtil.getSubType(contentType)); } else { part.setContent(sbuf.toString(), layout.getContentType()); } Multipart mp = new MimeMultipart(); mp.addBodyPart(part); mimeMsg.setContent(mp); mimeMsg.setSentDate(new Date()); addInfo("About to send out SMTP message \"" + subjectStr + "\" to " + Arrays.toString(toAddressArray)); Transport.send(mimeMsg); } catch (Exception e) { addError("Error occurred while sending e-mail notification.", e); } } abstract protected void fillBuffer(CyclicBuffer<E> cb, StringBuffer sbuf);
Returns value of the From option.
/** * Returns value of the <b>From</b> option. */
public String getFrom() { return from; }
Returns value of the Subject option.
/** * Returns value of the <b>Subject</b> option. */
public String getSubject() { return subjectStr; }
The From option takes a string value which should be a e-mail address of the sender.
/** * 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; }
The Subject option takes a string value which should be a the subject of the e-mail message.
/** * 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.subjectStr = subject; }
Alias for smtpHost
Params:
  • smtpHost –
/** * Alias for smtpHost * * @param smtpHost */
public void setSMTPHost(String smtpHost) { setSmtpHost(smtpHost); }
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.
/** * 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; }
Alias for getSmtpHost().
/** * Alias for getSmtpHost(). */
public String getSMTPHost() { return getSmtpHost(); }
Returns value of the SMTPHost option.
/** * Returns value of the <b>SMTPHost</b> option. */
public String getSmtpHost() { return smtpHost; }
Alias for setSmtpPort.
Params:
  • port –
/** * Alias for {@link #setSmtpPort}. * * @param port */
public void setSMTPPort(int port) { setSmtpPort(port); }
The port where the SMTP server is running. Default value is 25.
Params:
  • port –
/** * The port where the SMTP server is running. Default value is 25. * * @param port */
public void setSmtpPort(int port) { this.smtpPort = port; }
Alias for getSmtpPort
Returns:
/** * Alias for {@link #getSmtpPort} * * @return */
public int getSMTPPort() { return getSmtpPort(); }
Returns:
/** * See {@link #setSmtpPort} * * @return */
public int getSmtpPort() { return smtpPort; } public String getLocalhost() { return localhost; }
Set the "mail.smtp.localhost" property to the value passed as parameter to this method.

Useful in case the hostname for the client host is not fully qualified and as a consequence the SMTP server rejects the clients HELO/EHLO command.

Params:
  • localhost –
/** * Set the "mail.smtp.localhost" property to the value passed as parameter to * this method. * <p/> * <p>Useful in case the hostname for the client host is not fully qualified * and as a consequence the SMTP server rejects the clients HELO/EHLO command. * </p> * * @param localhost */
public void setLocalhost(String localhost) { this.localhost = localhost; } public CyclicBufferTracker<E> getCyclicBufferTracker() { return cbTracker; } public void setCyclicBufferTracker(CyclicBufferTracker<E> cbTracker) { this.cbTracker = cbTracker; } public Discriminator<E> getDiscriminator() { return discriminator; } public void setDiscriminator(Discriminator<E> discriminator) { this.discriminator = discriminator; } public boolean isAsynchronousSending() { return asynchronousSending; }
By default, SMTAppender transmits emails asynchronously. For synchronous email transmission set asynchronousSending to 'false'.
Params:
  • asynchronousSending – determines whether sending is done asynchronously or not
Since:1.0.4
/** * By default, SMTAppender transmits emails asynchronously. For synchronous email transmission set * asynchronousSending to 'false'. * * @param asynchronousSending determines whether sending is done asynchronously or not * @since 1.0.4 */
public void setAsynchronousSending(boolean asynchronousSending) { this.asynchronousSending = asynchronousSending; } public void addTo(String to) { if (to == null || to.length() == 0) { throw new IllegalArgumentException("Null or empty <to> property"); } PatternLayoutBase plb = makeNewToPatternLayout(to.trim()); plb.setContext(context); plb.start(); this.toPatternLayoutList.add(plb); } abstract protected PatternLayoutBase<E> makeNewToPatternLayout(String toPattern); public List<String> getToAsListOfString() { List<String> toList = new ArrayList<String>(); for (PatternLayoutBase plb : toPatternLayoutList) { toList.add(plb.getPattern()); } return toList; } public boolean isSTARTTLS() { return starttls; } public void setSTARTTLS(boolean startTLS) { this.starttls = startTLS; } public boolean isSSL() { return ssl; } public void setSSL(boolean ssl) { this.ssl = ssl; }
The EventEvaluator option takes a string value representing the name of the class implementing the EventEvaluator interface. A corresponding object will be instantiated and assigned as the event evaluator for the SMTPAppender.
/** * The <b>EventEvaluator</b> option takes a string value representing the name * of the class implementing the {@link EventEvaluator} interface. A * corresponding object will be instantiated and assigned as the event * evaluator for the SMTPAppender. */
public void setEvaluator(EventEvaluator<E> eventEvaluator) { this.eventEvaluator = eventEvaluator; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; }
See Also:
Returns:the charset encoding value
/** * @return the charset encoding value * @see #setCharsetEncoding(String) */
public String getCharsetEncoding() { return charsetEncoding; } public String getJndiLocation() { return jndiLocation; }
Set the location where a Session resource is located in JNDI. Default value is "java:comp/env/mail/Session".
Params:
  • jndiLocation –
Since:1.0.6
/** * Set the location where a {@link javax.mail.Session} resource is located in JNDI. Default value is * "java:comp/env/mail/Session". * * @param jndiLocation * @since 1.0.6 */
public void setJndiLocation(String jndiLocation) { this.jndiLocation = jndiLocation; } public boolean isSessionViaJNDI() { return sessionViaJNDI; }
If set to true, a Session resource will be retrieved from JNDI. Default is false.
Params:
  • sessionViaJNDI – whether to obtain a javax.mail.Session by JNDI
Since:1.0.6
/** * If set to true, a {@link javax.mail.Session} resource will be retrieved from JNDI. Default is false. * * @param sessionViaJNDI whether to obtain a javax.mail.Session by JNDI * @since 1.0.6 */
public void setSessionViaJNDI(boolean sessionViaJNDI) { this.sessionViaJNDI = sessionViaJNDI; }
Set the character set encoding of the outgoing email messages. The default encoding is "UTF-8" which usually works well for most purposes.
Params:
  • charsetEncoding –
/** * Set the character set encoding of the outgoing email messages. The default * encoding is "UTF-8" which usually works well for most purposes. * * @param charsetEncoding */
public void setCharsetEncoding(String charsetEncoding) { this.charsetEncoding = charsetEncoding; } public Layout<E> getLayout() { return layout; } public void setLayout(Layout<E> layout) { this.layout = layout; } class SenderRunnable implements Runnable { final CyclicBuffer<E> cyclicBuffer; final E e; SenderRunnable(CyclicBuffer<E> cyclicBuffer, E e) { this.cyclicBuffer = cyclicBuffer; this.e = e; } public void run() { sendBuffer(cyclicBuffer, e); } } }