I could find very little in the way of straight forward and informative examples of how to do basic log4j configuration in the Grails Config.groovy file. Since this is not something that I tend to revisit very often it is needlessly involved whenever I do have to go there. This represents my attempt to fill that general need of mine, and perhaps someone else will find it useful too.
The most basic configuration you can have would be this.
log4j = {
}
Let’s fill in what that represents. You get a stdout appender to the console for free. If you were to define it you would have approximately this.
log4j = {
appenders {
console name:'stdout', layout:pattern(conversionPattern: '%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %p %c{2} - %m%n')
}
}
The conversion pattern conforms to org.apache.log4j.PatternLayout. To make this appender a default appender explicitly and log at the “error” (or greater) level you would have something like this.
log4j = {
appenders {
console name:'stdout', layout:pattern(conversionPattern: '%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %p %c{2} - %m%n')
}
root {
error 'stdout'
}
}
So now we have the approximate equivalent of the first example. The next component comes into play when you want a category – by convention the equivalent of a package – treated differently than other stuff. For me, it is when I want to see informational or debugging messages related to some, or possibly all, of my code. Now I can decide to log that at a different level than my default (or root) logger.
log4j = {
appenders {
console name:'stdout', layout:pattern(conversionPattern: '%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %p %c{2} - %m%n')
}
root {
error 'stdout'
}
debug 'com.mydomain'
}
At this point everything that starts with com.mydomain that is logged at the level of debug or greater will get sent to my default appender, stdout. Of course everything that is at error or greater will be sent to my default appender too, just as before. So what if I want a different appender – not a stdout appender?
Well, we can define stdout to be any appender that we want! Just as we can define the pattern layout that we want, we can define the type of appender that we want.
log4j = {
appenders {
rollingFile name:'stdout', maxFileSize:"50MB", maxBackupIndex:100,file:"./logs/${appName}_app.log",{
layout:pattern(
conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
)
}
}
root {
error 'stdout'
}
debug 'com.mydomain'
}
Of course we might be better off not calling our appender stdout if we want it to be something considerably different – that convention over configuration thing. How about:
log4j = {
appenders {
rollingFile name:'logile', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app.log",{
layout:pattern(
conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
)
}
}
root {
error 'logfile'
}
debug 'com.mydomain'
}
This makes logfile our default appender. Of course we can have multiple appenders too. Unless you override stdout you have that automatically. So the prior example has both stdout and logfile. You can have them both as default appenders like so.
log4j = {
appenders {
rollingFile name:'logile', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app.log",{
layout:pattern(
conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
)
}
}
root {
error 'logfile', 'stdout'
}
debug 'com.mydomain'
}
Now they both receive the default logging output. What if I want only debugging to go to logfile, and both errors and debugging to go to stdout? Well, default appenders get everything by default, so we don’t want logfile as a default appender but rather a named appender.
log4j = {
appenders {
rollingFile name:'logile', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app.log",{
layout:pattern(
conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
)
}
}
root {
error 'stdout'
}
debug logfile: 'com.mydomain'
}
It is valid to name a default appender here but it will result in the appender being written to twice, once due to the explicit reference and once as the default. If you want to write to the named appender only – not to any default appenders – then you need to specify no additivity.
log4j = {
appenders {
rollingFile name:'logile', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app.log",{
layout:pattern(
conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
)
}
}
root {
error 'stdout'
}
debug logfile: 'com.mydomain', additivity: false
}
You can supply a list of packages instead of repeating a line for each.
log4j = {
appenders {
rollingFile name:'logile', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app.log",{
layout:pattern(
conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
)
}
}
root {
error 'stdout'
}
debug logfile: ['com.mydomain.one','com.mydomain.two'], additivity: false
}
Since ultimately you are setting up (and resolving) logging attributes for a package, the resolution of the logger must be unambiguous. Consequently if you set up the same package multiple times the last one wins for setting the level. So although the following logs to both appenders as you wold expect, it logs to them both as debug.
log4j = {
appenders {
rollingFile name:'logile1', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app1.log",{
layout:pattern(
conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
)
}
rollingFile name:'logile2', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app2.log",{
layout:pattern(
conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
)
}
}
root {
error 'stdout'
}
error logfile1: 'com.mydomain', additivity: false
debug logfile2: 'com.mydomain', additivity: false
}