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

it.openutils.log4j.AlternateSMTPAppender Maven / Gradle / Ivy

There is a newer version: 3.0.11
Show newest version
/**
 *
 * openutils for Log4j (https://github.com/openmindlab/openutils-log4j)
 * Copyright(C) 2005-2019, Openmind S.r.l. http://www.openmindonline.it
 *
 *  Licensed 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 it.openutils.log4j;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;

import javax.mail.Message;
import javax.mail.MessagingException;
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.mail.internet.MimeUtility;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.PatternLayout;
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.TriggeringEventEvaluator;


/**
 * An alternative of org.apache.log4j.net.SMTPAppender with few differences:
 * 
    *
  • allow customizing the mail subject using a pattern
  • *
  • can be configured with a timeout (in seconds), it will only send messages after this timeout
  • *
  • it will send a mail for every single message (bufferSize is not supported), but it will aggregate any identical * log event received during the timeout. Identical events are log with same message and same stack trace
  • *
* *
 *  <appender name="mail" class="it.openutils.log4j.AlternateSMTPAppender"&lgt;
 *      <param name="Threshold" value="ERROR" /&lgt;
 *      <param name="To" value="[email protected]" /&lgt;
 *      <param name="From" value="[email protected]" /&lgt;
 *      <param name="SMTPHost" value="localhost" /&lgt;
 *      <param name="Timeout" value="180" /&lgt;
 *      <param name="Subject" value="[EXAMPLE] %m" /&lgt;
 *      <layout class="it.openutils.log4j.FilteredPatternLayout"&lgt;
 *          <param name="ConversionPattern" value="%-5p  %c %d{dd.MM.yyyy HH:mm:ss} -- %m%n" /&lgt;
 *          <param name="Header"
 *          value="
 *        ===================================%n
 *        Myapp (production environment)%n
 *        Date: %d{dd.MM.yyyy HH:mm:ss}%n
 *        Url: %X{url}%n
 *        Number of occurrences: %o%n
 *        ===================================%n
 *          " /&lgt;
 *      </layout&lgt;
 *  </appender&lgt;
 * 
* @author Fabrizio Giustina * @version $Id$ */ public class AlternateSMTPAppender extends AppenderSkeleton { protected Map events = new LinkedHashMap() { /** * */ private static final long serialVersionUID = 1L; /** * {@inheritDoc} */ @Override public LoggingEventAggregator put(LoggingEventAggregator key, LoggingEventAggregator value) { LoggingEventAggregator lea = this.get(key); if (lea != null) { lea.incrementCount(); return lea; } return super.put(key, value); } }; protected Message msg; protected TriggeringEventEvaluator evaluator; private String to; private String from; private Layout subjectLayout; private Layout headerLayout; private String smtpHost; private boolean locationInfo; private Timer timer = new Timer("log4j mail appender", true); private TimerTask timerTask; private int timeout; /** * The default constructor will instantiate the appender with a {@link TriggeringEventEvaluator} that will trigger * on events with level ERROR or higher. */ public AlternateSMTPAppender() { this(new DefaultEvaluator()); // force loading this class MimeBodyPart.class.getName(); MimeUtility.class.getName(); } /** * Use evaluator passed as parameter as the {@link TriggeringEventEvaluator} for this SMTPAppender. */ public AlternateSMTPAppender(TriggeringEventEvaluator evaluator) { this.evaluator = evaluator; } /** * Activate the specified options, such as the smtp host, the recipient, from, etc. */ @Override public void activateOptions() { Properties props = new Properties(System.getProperties()); if (smtpHost != null) { props.put("mail.smtp.host", smtpHost); } Session session = Session.getInstance(props, null); // session.setDebug(true); msg = new MimeMessage(session); try { if (from != null) { msg.setFrom(getAddress(from)); } else { msg.setFrom(); } msg.setRecipients(Message.RecipientType.TO, parseAddress(to)); } catch (MessagingException e) { LogLog.error("Could not activate SMTPAppender options.", e); } } /** * 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. * @param event to be logged */ @Override public void append(LoggingEvent event) { if (!checkEntryConditions()) { return; } event.getThreadName(); event.getNDC(); event.getMDCCopy(); if (locationInfo) { event.getLocationInformation(); } LoggingEventAggregator leg = new LoggingEventAggregator(event); if (evaluator.isTriggeringEvent(event)) { if (timeout == 0) { // send immediately synchronized (events) { Collection le = new ArrayList(); le.add(leg); sendBuffer(le); } return; } else { events.put(leg, leg); if (timerTask == null) { timerTask = new TimerTask() { @Override public void run() { Collection le; synchronized (events) { le = new ArrayList(events.values()); events.clear(); timerTask = null; } sendBuffer(le); } }; this.timer.schedule(this.timerTask, this.timeout * 1000L); } } } } /** * 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. * @return 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; } public synchronized void close() { this.closed = true; } 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; } } /** * @return value of the To option. */ public String getTo() { return to; } /** * The SMTPAppender requires a {@link org.apache.log4j.Layout layout}. * @return if requires layout */ public boolean requiresLayout() { return true; } /** * Send the contents of the cyclic buffer as an e-mail message. * @param eventsCollection to be sent */ protected void sendBuffer(Collection eventsCollection) { // Note: this code already owns the monitor for this // appender. This frees us from needing to synchronize on 'cb'. try { for (LoggingEventAggregator lea : eventsCollection) { MimeBodyPart part = new MimeBodyPart(); StringBuffer sbuf = new StringBuffer(); LoggingEvent event = lea.getLoggingEvent(); if (layout.getHeader() != null) { if (headerLayout == null) { String header = layout.getHeader(); header = StringUtils.replace(header, "%o", "{number_of_occurrences}"); headerLayout = new PatternLayout(header); } String t = headerLayout.format(event); if (t != null) { t = StringUtils.replace(t, "{number_of_occurrences}", Integer.toString(lea.getCount())); sbuf.append(t); sbuf.append("\n"); } } if (this.subjectLayout != null) { String subject = this.subjectLayout.format(event); if (subject != null) { subject = subject.trim(); if (subject.indexOf("\n") > 0) { subject = subject.substring(0, subject.indexOf("\n")); } } this.msg.setSubject(subject); } sbuf.append(layout.format(event)); if (layout.ignoresThrowable()) { String[] s = event.getThrowableStrRep(); if (s != null) { for (String element : s) { sbuf.append(element); } } } String t = layout.getFooter(); if (t != null) { t = StringUtils.replace(t, "%n", Layout.LINE_SEP); sbuf.append(t); } part.setContent(sbuf.toString(), layout.getContentType()); Multipart mp = new MimeMultipart(); mp.addBodyPart(part); msg.setContent(mp); msg.setSentDate(new Date()); Transport.send(msg); } } catch (Exception e) { LogLog.error("Error occured while sending e-mail notification.", e); } } /** * @return value of the EvaluatorClass option. */ public String getEvaluatorClass() { return evaluator == null ? null : evaluator.getClass().getName(); } /** * @return value of the From option. */ public String getFrom() { return from; } /** * @return value of the Subject option. */ public String getSubject() { return subjectLayout.toString(); } /** * @param from option takes a string value which should be a e-mail address of the sender. */ public void setFrom(String from) { this.from = from; } /** * @param subjectPattern option takes a string value which should be a the subject of the e-mail message. */ public void setSubject(String subjectPattern) { this.subjectLayout = new PatternLayout(subjectPattern); } /** * This option is ignored! * @param bufferSize this param will be ignored */ @Deprecated public void setBufferSize(int bufferSize) { // kept as deprecated LogLog.warn("BufferSize property is deprecated for " + getClass().getName()); } /** * @param 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; } /** * @return value of the SMTPHost option. */ public String getSMTPHost() { return smtpHost; } /** * @param 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; } /** * @return value of the BufferSize option. */ public int getBufferSize() { return 0; } /** * @param value as 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); } /** * @param value as TriggeringEventEvaluator */ public void setEvaluator(TriggeringEventEvaluator value) { evaluator = value; } /** * @param 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; } /** * @return value of the LocationInfo option. */ public boolean getLocationInfo() { return locationInfo; } /** * Returns the timeout. * @return the timeout */ public int getTimeout() { return timeout; } /** * Sets the timeout. * @param timeout the timeout to set */ public void setTimeout(int timeout) { this.timeout = timeout; } } class LoggingEventAggregator { private LoggingEvent loggingEvent; private int count; public LoggingEventAggregator(LoggingEvent loggingEvent) { this.loggingEvent = loggingEvent; this.count = 1; } /** * Returns the loggingEvent. * @return the loggingEvent */ public LoggingEvent getLoggingEvent() { return loggingEvent; } /** * @return the count */ public int getCount() { return count; } /** * @param count the count to set */ public void setCount(int count) { this.count = count; } public void incrementCount() { count++; } /** * {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; int result = 1; Object lem = loggingEvent.getMessage(); String[] thstr = loggingEvent.getThrowableStrRep(); result = prime * result + ((lem == null) ? 0 : lem.hashCode()); result = prime * result + Arrays.hashCode(thstr); return result; } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final LoggingEventAggregator other = (LoggingEventAggregator) obj; Object lem = loggingEvent.getMessage(); String[] thstr = (String[]) ObjectUtils.defaultIfNull(loggingEvent.getThrowableStrRep(), new String[0]); Object otherLem = other.loggingEvent.getMessage(); String[] otherThstr = (String[]) ObjectUtils.defaultIfNull( other.loggingEvent.getThrowableStrRep(), new String[0]); int length = Math.min(otherThstr.length, thstr.length); length = Math.min(10, length); otherThstr = (String[]) ArrayUtils.subarray(otherThstr, 0, length); String[] thisThstr = (String[]) ArrayUtils.subarray(thstr, 0, length); if (lem == null) { if (otherLem != null) { return false; } } else if (!lem.equals(otherLem)) { return false; } if (!Arrays.equals(thisThstr, otherThstr)) { return false; } return true; } } class DefaultEvaluator implements TriggeringEventEvaluator { public boolean isTriggeringEvent(LoggingEvent event) { return event.getLevel().isGreaterOrEqual(Level.ERROR); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy