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