Today’s lesson involves the Kitchen Aid KEBC208 KSSO Stainless Steel electric
wall mounted double oven.
Please don’t overtighten the screws that hold the outer handle to the door. These
screws go right through the tempered glass and overtightening will cause you to
develop an urgent need for a new one of these. And a nervous twitch. Plus you will
have to go out to dinner unexpectedly.
It all started with Log4j — Java, log4j and I sort of grew up together. I loved log4j. But along came j.c.l, jdk1.4 logging and I had to try them all out. When building a library that other people are supposed to embed into something of theirs, though, turning the logging choice over to them makes a lot of sense. As our little project grew and needed to bring in other components we always appreciated those that did logging the way we wanted it done.
Jetty was a standout in that area — I first learned the coolness of dependency injection from studying Jetty. Jetty provided our first introduction to Slf4j. Slf4j is sort of the ultimately flexible logging system. You build to the Slf4j API and drop in the statically bound jar file for the back end implementation you want (e.g. log4j, logback, log4juli). This is a fantastic idea and thanks are due to Ceki Gülcü for log4j, logback and slf4j.
Quite some time back we felt the pain of just trying out a new logging system. Since every file has imports and a factory method call to get the logger, it is painful to switch. So we insulated ourselves from that by creating our own Logger API, an abstract base class that pretty closely matched what we needed and were using from log4j. We had a Logger.getLogger(String) method, debug, info, warn, error and fatal levels. And we had a LogManager that acted as the factory for it all, returning a wrapper around log4j loggers that met our API. Life was good.
We added Slf4j into the mix at first only to get logging from some Jakarta components that were using j.c.l out into the log4j backend where we wanted them. Slf4j was fantastic at that.
Then the haunting question — why aren’t we doing in our own library the very thing that we enjoy about the logging capabilities of Jetty? Jetty has a soft dependency on Slf4j and falls back to some type of logging to stdout when it isn’t present. Cool. We can do that too.
The friction came when I realized that our nice little log4j-based API abstraction needed NDC. The Slf4j API does not support NDC. Major bummer. We find this incredibly helpful in debugging a complex multi-threaded system. We have pushContext(String), popContext() and clearContext() methods in our logging abstraction and I don’t really want to give them up.
But hey! Why not make a soft dependency on those features just like we are doing for Slf4j? So in the new Slf4jLogger that we use to encapsulate our Slf4j capability, we can check for the presence of org.apache.log4j.Logger and grab those methods we need for NDC. If they aren’t there, ok, we drop the NDC information, but if they are there, then we have it all!
Here’s some code to add the soft dependency on NDC. First, in the static initializer of our Slf4jLogger class:
// Support methods for ndc capability if log4j is available
private static Method ndcClear;
private static Method ndcPop;
private static Method ndcPush;
static {
/* Not shown - Slf4j soft dependency loading */
Class ndcz = null;
String NDC = "org.apache.log4j.NDC"
try
{
ClassLoader cl = Thread.currentThread().getContextClassLoader();
ndcz = cl==null?Class.forName(NDC):cl.loadClass(NDC);
ndcClear = ndcz.getMethod("clear",new Class[]{});
ndcPop = ndcz.getMethod("pop",new Class[]{});
ndcPush = ndcz.getMethod("push",new Class[]{String.class});
} catch (Throwable t) {}
}
So that grabs the underlying methods we need when they are present and doesn’t carp when they aren’t.
We can wrap that up in a simple function to let us know when NDC capability is present:
protected static boolean isNDCEnabled ()
{
return ndcPush != null;
}
And protect the actual methods that are the NDC part of the logger API:
/**
* Push the string on the ndc context stack if the capability is supported
* @param s the new context
*/
public void pushContext(String s)
{
if (isNDCEnabled())
{
try
{
ndcPush.invoke(null,s);
}
catch (Exception e) { /* e.printStackTrace(); */}
}
}
Since we are pushing the context into the actual Log4j NDC object, we didn’t have to reinvent anything. If Log4j is on the runtime classpath the features will be found. If Slf4j is configured to actually use Log4j as the backend for logging, then the NDC will actually pop out of the log and we still don’t need Log4j to be on the classpath at compile time!
So have your Slf4j and your NDC too!
String s = "This is a test of the Google Syntax highligher plugin.";