Friday 9 March 2012

Custom Log Object with Log4J

In this article, I will explain how to create Custom Log Object with Log4J.

It is a common practice to pass log message string to info, debug, warn, error and fatal methods of logger class, and Log4J by default logs the given message string given to a log file, database etc. using the appenders. And, what if we want to log more details such as user ID, application name, version, browser information, client IP etc.

The PatternLayout configuration does not support logging any of the above additional information, and we cannot log any of these additional values using any of Log4J format modifier.  The solution is - we write our own log message class containing these additional information.

The info, debug, warn, error and fatal methods Log4J logger takes object as method argument. So, any Java object can be passed to these methods.

Let us create a Java POJO class containing the additional fields we are interested in:

package com.yourcompany.logger;

public class LogMessage {
      private String appId;
      private String version;
      private String userId;
      private String browserInfo;
      private String clientIp;
      private String hostIp;
      private String url;
      private String message;
      private String stackTrace;
           
      public String getAppId() {
            return appId;
      }
      public void setAppId(String appId) {
            this.appId = appId;
      }
      public String getVersion() {
            return version;
      }
      public void setVersion(String version) {
            this.version = version;
      }
      public String getUserId() {
            return userId;
      }
      public void setUserId(String userId) {
            this.userId = userId;
      }
      public String getBrowserInfo() {
            return browserInfo;
      }
      public void setBrowserInfo(String browserInfo) {
            this.browserInfo = browserInfo;
      }
      public String getClientIp() {
            return clientIp;
      }
      public void setClientIp(String clientIp) {
            this.clientIp = clientIp;
      }
      public String getHostIp() {
            return hostIp;
      }
      public void setHostIp(String hostIp) {
            this.hostIp = hostIp;
      }
      public String getUrl() {
            return url;
      }
      public void setUrl(String url) {
            this.url = url;
      }
      public String getMessage() {
            return message;
      }
      public void setMessage(String message) {
            this.message = message;
      }
      public String getStackTrace() {
            return stackTrace;
      }
      public void setStackTrace(String stackTrace) {
            this.stackTrace = stackTrace;
      }
     
      @Override
      public String toString(){
            StringBuilder logMessageString = new StringBuilder();
            //Create message string with all additional

            //information fields. Each field can be delimited
            //with comma, space, tab, etc
            return logMessageString.toString();
      }
}


After populating all the fields in the LogMessage object, it can be passed to info, debug, warn, error and fatal methods of logger class as a parameter. Log4J internally calls toString method of passed object and you will have format modifier %m of PatternLayout printing the output of LogMessage.toString(). So, your PatternLayout in log4j.xml remains unchanged with all of your other format modifier:

<layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%-5p [%d] %c.%t: %m%n" />
</layout>


We can have another wrapper class to create an instance of LogMessage and invoke Log4J logger as below:

package com.yourcompany.logger;

import org.apache.log4j.Logger;

public class LogManager {
      private Logger log4JLogger;
     
      public LogManager(String callerClassName){
            log4JLogger = Logger.getLogger(callerClassName);
      }
     
      public void logError(String msg, Exception ex){
            LogMessage logMessage = constructLogMessage(msg, ex);
            log4JLogger.error(logMessage);
      }
     
      //You can have more methods for different log level
     
      private String constructLogMessage(String msg,

                        Exception ex){
            LogMessage logMessage = new LogMessage();
           
            //Get appId and version from application configuration

            //file, DB or wherever it is available
            logMessage.setAppId("MyApp");
            logMessage.setVersion("1.0.0.2");
           
            //The following can be got from HttpServletRequest

            //when request object is available in this class
            logMessage.setUserId(request.getRemoteUser());
            logMessage.setBrowserInfo

                        (request.getHeader("User-Agent"));
            logMessage.setClientIp(request.getRemoteAddr());
            logMessage.setHostIp(request.getLocalAddr());
            logMessage.setUrl(request.getRequestURL().toString());
           
            logMessage.setMessage(msg);
            logMessage.setStackTrace(getCompleteStackTrace(ex));
      }
     
      private String getCompleteStackTrace(Exception e){
            StringWriter writer = null;
            try {
                  writer = new StringWriter();
                  joinStackTrace(e, writer);
                  return writer.toString();
            }
            finally {
                  if (writer != null){
                        try {
                              writer.close();
                        }
                        catch (IOException e1) {
                        // ignore
                        }
                  }
            }
      }
     
      private void joinStackTrace(Throwable e, StringWriter sw) {
            PrintWriter printer = null;
            try {
                  printer = new PrintWriter(sw);
                  while (e != null) {
                        printer.println(e);
                        StackTraceElement[] trace =

                                    e.getStackTrace();
                        for (int i = 0; i < trace.length; i++)
                        printer.println("\tat " + trace[i]);

                        e = e.getCause();
                        if (e != null)
                        printer.println("Caused by:\r\n");
                  }
            }
            finally {
                  if (printer != null)
                        printer.close();
            }
      }
}


Finally, you can invoke the logger wrapper from your class as below:

public class MyClass {
      private static LogManager logger =

            new LogManager("com.yourcompany.MyClass");
     
      public void myMethod() {
            iriLogger.logError("Test exception", new Exception());
      }
}

No comments:

Post a Comment