Thursday, May 06, 2010

Repurposing test code for production

For various reasons I often wrap the well-known logback logging library; more precisely, I wrap the SLF4J API it implements.
However, this has a strong drawback: filename and line numbers in the log which should tell me the origin of the log calls instead show me my wrapper class. From logback's perspective the wrapper is the caller; from my perspective the wrapper should be invisible.
The culprit is CallerData.isDirectlyInvokingClass(String, String). It tells logback if a given stack frame is internal to logback or from the calling application. It is static and logback has no hooks for changing its behavior. What to do?
The hack:
public class Wrapper {
    static {
        new MockUp() {
            CallerData it;
 
            @Mock(reentrant = true)
            public boolean isDirectlyInvokingClass(
                    final String currentClass,
                    final String fqnOfInvokingClass) {
                return it.isDirectlyInvokingClass(
                        currentClass, fqnOfInvokingClass)
                    || currentClass.equals(Wrapper.class.getName());
            }
        };
    }

    // Rest of class
}
Enter the clever jmockit library sitting at the high end of the testing mock library pile up (or deep end, if you prefer). Jmockit is not the easiest library to use, but is has more code-fu per line than most other swiss-army knives.
I replace the static method in logback with my own when my wrapper class is loaded. I even get a handle (it) to the original method so I may forward appropriately.
This is not my first try at a solution to wrapping logback. Nor my second. Nor my third. But it is the only one so far which:
  • Works
  • Looks like Java
  • Is compact and limited to the wrapper class
  • Can be explained to others (mostly)
  • Can be maintained (by some)
I have penance to pay for pulling a testing library into production code, but I get some colleague credit for cleverness. It might even be a wash.
Do watch out for: