configurations {
all*.exclude group: 'org.grails', module: 'grails-plugin-log4j' (1)
all*.exclude group: 'org.slf4j', module: 'slf4j-log4j12' (2)
}
22 April 2014
In a previous post, I discussed how to switch a Grails application to use Logback instead of Log4j for logging. In that post, I
covered using both the Maven support in Grails to configure the dependencies required to use Logback with Grails, as well as how to use the built in dependency management support via the
BuildConfig.groovy
dependencies DSL. I did not cover how to make this all work with Gradle. The same caveats and issues, as mentioned previously, still apply with regards to the Logback DSL
defined in Config.groovy
and the use of the logback.groovy
file in its place, so I will not re-hash that here. Instead, I will point out a couple of issues with regards to getting the
dependencies configured correctly in the build.gradle
file. Similar to the other approaches, you still need to provide global excludes for the Log4J related dependencies:
configurations {
all*.exclude group: 'org.grails', module: 'grails-plugin-log4j' (1)
all*.exclude group: 'org.slf4j', module: 'slf4j-log4j12' (2)
}
1 | Exclude the Grails Log4J plugin, per the instructions provided by the Grails Logback Plugin. |
2 | Exclude the Log4J SLF4J binding, as Logback provides its own SLF4J binding. |
Next, include the Grails Logback Plugin as a dependency:
dependencies {
compile ('org.grails.plugins:logback:0.3.1') {
exclude(module:'slf4j-api') (1)
}
compile ('ch.qos.logback:logback-classic:1.1.1') { (2)
exclude(module:'slf4j-api')
}
}
1 | Exclude the SLF4J transitive dependency so that it does not conflict with the version provided by Grails |
2 | I added an explicit dependency for logback-classic so that I could use a newer version than the one transitively provided by the Grails Logback Plugin. |
I thought that this would be enough to build my application’s WAR file using the grails-war
task of the Grails Gradle Plugin. However, when I went to execute the task, the
build failed with the following exception:
| Error Error generating web.xml file
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'org.slf4j.helpers.NOPLoggerFactory@7f4cfb5' with class 'org.slf4j.helpers.NOPLoggerFactory' to class 'ch.qos.logback.classic.LoggerContext'
What is really strange is that this does not happen when using the grails-run-app
Gradle task. Additionally, the following also appears in the output when attempting to build the WAR file:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
A quick search of the internets for both of these issues indicate that you are either A) do not have any SLF4J binding on your classpath, thus causing SLF$J to default to the no-op binding, or B) have an
SLF4J binding on the classpath in addition to the one provided by Logback. Those two statements obviously contradict with each other. After scratching my head for a while, I realized that the Grails Gradle Plugin
has a bootstrap
dependency scope, which is used to provide dependencies to the Grails commands, which themselves are GAnt scripts written in Groovy. These scripts also need a logger to output
the status of what is being executed. I decided to add a duplicate dependency to the build.gradle
build file to see if that would fix the problem:
dependencies {
compile ('org.grails.plugins:logback:0.3.1') {
exclude(module:'slf4j-api')
}
compile ('ch.qos.logback:logback-classic:1.1.1') {
exclude(module:'slf4j-api')
}
bootstrap 'ch.qos.logback:logback-classic:1.1.1'
}
After adding the dependency to the separate bootstrap
scope, I once again ran the grails-war
Gradle task and got a much better outcome:
| Done creating WAR build/distributions/test-1.0.0.war
BUILD SUCCESSFUL
Total time: 17.166 secs
Much better. It’s important to remember that the Grails build eco-system is itself a Java-based application that requires its own set of dependencies just to execute. When using the Grails
command line arguments or building via Maven, this classpath is management automatically. However, when using the Grails Gradle Plugin plugin, you currently have to manage any additions to this yourself
via the bootstrap
scope (an interesting side note here is that now Grails is using Logback for all logging, not just when executing the application).