Log4j Appenders for Domino Logging

Overview

This implementation contains three log4j appenders that make it possible to log into a Notes database from either a servlet running in a Domino server, an agent running in a Domino server and a remote location (e.g. a EJB or a Servlet running in a Tomcat). The first two appenders use native Notes APIs, the remote appender uses IIOP to connect to the server.

All appenders use a caching based on, first, the numbers of entries in a single log document and, second, a timeout value. Therefore, if either the maximum numbers of lines are reached in a single log document or a certain timeout value since the last write action has past, the cached log entries are written to the Domino database.

Appender Classes

The following table contains the three supported appender classes. They can be used as the class property in a log4j configuration, here is an example:

log4j.appender.appendername=org.goetz.domino.log4j.AgentAppender
        	

Class NameDescription
org.goetz.domino.log4j.AgentAppenderAppender implementation to be used in a Domino agent.
org.goetz.domino.log4j.ServletAppenderAppender implementation to be used in a serlvet running in the Domino server context.
org.goetz.domino.log4j.RemoteAppenderAppender implementation to be used from remote locations of any kind, e.g. a J2EE container (servlets or EJBs).

Sample Configuration

Here is a sample configuration for an agent appender:

# Agent appender:
log4j.appender.testagent1=org.goetz.domino.log4j.AgentAppender
log4j.appender.testagent1.applicationName=TestAgent1
log4j.appender.testagent1.database=DOMAPP/applog00.nsf
log4j.appender.testagent1.maxLines=500
log4j.appender.testagent1.flushTimeout=10000
log4j.appender.testagent1.message=%d %u %p - %m
		

Here is a sample configuration for a Domino servlet appender:

# Servlet appender:
log4j.appender.testservlet1=org.goetz.domino.log4j.ServletAppender
log4j.appender.testservlet1.applicationName=DominoTestServlet1
log4j.appender.testservlet1.database=DOMAPP/applog00.nsf
log4j.appender.testservlet1.maxLines=1000
log4j.appender.testservlet1.flushTimeout=60000
log4j.appender.testservlet1.message=%d %u %p - %m
		

Here is a sample configuration for a remote appender:

# Remote appender (Serlvet):
log4j.appender.testremote1=org.goetz.domino.log4j.RemoteAppender
log4j.appender.testremote1.applicationName=RemoteTestServlet1
log4j.appender.testremote1.server=localhost
log4j.appender.testremote1.database=DOMAPP/applog00.nsf
log4j.appender.testremote1.userName=userid
log4j.appender.testremote1.keystore=c:/data/protect/dataprotector.keystore
log4j.appender.testremote1.userPassword=password
log4j.appender.testremote1.maxLines=1000
log4j.appender.testremote1.flushTimeout=10000
log4j.appender.testremote1.message=%d %u %p - %m
		

Please note: If you have multiple remote appenders defined, they must all point to the same keystore at the moment, because the decryption of the password is done via a static class being initialized by all appenders. If you have different keystores, some appender initializations will sporadically fail because of multiple concurrent access to the singleton decrypting class.

Standard Properties

The following properties apply to all appenders:

PropertyDescription
applicationNameName of the application written as document attribute AppName. If not set, the first used logger category is being used as application name. I.e. the target category name might be 'comp.sample.comp' for a whole component (e.g. servlet). However, it could be that the first log entry is being written by another package called 'com.sample.comp.ejb'. Then, unfortunately, the application name would be initialized with the name 'com.sample.comp.ejb'. That's why you have the possibility to override that behavior by setting an explicit name by using this property.
serverServer name where the log database is located. Optional. If not defined, localhost is used.
databaseName of the Domino database to log to, relative to the data directory of the Domino server.
maxLinesMaximum number of lines per log document. Optional. If not set, 1000 is used as the default. If the fixed maximum size of a log document of about 30 kB is reached, the document is written to the database, with no regards to the current number of lines. The reason for this limit is a) rich text fields in Domino are able to contain about 30 kB without a
flushTimeoutNumber of miliseconds until a non-full log document is written to the database. Optional. If not set, 20 seconds is used as the default.
messageLog4j message definition. The placeholder %u is being replaced by the current user id. For the RemoteAgent implementation, the user id is read from a log4j MDC context. You need to insert the following code line into your code for each thread:
MDC.put("id", request.getRemoteUser());
		    	
formNameName of the Domino log document form. Optional. If not defined, the default 'frmEvents' is used.

Additional Properties for RemoteAppender

The following properties apply to the RemoteAppender only:

PropertyDescription
connectUserNameTechnical user id to connect to the log database via IIOP. If not set, the IIOP connection tries to connect anonymously.
connectPasswordPassword of the technical user id. If not set, the IIOP connection tries to connect anonymously.

How to deal with the two different Domino VMs

The target of this project also was to have a single source of log4j configuration for all Java-based code running in the context of the Domino server. Java code can run in the form of a servlet or an agent. Unfortunately, HTTP based calls to agents and servlets on one hand and RunOnServer calls to agents on the other run in different VMs. Hence, loading a log4j configuraiton in one VM does not update the current configuration in the other VM.

