Better logging - how Nice!
One of the things that often drives people crazy in Java is the question of logging. Java has great logging toolkits such as Log4J, the Apache Commons Logging framework, and others. However, there's a very basic problem with the language, which no mere library can circumvent: You want to put interesting things into the log messages, but you don't want to pay for them when you're not actually logging. With Java, your only choice is to wrap the logging code in if statements, thus:
if (DEBUG) {
logger.debug("This manager has " +
manager.getEmployees().size() +
" employees");
}
Of course, you could always skip the if (DEBUG) protection, and many times that works. The people who design these systems try very hard to minimize the performance overhead of calling debug when nothing's interested in collecting debug information. But that doesn't solve a more important problem. Sometimes, the arguments to the logging method ("This manager..", above) are expensive to evaluate, and without that if statement, the arguments to the logging method always get evaluated, whether or not they get used. If, for instance, manager.getEmployees() involves a call to the database that wouldn't otherwise be needed, it's a big problem.
The Nice programming language solves this problem neatly, with a clean syntax for anonymous methods which aren't associated with any particular class. So we change our API to allow us to pass a method instead of the actual string. Then, the logging function can decide whether to call the method and produce the needed string, or not. Here's how a rough first pass might look:
logger.debug(DEBUG, ()=> "This manager has " + manager.getEmployees().size() + " employees");
The notation ()=>... introduces an anonymous method which takes no arguments, and returns the value of the expression (...). This neatly solves our main problem of delaying evaluation of our string, while not introducing as much clutter as the if statement did.
Nice will let us make this even more ergonomic. First, let's make an obvious guess: 90% of the time, if we want debug information, we'll have the logging system configured for it. So we'll make the test condition that determines whether to evaluate the string or not be based on the logging system. We'll use Nice's default parameters to make this guess implicit, we can always change it at the call site if we need to. Here's what our implementation looks like so far, using Apache Commons Logging.
void debug(Log logger,
void->String message,
boolean when = logger.isDebugEnabled())
{
if (when) {
info(logger, message());
}
}
All of the Apache Commons Logging methods also have overloaded versions which take throwables. We've got default parameters, so we'll just go ahead and accommodate that in the same method definition:
void debug(Log logger,
void->String message,
?Throwable err = null,
boolean when = logger.isDebugEnabled())
{
if (when) {
info(logger, message(), err);
}
}
Finally, Nice allows an alternate syntax for parameters which are zero-argument methods - you simply make a block after the call, and write the code for the method in that block. So, our final, streamlined call looks like this:
logger.debug() {
return "This manager has " +
manager.getEmployees().size() +
" employees";
}
Problem solved! And we can do variations:
logger.debug(when: isTuesday()) {
return "This manager has " +
manager.getEmployees().size() +
" employees";
}
try {
//...
} catch (Exception e) {
logger.debug(when: isTuesday(), err: e) {
return "This manager has " +
manager.getEmployees().size() +
" employees";
}
}
Much Nicer! A complete wrapper for the Apache Commons Logging library which uses this approach is available here.
