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:

build.gradle
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:

build.gradle
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:

build.gradle
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).

comments powered by Disqus