Setting up a Log4j Socket Server to Email You Errors From Your Java Programs

Most developers quickly realize that being notified of errors within their applications as soon as they happen is a great way to proactively fix problems. This approach has a few benefits. First, the developer does not have to spend time each day scouring logs looking for errors, and then trying to recreate the state of the system at that time to determine what happened. Secondly, the admin can actually call the end user almost immediately after being notified of the error, making them seem omniscient. Imagine being able to call your users as they are encountering the error. They will literally think you are superhuman and be amazed that you knew they needed help.

So how do we do this? Fortunately, if you are developing your application in the java programming language, most of the hard stuff is already done if you use the Apache Log4j logging framework.

Log4j has a class called org.apache.log4j.net.SocketServer. This class is capable of receiving log messages over a socket and processing them through any of the supported Log4j Appenders. For this example we will use the SMTPAppender, and setup a log4j socket server on our unix box to listen for log messages and process them.

The example I'll present below has been tested on a RedHat Linux AS3 server. The steps are as follows:



1) First download the required software. We'll need the following java libraries:
Java Mail
JavaBeans Activation Framework
Log4j



2) Unpack the archive files you downloaded. From each of the distributions we'll need to get the key jar file. In my example I retrieved the following jar files from the archives that I unpacked:

activation-1.0.2.jar
log4j-1.2.13.jar
mail-1.3.1.jar



3) We will now create the directory structure which will hold all the files needed to run our socket server. Login to your unix box and run the following commands as the root user:

mkdir /usr/local/log4j
mkdir /usr/local/log4j/bin
mkdir /usr/local/log4j/config
mkdir /usr/local/log4j/dist
mkdir /usr/local/log4j/dist/lib
mkdir /usr/local/log4j/log



4) Put the jar files you downloaded in step #2 into the directory /usr/local/log4j/dist/lib
The contents of this directory now looks like:

-rw-r--r--  1 root root  54665 Mar 23 14:09 activation-1.0.2.jar
-rw-r--r--  1 root root 358180 Mar 23 14:09 log4j-1.2.13.jar
-rw-r--r--  1 root root 327603 Mar 23 14:09 mail-1.3.1.jar



5) Next we need to configure a properties file containing how we want our logserver to work. We will create a file named /usr/local/log4j/config/logserver.properties, and enter the appropriate configuration to log our messages to a file and email us errors. Paste the following code into the logserver.properties file:

log4j.rootCategory=DEBUG, Default, SMTP

#
# The default file appender
#
log4j.appender.Default=org.apache.log4j.RollingFileAppender
log4j.appender.Default.Threshold=DEBUG
log4j.appender.Default.File=/usr/local/log4j/log/log4j.log
log4j.appender.Default.layout=org.apache.log4j.PatternLayout
log4j.appender.Default.layout.ConversionPattern=%d (${dbUserid}) %c{1} [%p] %x - %m%n
# Do not Truncate if it aleady exists.
log4j.appender.Default.Append=true
log4j.appender.Default.MaxFileSize=1500KB
# Archive log files (one backup file here)
log4j.appender.Default.MaxBackupIndex=10

# 
# The SMTP Appender
#
log4j.debug=true
log4j.appender.SMTP=org.apache.log4j.net.SMTPAppender
log4j.appender.SMTP.Threshold=ERROR
log4j.appender.SMTP.SMTPHost=localhost
log4j.appender.SMTP.To=tima@timarcher.com
log4j.appender.SMTP.From=log4j@timarcher.com
log4j.appender.SMTP.Subject=Application Error Logged
log4j.appender.SMTP.layout=org.apache.log4j.PatternLayout
log4j.appender.SMTP.layout.ConversionPattern=%d (${dbUserid}) %c{1} [%p] %x - %m%n
log4j.appender.SMTP.BufferSize=10

You will need to replace the log4j.appender.SMTP.To parameter above with your email address.



6) Create a file named /usr/local/log4j/bin/startSocketServer.sh
This is the file we'll use to start our socket server. Within the file paste the following code:

#!/bin/sh
export LIB_DIR=/usr/local/log4j/dist/lib
#set -x
/usr/java/current/bin/java -cp $LIB_DIR/activation-1.0.2.jar:$LIB_DIR/mail-1.3.1.jar:$LIB_DIR/log4j-1.2.13.jar \
    org.apache.log4j.net.SocketServer "$@" &

SPID=$!
sleep 1
echo $SPID > /var/run/log4j.pid

Make sure you set the execute bit on this file.

chmod 755 /usr/local/log4j/bin/startSocketServer.sh 



7) Create a unix init script to start our socket server when the system boots. Create the file /etc/init.d/log4j
Paste the following within this file:

#!/bin/bash
#
# Startup script for Log4J Socket Server
#
# chkconfig: - 86 15
# description: Log4J Socket Server
# processname: log4j


JAVA_HOME=/usr/java/current
export JAVA_HOME
start_log4j="/usr/local/log4j/bin/startSocketServer.sh 58778 /usr/local/log4j/config/logserver.p
roperties /usr/local/log4j/log/"
stop_log4j="kill -9 `cat /var/run/log4j.pid`"

