February 14, 2010

Sending Log Errors with Log4j and SMTPAppender

Log4j comes with several Appenders and if you like to send mail notification if severe errors occur the you will be pleased to see, that Log4j comes with a SMTPAppender, but there are some pitfalls when using the SMTP appender:
  • The SMTP apppender does not out of the box support a different SMTP port.
  • Using secure connection, such as SSL or TLS.
  • The derived standardized method setThreshold is ignored, and replaced with TriggeringEventEvaluator class.
Configure SMTP appender to use a different port, than the default 25
If your mailserver is configured to answer on a different port than the default 25 for SMTP, you need to configure the underlying javax.mail.Session property "mail.smtp.port". But when looking at the API for SMTPAppender you see no set method for that. What to do? Writing you own extended log4j class? No, that is not neccessary. If you look at the source code of SMTPAppender, you see the following in the createSession():

Properties props = new Properties (System.getProperties());


These means that SMTPAppender is reading system properties, which means you can add the following argument when starting you application.

$ -Dmail.smtp.port=587


This is a not gracefefully way, and sometimes also not applicable, because you are not in charge of the deployment environment. And what happens when you have two different application hosted on the same server, that wants to use different ports?
There is also the oppurtunity to do it programmatically:

System.setProperty("mail.smtp.port", "587");


This has also drawbacks. For examaple if your application is a J2EE application or a web application your code is not the main start class. You can of course always write a boot strap class, but what happens when you have several bootstrap classes, then things starts to get more hard to overlook for a more junior programmer.

Another way if you have problems settings the System Property is to create a class that implements the interface org.apache.log4j.spi.TriggeringEventEvaluator and there set the system properties and then in your log4j properties or xml file override the default EvaluatorClass for SMTPAppender. This is a little hacky, but it will get the work done and it keeps your code clean from log4j configuration code that should in the first place be placed in the log4j configration file.

Also what is lacking is, if you are deploying to a container you might want to define your java.mail.Session properties in the container and then bind them to JNDI, then it should be nice if Log4j could read these property from JNDI, but that is not possible.

Using Secure Connection
The next problem comes when your mailserver uses secure connection as TLS or SSL, then you need to add the following javax.mail.Session properties:

System.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
System.setProperty("mail.smtp.socketFactory.port", "465");
System.setProperty("mail.smtp.socketFactory.fallback", "false");


Bellow follows the source code for the example.
# configure the root logger
log4j.rootLogger=INFO, STDOUT

# configure the console appender
log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
log4j.appender.STDOUT.Target=System.out
log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
log4j.appender.STDOUT.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%p] %c:%L - %m%n

log4j.logger.se.msc.examples.log4j.Log4jNoSecureConnectionTest=ERROR, SMTP_NO_SECURE_CONNECTION

log4j.appender.SMTP_NO_SECURE_CONNECTION.layout=org.apache.log4j.SimpleLayout
log4j.appender.SMTP_NO_SECURE_CONNECTION=org.apache.log4j.net.SMTPAppender
#log4j.appender.SMTP_NO_SECURE_CONNECTION.Bcc=
# the maximum number of logging events to collect in a cyclic buffer
log4j.appender.SMTP_NO_SECURE_CONNECTION.BufferSize=1
#log4j.appender.SMTP_NO_SECURE_CONNECTION.Cc=
log4j.appender.SMTP_NO_SECURE_CONNECTION.From=system.alert@examples.msc.se
log4j.appender.SMTP_NO_SECURE_CONNECTION.LocationInfo=true
log4j.appender.SMTP_NO_SECURE_CONNECTION.SMTPDebug=true
log4j.appender.SMTP_NO_SECURE_CONNECTION.SMTPHost=smtp.XXX.se
log4j.appender.SMTP_NO_SECURE_CONNECTION.SMTPPassword=YYY
log4j.appender.SMTP_NO_SECURE_CONNECTION.SMTPUsername=ZZZ
log4j.appender.SMTP_NO_SECURE_CONNECTION.Subject=System Alert NO SECURE CONNECTION
log4j.appender.SMTP_NO_SECURE_CONNECTION.To=DDD

log4j.logger.se.msc.examples.log4j.Log4jTLSTest=ERROR, SMTP_TLS

log4j.appender.SMTP_TLS.layout=org.apache.log4j.SimpleLayout
log4j.appender.SMTP_TLS=org.apache.log4j.net.SMTPAppender
#log4j.appender.SMTP_TLS.Bcc=
# the maximum number of logging events to collect in a cyclic buffer
log4j.appender.SMTP_TLS.BufferSize=1
#log4j.appender.SMTP_TLS.Cc=
log4j.appender.SMTP_TLS.From=system.alert@examples.msc.se
log4j.appender.SMTP_TLS.LocationInfo=true
log4j.appender.SMTP_TLS.SMTPDebug=true
log4j.appender.SMTP_TLS.SMTPHost=smtp.gmail.com
log4j.appender.SMTP_TLS.SMTPPassword=FOO
log4j.appender.SMTP_TLS.SMTPUsername=foo.bar@gmail.com
log4j.appender.SMTP_TLS.Subject=System Alert TLS
log4j.appender.SMTP_TLS.To=foo.bar@gmail.com



package se.msc.example.log4j;

import org.apache.log4j.Logger;
import org.junit.Test;

public class Log4jNoSecureConnectionTest {
 private static final Logger log = Logger.getLogger(Log4jNoSecureConnectionTest.class);
 
 @Test
 public void testFoo() throws Exception {
  // Either set smtp properties programmatically or via JVM argument -D
  System.setProperty("mail.smtp.port", "587");
  log.error("Fatal log message.", new NullPointerException("Null pointer exceptopn."));
 }

}



package se.msc.example.log4j;

import org.apache.log4j.Logger;
import org.junit.Test;

public class Log4jTLSTest {
 private static final Logger log = Logger.getLogger(Log4jTLSTest.class);
 
 @Test
 public void testFoo() throws Exception {
  // Either set smtp properties programmatically or via JVM argument -D
  System.setProperty("mail.smtp.port", "465");
  System.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
  System.setProperty("mail.smtp.socketFactory.port", "465");
  System.setProperty("mail.smtp.socketFactory.fallback", "false");
  log.error("Fatal log message.", new NullPointerException("Null pointer exceptopn."));
 }

}



<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

 <modelVersion>4.0.0</modelVersion>
 <groupId>se.msc.example</groupId>
 <artifactId>log4j</artifactId>
 <packaging>jar</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>Log4J Test</name>
 <url>http://www.msc.se/example/log4j</url>

 <developers>
  <developer>
   <id>magnus.k.karlsson</id>
   <name>Magnus K Karlsson</name>
   <email>magnus.k.karlsson@msc.se</email>
   <organization>MSC</organization>
  </developer>
 </developers>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties>

 <dependencies>
  <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.15</version>
  </dependency>
  <!-- sun java mail -->
  <dependency>
   <groupId>javax.mail</groupId>
   <artifactId>mail</artifactId>
   <version>1.4.1</version>
  </dependency>  
  <!-- unit testing -->  
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.7</version>
   <scope>test</scope>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <!-- to compile with JDK 1.6 -->
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <source>1.6</source>
     <target>1.6</target>
    </configuration>
   </plugin>
  </plugins>
 </build>

</project>

No comments: