Grails - Multiple Log4j Configurations

This is a quick tip that I found very useful and - to my knowledge - is not part of the Grails documentation by now. So let's spread the word :-) In one of my Grails projects, we use external configuration files to apply customer specific settings. We have a custom ConfigurationLoader that is used to locate customer-specific configuration files based on the current environment (VM arguments, *.properties file and such). The configuration loader returns a list of additional configuration files which is handed over to grails.config.locations [0]
 
// ConfigurationLoader retrieves customConfigLocations, being an ArrayList<String>


grails.config.locations = customConfigLocations
 
So far so good. Lately we had the requirement to specify customer-specific Log4J log levels, as various components needed a more fine-grained logging level. In Grails versions prior to Grails 2.0 we could have only satisfied this requirement by completely overriding the log4j variable from Config.groovy in the custom configuration file.
 

// log4j configuration

log4j = {
    
    error  'org.codehaus.groovy.grails.web.servlet',  //  controllers

           'org.codehaus.groovy.grails.web.pages', //  GSP

           'org.codehaus.groovy.grails.web.sitemesh', //  layouts

           'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping

           'org.codehaus.groovy.grails.web.mapping', // URL mapping

           'org.codehaus.groovy.grails.commons', // core / classloading

           'org.codehaus.groovy.grails.plugins', // plugins

           'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration

           'org.springframework',
           'org.hibernate',
           'net.sf.ehcache.hibernate'
}

 
Luckily, Ian Roberts introduced a far more elegant way to extend the log4j configuration.

Using a map of closures

As of Grails 2.0, there is a way to extend the Log4J configuration rather than replacing it. The way to go is to use a log4j variable of type Map<String, Closure> instead of Closure.
 

// main configuration

log4j.main = {
   // ...

}

// external (customer-specific) configuration

log4j.external = {
  // common configuration

}

environments {
  development {
    log4j.env = {
      // environment-specific config

    }
  }
  production {
    log4j.env = {
      // environment-specific config

    }
  }
}

 
As you can see in the code listing above, log4j is now treated as map, rather than a single value object. As groovy.util.ConfigObject [1] extends LinkedHashMap, the log4j entries will be called in the defined order and the resulting configuration entries will be merged. With this patch, we were able to add customer-specific log levels to our external configuration files. If you want to have a look at the corresponding patch introducing this feature, check out [2].

[0] Grails Documentation - Externalized configuration
[1] groovy.util.ConfigObject
[2] Jira GRAILS-7314