Tuesday, September 13, 2011

Grails Logging & New Website launched

This month I’ve got special news. We just launched our new family site http://scheelethek.de. :)
It’s developed in Grails and shows once more that Grails is the right application framework for fast, fun and reliable web-development.
Ok, it took some time to launch it. But that wasn’t Grails’ fault. There was just no time besides my professional and family life. I coded up to one hour every several weeks only.
But Grails makes it easy to stay connected without the need to read up on the previous development again and again.

This month I decided to post the logging configuration of this application. This is a part of Grails that isn’t very intuitive.
I think the logging requirements of my application are sophisticated but still very common:

  • In Development and Test-Mode logging should be done to the console (“Stdout”)
  • In Production-Mode logging should go to a file (and “Stdout” should be deactivated)
  • The file should be recreated on size-threshold or time (“rollingFile”)
  • In Development and Test-Mode logging should be done on “Debug-Level”; for Production it should be the “Info-Level
  • The Logging should include the name of the logged in user (or session-id if not logged in). The format is as follows:
    2011-09-11 17:19:02,290 [http-8080-5] (0646291FBCC5F8E95BE6E10C4AB9DFBE) INFO user.RegistrationController - User 'Hans Wurst' has registered. Sending email to: hans_wurst@sdfds.de
  • When Warnings or Errors are logged, an eMail should be sent directly into my mailbox; including the message
  • Of course: All components of my application should have logging activated

My configuration for Grails 1.3.7 in the Config.groovy looks like this:
def logPattern = '%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] (%X{user}) %p %c{2} - %m%n'
log4j = {
    
	appenders {
   	 
      	console name:'stdout', layout:pattern(conversionPattern: logPattern)
      	'null' name:'stacktrace'
     	 
      	environments {
          	production {
               // rolling file per size
              	rollingFile name:'fileAppender', maxFileSize:'2MB', file: "/opt/mypath/myLog.log", layout:pattern(conversionPattern: logPattern)
                // rolling file per date
                //appender new DailyRollingFileAppender(name: 'file', datePattern: "'.'yyyy-MM-dd", fileName: "/opt/mypath/myLog.log",layout: pattern(conversionPattern: logPattern))
              	'null' name:'stdout'
              	// mail appender
              	appender new org.apache.log4j.net.SMTPAppender(
                  	name: 'mail',
                  	to: 'myemail@mysdfwhatever.de',
                  	from: 'noreply@mysdfwhatever.de',
                  	threshold: org.apache.log4j.Level.WARN,
                  	bufferSize: 1,
                  	subject: "Error on ScheeleThek",
                  	layout: pattern(conversionPattern: logPattern),
                  	//SMTPDebug: true,
                  	SMTPHost: 'localhost'
              	)
          	}
      	}
	}
    
	root {
    	     error 'stdout', 'fileAppender', 'mail'
	}

	error  'org.codehaus.groovy.grails.web.servlet',   
       	'org.codehaus.groovy.grails.web.pages',  
       	'org.codehaus.groovy.grails.web.sitemesh',  
       	'org.codehaus.groovy.grails.web.mapping.filter',  
       	'org.codehaus.groovy.grails.web.mapping',  
       	'org.codehaus.groovy.grails.commons',  
       	'org.codehaus.groovy.grails.plugins',  
       	'org.codehaus.groovy.grails.orm.hibernate',  
       	'org.springframework',
       	'org.hibernate',
       	'net.sf.ehcache.hibernate'

	warn   'org.mortbay.log'
    
	debug  'de.scheelethek',
        	'grails.app'
	 
	environments {
   	   production {
        	  info	'de.scheelethek',
        	    	'grails.app'
   	    }
	}
  	 
}
As described above the logging outputs the name or session-id of the current user. This is very useful when all activities of a user should be collected later on. To make this work you need a Grails filter like this:
      class UtilityFilters {
    
	SpringSecurityService springSecurityService
    
	def filters = {
   	 
   	 
    	/**
     	* Used for setting the log4j diagnostic context:  Username if available; otherwise the session-id
     	*/
    	logContext (controller: "*", action: "*") {
       	 
        	before = {
            	if (springSecurityService.isLoggedIn()) {
                	MDC.put('user',"${springSecurityService.principal?.username}")
            	} else {
                	if (RequestContextHolder.getRequestAttributes()!=null) {   	// if we have a valid web-request
                    	MDC.put('user',"${session.id}")
                	}
            	}
        	}
       	 
        	afterView = {
            	MDC.remove 'user'
         	}
       	 
    	}
   	 
	}

}