9/04/2009

Log4J: Sending only ERRORs and FATALs to SMTPAppender

SMTPAppender is a very nice way to notify support personnel about a serious error occuring in our application. Unfortunately when configured by the log4j.properties file, there is no easy way to have sent only the ERROR and FATAL messages. By using the XML to configure Log4J, we can achieve this aim by using filters.

LevelRangeFilter can be configured to have sent only ERRORs and FATALs:

<appender class="org.apache.log4j.net.SMTPAppender" name="smtp">
  <param name="To" value="Support@yourcompany.com" />
  <param name="From" value="" />
  <param name="Subject" value="An error occured" />
  <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{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>

If you want to include information about the exception in the subject, see here.

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.

Daily logging with log4j

To generate a daily log with log4j, we have the following options:
  1. Using org.apache.log4j.DailyRollingFileAppender, part of the core Log4j API.
    Configuration in XML:
    <appender name="light" class="org.apache.log4j.DailyRollingFileAppender">
      <param name="File" value="${catalina.base}/logs/light.log" />
      <param name="Append" value="true" />
      <param name="DatePattern" value="'.'yyyy-MM-dd" />
      <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" value="%d{HH:mm:ss} %-5p %t %c{2} - %m%n" />
      </layout>
    </appender>
    
    This will create a file light.log, and at each midnight it will roll, renaming the light.log to something like light.log.2009-09-02
    If you want to configure when the rollover happens, check the possibilities here.
    My only problem here was with the fixed, arbitrary filename (the current date/time is always appended to the end). If you want to change this, you have to look further.

  2. Using org.apache.log4j.rolling.RollingFileAppender from log4j companions you can specify the file-naming conventions as well. Configuration in XML:
    <appender name="light" class="org.apache.log4j.rolling.RollingFileAppender">
      <rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy">
        <param name="FileNamePattern" 
               value="${catalina.base}/logs/light-%d{yyyy-MM}.log" />
      </rollingPolicy>
      <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" 
               value="%d{HH:mm:ss} %-5p %t %c{2} - %m%n" />
      </layout>
    </appender>
    
    By changing FileNamePattern, you can specify the format of the date in the generated log file's name. For more info about the possible configurations see http://logging.apache.org/log4j/companions/extras/apidocs/org/apache/log4j/rolling/RollingFileAppender.html. Don't forget to have the Apache Log4J Extras in your classpath!