For that reason, I introduced a socket based communication between the configuration agent (ran as RunOnServer method) and its counterpart VM, the one responsible for servlets and agents called vai HTTP. To make something listen to update requests, you need to install a specific servlet in your Domino server as follows:

servlet.log4jconfiglistener.code=org.goetz.domino.log4j.config.Log4JReloadServlet
servlet.log4jconfiglistener.initArgs=port=4444,configdir=C:/opt/domino65/data/conf
servlets.startup=log4jconfiglistener

The init method of this servlet creates a daemon thread that listens on a socket on a specific port. When a client connects to that socket, the thread tries to reload the log4j configuration from the directory specified by the second servlet argument called configdir.

Another solution is to use the log4j configuration file watcher thread. This can be configured as follows:

servlet.log4jconfiglistener.code=org.goetz.domino.log4j.config.Log4JReloadServlet
servlet.log4jconfiglistener.initArgs=delay=60000,configdir=C:/opt/domino65/data/conf
servlets.startup=log4jconfiglistener

This makes the servlet listen to log4j configuration file changes every 60 seconds (60000 ms). A listener port is then not created.

Preparing the Domino Server

Unfortunately, preparation of the Domino Server got quite complicated. To be able to carry agents and servlets that use the appenders provided in this component, you must do the following things:

Add the following libraries to the Domino server's JVM extension directory (i.e. C:\Lotus\Domino\jvm\lib\ext):

  • Log4j - log4j-1.2.8.jar
  • Domino Log4j Appenders - org.goetz.domino.log4j-1.5.2.jar (this library)
  • Add the following entries in the server's notes.ini:

            	
    Log4jDirectory=directory relative to the domino directory
    JavaUserClasses=class path including the absolute directory from above
            

    e.g.

            	
    Log4jDirectory=conf
    JavaUserClasses=C:\Lotus\Domino\data\conf
            

    Add the log4j config servlet to the servlets.properties file in your data directory, e.g. as follows:

    servlet.log4jconfiglistener.code=org.goetz.domino.log4j.config.Log4JReloadServlet
    servlet.log4jconfiglistener.initArgs=delay=60000,configdir=C:/Lotus/Domino/data/conf
    servlets.startup=log4jconfiglistener
    

    Restart the Domino server.

    Install the Notes database applog00.nsf in the nsf directory of this project in your Domino server. It contains the configuration agent and views to look at log documents created by the appenders.

    Sample Configuration and Logging Database

    The database nsf/applog00.nsf provided in the distribution contains a configuration and a logging part. Both parts are contained in one database for convenience reasons. You would, however, in your own environments separate configurations and logging into two or more databases.

    The agent "LogConfigLoaderAgent" is called by using the button in the configuration view in the provided database. It activates the selected log4j configuration, stores this configuration to a log4j.properties configuration file and calls the other VM via the socket provided by the servlet described above. The port to which the agent connects is hard coded into the agent source code.

    TestAgent1

    The agent 'TestAgent1' uses the Domino agent appender as an example. You can call it by using the test button in the configuration view or calling it via HTTP as follows:

            	
    http://server/domapp/applog00.nsf/TestAgent1?OpenAgent
            

    Performance

    The following table contains numbers taken from some performance tests that were run locally on a Thinkpad T42. The test client, the Tomcat instance and the Domino server were located on that Thinkpad. Each request/response pair contains the following log method calls:

    log.debug("This is a debug message");
    log.info("This is an info message");
    log.warn("This is a warn message");
    log.error("This is an error message");
    log.fatal("This is a fatal message");
    ...
    try {
    	throw new Exception("Meine Ausnahme");
    } catch (Exception e) {
       	log.error("Error in TestServlet!", e);
    }
    		

    This logging scenario was executed using JMeter having 5 client threads in parallel for a couple of minutes.

    IIOP/RemoteAppender (Tomcat, Sun JDK 1.5.0)

    ValueMeasures for Domino Appender (ms)Measures for File Appender (ms)
    Avg (ms)3932
    Median (ms)2030
    90% (ms)6140
    Max (ms)1443621
    Throughput (reqests per second)126.2151.8
    KB per second283.03340.46

    Local/ServletAppender (Domino 6.5, IBM JDK 1.3.1)

    ValueMeasures for Domino Appender (ms)Measures for File Appender (ms)
    Avg (ms)6558
    Median (ms)6060
    90% (ms)8070
    Max (ms)821230
    Throughput (reqests per second)75.085.0
    KB per second119.32135.41

    Local/AgentAppender (Domino 6.5, IBM JDK 1.3.1)

    ValueMeasures for Domino Appender (ms)Measures for File Appender (ms)
    Avg (ms)254257
    Median (ms)161160
    90% (ms)551581
    Max (ms)12211312
    Throughput (reqests per second)19.619.4
    KB per second67.1766.50