Grails Goodness: Writing Log Messages With Grails 3.2 (Slf4J)
Grails 3.2 changed the logging implementation for the log
field that is automatically injected in the Grails artefacts, like controllers and services.
Before Grails 3.2 the log
field was from Jakarta Apache Commons Log
class, but since Grails 3.2 this has become the Logger
class from Slf4J API.
A big difference is the fact that the methods for logging on the Logger
class don’t accepts an Object
as the first argument.
Before there would be an implicit toString
invocation on an object, but that doesn’t work anymore.
In the following example we try to use an object as the first argument of the debug
method in a controller class:
package mrhaki.grails3
class SampleController {
def index() {
log.debug new Expando(action: 'index')
[:]
}
}
When we invoke the index
action we get an exception:
...
2016-12-09 14:59:20.283 ERROR --- [nio-8080-exec-1] o.g.web.errors.GrailsExceptionResolver : MissingMethodException occurred when processing request: [GET] /sample/index
No signature of method: ch.qos.logback.classic.Logger.debug() is applicable for argument types: (groovy.util.Expando) values: [{action=index}]
Possible solutions: debug(java.lang.String), debug(java.lang.String, [Ljava.lang.Object;), debug(java.lang.String, java.lang.Object), debug(java.lang.String, java.lang.Throwable), debug(org.slf4j.Marker, java.lang.String), debug(java.lang.String, java.lang.Object, java.lang.Object).
Stacktrace follows:
java.lang.reflect.InvocationTargetException: null
at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:210)
at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:187)
at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: groovy.lang.MissingMethodException: No signature of method: ch.qos.logback.classic.Logger.debug() is applicable for argument types: (groovy.util.Expando) values: [{action=index}]
Possible solutions: debug(java.lang.String), debug(java.lang.String, [Ljava.lang.Object;), debug(java.lang.String, java.lang.Object), debug(java.lang.String, java.lang.Throwable), debug(org.slf4j.Marker, java.lang.String), debug(java.lang.String, java.lang.Object, java.lang.Object)
at mrhaki.grails3.SampleController.index(SampleController.groovy:6)
... 14 common frames omitted
...
When we change the log statement to log.debug new Expando(action: 'index').toString()
it works.
Another time saver and great feature is the use of placeholders {}
in the logging message.
This allows for late binding of variables that are used in the logging message.
Remember when we used the Apache Commons Logging library we had to enclose a logging statement in a if
statement to check if logging was enabled.
Because otherwise the logging message with variable references was always evaluated, even though the logging was disabled.
With Slf4J Logger
we don't have to wrap logging statements with an if
statement if we use the {}
placeholders.
Slf4J will first check if logging is enabled for the log message and then the logging message is created with the variables.
This allows for much cleaner code.
In the following example we use the placeholders to create a logging message that included the variable id
:
package mrhaki.grails3
class SampleController {
def show(final String id) {
// Before Grails 3.2 we should write:
// if (log.debugEnabled) {
// log.debug "Invoke show with id [$id]"
// }
// With Grails 3.2 it is only the debug method and
// String evaluation using {}.
log.debug 'Invoke show with id [{}]', id
[id: id]
}
}
Written with Grails 3.2.3