start() {
        echo -n "Starting log4j Socket Server:"
        ${start_log4j}
        echo "done."
}
stop() {
        echo -n "Shutting down log4j: "
        ${stop_log4j}
        echo "done."
}

# See how we were called
case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  restart)
        stop
        sleep 10
        start
        ;;
  *)
        echo "Usage: $0 {start|stop|restart}"
esac

exit 0

Note that in the init script we are specifying the parameter of which port the socket server should listen to. In my example I picked the port 58778.

Make sure you set the execute bit on this file.

chmod 755 /etc/init.d/log4j



8) Use the standard RedHat chkconfig command to tell the system to start the log4j socket server at system boot time. Run the following commands:

chkconfig --add log4j
chkconfig --level 35 log4j on

If you have done everything properly, you should now be able to run the command:

chkconfig --list log4j

And your output should look like:

log4j           0:off   1:off   2:off   3:on    4:off   5:on    6:off



9) Now it's time to start our socket server. Run the following command:

/etc/init.d/log4j start

The output should look like the following:

log4j: Parsing layout options for "Default".
log4j: Setting property [conversionPattern] to [%d () %c{1} [%p] %x - %m%n].
log4j: End of parsing for "Default".
log4j: Setting property [append] to [true].
log4j: Setting property [file] to [/usr/local/log4j/log/usf_log4j.log].
log4j: Setting property [maxBackupIndex] to [10].
log4j: Setting property [maxFileSize] to [1500KB].
log4j: Setting property [threshold] to [DEBUG].
log4j: setFile called: /usr/local/log4j/log/usf_log4j.log, true
log4j: setFile ended
log4j: Parsed "Default" options.
log4j: Parsing appender named "SMTP".
log4j: Parsing layout options for "SMTP".
log4j: Setting property [conversionPattern] to [%d () %c{1} [%p] %x - %m%n].
log4j: End of parsing for "SMTP".
log4j: Setting property [threshold] to [ERROR].
log4j: Setting property [SMTPHost] to [localhost].
log4j: Setting property [bufferSize] to [10].
log4j: Setting property [to] to [tima@timarcher.com].
log4j: Setting property [subject] to [Application Error Logged].
log4j: Setting property [from] to [log4j@timarcher.com].
log4j: Parsed "SMTP" options.
log4j: Finished configuring.



10) Now for one last test, we'll check to make sure our socket server is indeed listening. We'll do a simple telnet to its listening port to see if it answers. On your unix box, run the command:

telnet localhost 58778

You should get output that looks like:

Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.

Press the ctrl-] key sequence to cancel the telnet you initiated above. If you get output that says Connection refused then there is a problem with your configuration. Review the steps above to make sure you haven't made an error.



11) If you've gotten here your socket server is setup and configured so that all messages of level DEBUG and higher will be written to the file /usr/local/log4j/log/log4j.log, and all messages of level ERROR and higher will get emailed to you.



12) The last step is to add the configuration into the log4j.properties file for your client application. We will modify the rootCategory to tell log4j to write its output to the SocketAppender.

Add the following to your log4j.properties file:

#
# The Socket Appender
#
log4j.appender.Socket=org.apache.log4j.net.SocketAppender
log4j.appender.Socket.Threshold=ERROR
log4j.appender.Socket.RemoteHost=as1.timarcher.com
log4j.appender.Socket.Port=58778
log4j.appender.Socket.ReconnectionDelay=5000
log4j.appender.Socket.LocationInfo=true

Be sure to change the RemoteHost to contain the hostname of the server that the log4j SocketServer is running on.

Also, ensure your root category is setup in your log4j.properties file to send messages to the Socket appender.

log4j.rootCategory=INFO, Default, Console, Socket

For illustrative purposes, my entire client log4j.properties file looks like:

log4j.rootCategory=INFO, Default, Console, Socket

#
# The default file appender
#
log4j.appender.Default=org.apache.log4j.RollingFileAppender
log4j.appender.Default.Threshold=DEBUG
log4j.appender.Default.File=log4jClientOutput.log
log4j.appender.Default.layout=org.apache.log4j.PatternLayout
log4j.appender.Default.layout.ConversionPattern=%d %c{1} [%p] %x - %m%n
log4j.appender.Default.MaxBackupIndex=10
# Do not Truncate if it aleady exists.
log4j.appender.Default.Append=true
log4j.appender.Default.MaxFileSize=1000KB


#
# The console appender
#
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Threshold=ERROR
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
#log4j.appender.Console.layout.ConversionPattern=%d %c{1} [%p] %x - %m%n
log4j.appender.Console.layout.ConversionPattern=%d (${dbUserId}) %c{1} [%p] %x - %m%n


#
# The Socket Appender
#
log4j.appender.Socket=org.apache.log4j.net.SocketAppender
log4j.appender.Socket.Threshold=ERROR
log4j.appender.Socket.RemoteHost=as1.timarcher.com
log4j.appender.Socket.Port=58778
log4j.appender.Socket.ReconnectionDelay=5000
log4j.appender.Socket.LocationInfo=true

Only messages of type INFO and higher are processed. Everything gets written to the file, and only messages of type ERROR and higher are shown on the console and written to the Socket Appender.



v2.0