A Grails-aware Log4j SMTP Appender
Proper logging implementation is a complex issue. If you log too much in production you risk wasting CPU and I/O time. If you log too little, you won’t have enough information to diagnose problems. And no matter how efficiently you implement your logging, if you don’t check your logs you won’t know when bad things are happening.
So here’s a Log4j appender that sends emails for messages logged at the
Error
level or higher. Yes, Log4j comes with an appender that sends emails, org.apache.log4j.net.SMTPAppender
. The one I present here is a little simpler, but more importantly it’s Grails-friendly. Specifically it doesn’t send emails unless the app is running in production mode. Error emails are great, but very annoying while debugging 🙂
import grails.util.GrailsUtil; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; import org.apache.log4j.spi.LoggingEvent; import org.codehaus.groovy.grails.commons.GrailsApplication; /** * Replacement for Log4j's SMTP appender that uses the mail service. * * @author Burt */ public class SmtpErrorAppender extends AppenderSkeleton { /** * Perform 1-time initialization. */ public static void register() { if (!GrailsApplication.ENV_PRODUCTION.equals(GrailsUtil.getEnvironment())) { // only configure for production return; } SmtpErrorAppender appender = new SmtpErrorAppender(); Logger.getRootLogger().addAppender(appender); Logger.getLogger("StackTrace").addAppender(appender); } /** * Constructor with default values. */ public SmtpErrorAppender() { setThreshold(Level.ERROR); setLayout(new PatternLayout( "%-27d{dd/MMM/yyyy HH:mm:ss Z}%n%n%-5p%n%n%c%n%n%m%n%n")); } /** * {@inheritDoc} */ @Override public void append(final LoggingEvent event) { if (!event.getLevel().isGreaterOrEqual(Level.ERROR)) { return; } event.getThreadName(); event.getNDC(); event.getMDCCopy(); mailService.sendErrorEmail(layout.format(event), event); } /** * {@inheritDoc} * @see org.apache.log4j.AppenderSkeleton#close() */ public synchronized void close() { closed = true; } /** * {@inheritDoc} * @see org.apache.log4j.AppenderSkeleton#requiresLayout() */ public boolean requiresLayout() { return true; } }
Unfortunately since this class will be compiled with the application code after Config.groovy is loaded, you can’t add it to the Config.groovy log4j section. Instead, just put some initialization code in Bootstrap
:
class BootStrap { def init = { servletContext -> // can't put this in Config.groovy since the class won't have been compiled yet SmtpErrorAppender.register() } }
Also, note that I’ve omitted the setter and declaration of ‘mailService’, which you’ll need to provide. The Grails mail plugin is good candidate. The details of what the email body looks like and who receives the emails is left as an exercise for the reader.
As I said, this is different from the standard email appender, so use that one if you prefer, but either way you should be using something to automatically notify yourself when errors are happening.
One consideration that’s important when implementing this is clarifying what you consider an Error
-level or Fatal
message. Ideally a Fatal
error message should be severe enough that pagers start going off and phones start ringing (that’ll take another appender). So it’s important to decide what gets logged and at what level, so you don’t overwhelm yourself and support people with so many non-critical messages that they start ignoring them.
Hi Burt,
just wondering why you have written your own appender implementation. Both features that make your custom appender “Grails-friendly” (ie. level threshold and environment-awareness) can be easily achieved via the log4j config in Config.groovy:
1) The level threshold can be set via:
log4j.appender.’smtp.threshold’ = ‘ERROR’
2) Environment specific appender config can be done via the built-in Grails environments config syntax, eg:
// root logger for all environments except production
log4j.rootLogger = “logfile”
// appender and root logger config for production
environments {
production {
// appender config here
log4j.rootLogger = “logfile, smtp”
}
}
Or am I missing the point? I use the same approach for the XMPP (Jabber) appender (great stuff by the way, see http://www.ibm.com/developerworks/library/j-instlog/), since I don’t want to get spammed by debug log IMs either 😉
Cheers,
Daniel
A Grails-aware Log4j SMTP Appender…
Proper logging implementation is a complex issue. If you log too much in production you risk wasting CPU and I/O time. If you log too little, you won’t have enough information to diagnose problems. And no matter how efficiently you implement your logg…