Tuesday, June 7, 2011

Host and user name in log4j logs

In the production environment web application debuging is not so easy. Especially because when multiple users perform multiple actions in the same time, the output log is interleaved, and the step-by-step situation is hard to reproduce in development lab. The solution for that can be by injecting user name into the log, and then "grepping" the source log in looking for particular user paths. 

If you use log4j as your logging framework the implementation is not very obvious, but can be achieved using custom PatternLayout, injecting additional variables to the logs. Here I'm presenting such class and config. We want to inject transparently hostname and username into the logs.

First the configuration (using log4j xml config). The only important is appender part:

<appender name="consoleAppender" class="org.apache.log4j.ConsoleAppender">
    <layout class="ContextPatternLayout">
        <param name="ConversionPattern" value="%d %p %h %u [%c] - %m %n"/>
    </layout>
</appender>

As you can see there're here two additional (regarding original PatternLayout) parameters: %h representing hostname, and %u representing username.

Now the implementation. All in one class with some anonymous members, to be as simple as possible:

import java.net.InetAddress;
import java.net.UnknownHostException;

import org.apache.log4j.helpers.PatternConverter;
import org.apache.log4j.helpers.PatternParser;
import org.apache.log4j.spi.LoggingEvent;


/**
 * Context pattern layout with %h (hostname) and %u (username) variables.
 */
public class ContextPatternLayout extends PatternLayout {

  protected String host;
        
  protected String getUsername() {
    return "username";
  }

  protected String getHostname() {
    if (host==null) {
      try {
        InetAddress addr = InetAddress.getLocalHost();
        this.host = addr.getHostName();
      } catch (UnknownHostException e) {
        this.host = "localhost";
      }
   }
   return host;
 }

 @Override
 protected PatternParser createPatternParser(String pattern) {
   return new PatternParser(pattern) {

     @Override
     protected void finalizeConverter(char c) {
       PatternConverter pc = null;

       switch (c) {
         case 'u':
           pc = new PatternConverter() {
             @Override
             protected String convert(LoggingEvent event) {
               return getUsername(); 
             }
           };
           break;

         case 'h': 
           pc = new PatternConverter() {
             @Override
             protected String convert(LoggingEvent event) {
               return getHostname();
             }
           };
           break;
       }
                                
       if (pc==null)
         super.finalizeConverter(c);
       else
         addConverter(pc);
     }
   };
 }
        
}

Of course the getUsername() method need to be overriden to provide actual value.