9/02/2009

Log4J SMTPAppender: Exception info in the subject

Log4J's SMTPAppender can be nicely configured to send emails when events are logged with priority ERROR or FATAL in our applications. But as soon as you start using this marvelous feature and you'll begin the get notifications about real exceptions, you'll realize how nice it would be to put some dynamic information into the subject of the message - like the name of the exception.

Unfortunately you can't do this (at least in 1.2.15).

But it is quite easy to implement this feature as a small addendum:
package org.apache.log4j.net;

import java.util.Date;

import javax.mail.Multipart;
import javax.mail.Transport;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;

import org.apache.log4j.Layout;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;

/**
* @author ldomaniczky
*
*/
public class PatternSubjectSMTPAppender extends SMTPAppender {
@Override
protected void sendBuffer() {

// 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 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();

// setting the subject
if (i==0) {
Layout subjectLayout = new PatternLayout(getSubject());
msg.setSubject(MimeUtility.encodeText
(subjectLayout.format(event), "UTF-8", null));
}

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);
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);
}
}
}

As soon as we get the event with cb.get(), the next call does not return it anymore - it considers the event as 'consumed'. Thus, we cannot just override the method, call cb.get() to have access to the event and call the base implementation with super.sendBuffer().

We must override the sendBuffer function in SMTPAppender, copy the appropriate functionality from the superclass, and inject the code to set the custom subject at the very moment of processing the errors.

You can configure the appender with the following code:

<appender name="smtp" class="org.apache.log4j.net.PatternSubjectSMTPAppender">
<param name="To" value="Support@yourcompany.com" />
<param name="From" value="" />
<param name="Subject" value="%c{1}: %m" />
<param name="SMTPHost" value="localhost" />
<param name="BufferSize" value="100" />
<param name="Threshold" value="ERROR" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{MMM-dd HH:mm:ss} %-5p %t %c - %m%n" />
</layout>
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="LevelMin" value="ERROR" />
<param name="LevelMax" value="FATAL" />
</filter>
</appender>


The LevelRangeFilter makes sure only the ERROR and FATAL logs will be sent to the support address.

Note: this has been tested on Log4J 1.2.15.

1 comment:

  1. How would you configure this to work with a properties file in lieu of the properties.xml? I have been researching this for the better part of the day and finally happened upon your post. I have been trying to insert dynamic values into the subject line of the email but have struck out thus far... Thanks for any help or direction you could provide!

    ReplyDelete