I’ve just released a @WithLog annotation to GitHub under RobertFischer/Groovy-WithLog. The goal of @WithLog is to reduce the boilerplate associated with using Log4J.
Normally, using Log4J looks like this:
import org.apache.log4j.Logger class Foo { static log = Logger.getLogger(Foo) /* ... */ }
I’ve now said Logger three times and my class name twice. And there’s absolutely no useful value to having it clogging up the body of my class. This burns my eyes, and needs to stop.
Now you can replace it with:
@WithLog class Foo { /* ... */ }
As an AST transform, the class itself is actually changed at compile time: no runtime cost is paid, no additional library needs to be schlepped around (except Log4J, obviously), and you don’t have to worry about what order your code executes in for fear that log won’t be defined soon enough.
I’m considering creating a super-powered version which defaults to inserting log properties onto the compiling classes, and you have to explicitly say @NoLog to avoid it. I’m still trying to decide if that’s evil: on the one hand, it’s basically changing the Groovy language; on another, the user has to put it onto their classpath during compile, so you’re basically asking for it. Another potential option is to enable [EDIT: I went ahead and implemented that approach. Old version was still too chatty.][EDIT 2: See Hamlet's fully-qualified class name pattern approach for another interesting take.]@WithLog with no import (I’ll add the import for you), which would basically stomp anyone else’s @WithLog, but it’d be sexy-short.
The biggest difficulties I encountered when coding this up was the overwhelming lack of information about how to deal with Groovy AST Transforms: they’re not for the faint of heart, and I basically had to go grepping through the Groovy language source code to figure it out. The documentation on Groovy’s Codehaus site (notably similar to Hamlet D’Arcy’s blog posts) was a start, but questions like “What do these compile phases mean, anyway?” are nowhere to be found.
Also encountered a fun problem where Groovy went checking for static fields at compile time. You can see the same issue here:
class Foo { static doLog() { log.info("This won't even compile!") } } Foo.metaClass.static.log = [info:{ println it }] // Enable the above code to work /* org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, /Users/robert/dev/workspace/Groovy-WithLog/test/Bogus.groovy: 2: Apparent variable 'log' was found in a static scope but doesn't refer to a local variable, static field or class. Possible causes: You attemped to reference a variable in the binding or an instance variable from a static context. You mispelled a classname or statically imported field. Please check the spelling. You attempted to use a method 'log' but left out brackets in a place not allowed by the grammar. @ line 2, column 19. static doLog() { log.info("This won't even compile!") } ^ 1 error */
Note that ripping the word static out of that code makes it compile and run just fine.
Because of that issue, I couldn’t use a local AST transform, which is basically exactly what the situation called for. Instead, I had to use a global transform at the magic “CONVERSION” stage, which also meant effectively having to duplicate annotation hunting and class resolution logic. The silver lining of that particular WTF-Cloud is that I can go all kinds of crazy with how I apply things now (see above conversation on @NoLog and raw imports).
Additional conversation about implementing this AST Transform will probably be in an upcoming GroovyMag. Coming up in August is Part II of my “Goldilocks and Grails Logging”, which is the impetus for this code.
Thanks to Hamlet D’Arcy and Peter Niederwieser for their long-suffering assistance in this effort.
As with most of my open source contributions, it’s released under the WTFPL. Have a blast.
Related posts:
Pingback: Cry for Help | Enfranchised Mind
Pingback: log.debug { "$toStringMe only if necessary!" }