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

ch.qos.logback.core.net.SMTPAppenderBase Maven / Gradle / Ivy

There is a newer version: 2.12.15
Show newest version
/**
 * 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ü * @author Sébastien Pennec */ public abstract class SMTPAppenderBase extends AppenderBase { 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 subjectLayout; protected Layout layout; private List> toPatternLayoutList = new ArrayList>(); 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 eventEvaluator; protected Discriminator discriminator = new DefaultDiscriminator(); protected CyclicBufferTracker 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. * * @param subjectStr * @return a layout as appropriate for the module */ abstract protected Layout makeSubjectLayout(String subjectStr); /** * Start the appender */ public void start() { if (cbTracker == null) { cbTracker = new CyclicBufferTracker(); } 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. */ protected void append(E eventObject) { if (!checkEntryConditions()) { return; } String key = discriminator.getDiscriminatingValue(eventObject); long now = System.currentTimeMillis(); final CyclicBuffer cb = cbTracker.getOrCreate(key, now); subAppend(cb, eventObject); try { if (eventEvaluator.evaluate(eventObject)) { // clone the CyclicBuffer before sending out asynchronously CyclicBuffer cbClone = new CyclicBuffer(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 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. */ 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 parseAddress(E event) { int len = toPatternLayoutList.size(); List iaList = new ArrayList(); for (int i = 0; i < len; i++) { try { PatternLayoutBase 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. */ public List> getToList() { return toPatternLayoutList; } /** * Send the contents of the cyclic buffer as an e-mail message. */ protected void sendBuffer(CyclicBuffer 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 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 cb, StringBuffer sbuf); /** * Returns value of the From option. */ public String getFrom() { return from; } /** * Returns value of the Subject option. */ public String getSubject() { return subjectStr; } /** * 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; } /** * The Subject 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 * * @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. */ public void setSmtpHost(String smtpHost) { this.smtpHost = smtpHost; } /** * Alias for getSmtpHost(). */ public String getSMTPHost() { return getSmtpHost(); } /** * Returns value of the SMTPHost option. */ public String getSmtpHost() { return smtpHost; } /** * 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. * * @param port */ public void setSmtpPort(int port) { this.smtpPort = port; } /** * Alias for {@link #getSmtpPort} * * @return */ public int getSMTPPort() { return getSmtpPort(); } /** * 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. *

* * @param localhost */ public void setLocalhost(String localhost) { this.localhost = localhost; } public CyclicBufferTracker getCyclicBufferTracker() { return cbTracker; } public void setCyclicBufferTracker(CyclicBufferTracker cbTracker) { this.cbTracker = cbTracker; } public Discriminator getDiscriminator() { return discriminator; } public void setDiscriminator(Discriminator discriminator) { this.discriminator = discriminator; } public boolean isAsynchronousSending() { return asynchronousSending; } /** * 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 property"); } PatternLayoutBase plb = makeNewToPatternLayout(to.trim()); plb.setContext(context); plb.start(); this.toPatternLayoutList.add(plb); } abstract protected PatternLayoutBase makeNewToPatternLayout(String toPattern); public List getToAsListOfString() { List toList = new ArrayList(); 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 {@link EventEvaluator} interface. A * corresponding object will be instantiated and assigned as the event * evaluator for the SMTPAppender. */ public void setEvaluator(EventEvaluator 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; } /** * @return the charset encoding value * @see #setCharsetEncoding(String) */ public String getCharsetEncoding() { return charsetEncoding; } public String getJndiLocation() { return jndiLocation; } /** * 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 {@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. * * @param charsetEncoding */ public void setCharsetEncoding(String charsetEncoding) { this.charsetEncoding = charsetEncoding; } public Layout getLayout() { return layout; } public void setLayout(Layout layout) { this.layout = layout; } class SenderRunnable implements Runnable { final CyclicBuffer cyclicBuffer; final E e; SenderRunnable(CyclicBuffer cyclicBuffer, E e) { this.cyclicBuffer = cyclicBuffer; this.e = e; } public void run() { sendBuffer(cyclicBuffer, e); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy