Sunday September 04, 2005 (See intro for a background and caveats on these coding advice blog entries.)
Rule number one: Don't Log, Debug!
One of my pet peeves in code is gratuitous logging. Gratuitous logging is where every little thing happening in your code is also sent as a log message. In other words, logging is used to record program events, rather than program errors!
The rationale for this is usually that logging is inserted to help find bugs later. "Just turn on logging and comb through the output to trace execution and discover the problem."
I don't buy the above argument, but before I go into that, let me state why I think logging is bad: It reduces code readability. You've probably heard that you shouldn't sprinkle your code with comments that are obvious (and therefore redundant). Sprinkling your code with logging calls describing what's going on is essentially the same thing, only it's even harder to mentally filter these out while reading the code, since they don't have the visual "I can ignore this" property that comments have in syntax highlighting.
There's a much better way to find bugs than by using logging: Use a debugger! I will admit that I used to rely on logging myself, since in the early days restarting the application, and even single stepping the debugger, was frustratingly slow. But with the introduction of Hotswap, and general improvement in features and speed of Java debuggers, there's no good excuse for not using a debugger to track down program bugs today.
The argument that's usually thrown out in defense of logging is that you need it to track down bugs in a deployed environment, for example at a customer's site, where you don't have physical access and cannot debug it yourself. You want to enable logging and use the log from the customer to figure out what's going on.
In my experience, that sounds good in theory but never works in practice. The logging never has all the information you need. To track down a specific bug you typically need fine grained information that you hadn't thought you needed when you wrote the code.
Let me also point out that this is a really rare scenario. Usually, if a customer reports a bug, they can tell you how to reproduce it, and once you can reproduce it locally you don't need to debug it via logs on their system - use a debugger locally.
The scenario has come up once for me. In Creator 1.0, some customers reported that after using the IDE for a while, the IDE would suddenly stop updating the source files even though components were added and properties changed. There were no direct instructions to reproduce, and it was rare - nobody internally had ever seen it. Finally a case for customer site logging! What I did was add a number of targeted logging calls; anything having to do with buffer manipulation was heavily "instrumented". I also developed a couple of hypotheses about what the problem might be, and added special code for checking these hypotheses, adding log messages if they succeeded. I then provided a custom jar to two customers who had reported seeing this problem regularly - and a few days later I had confirmation of one hypothesis.
The key point I want to make here is that even though this actually is a scenario which called for logging, it didn't have to be put in the product a priori. The logging calls were only added temporarily in one of my source trees. And furthermore, to get useful logging data I added a lot more tracking than would have been feasible to do in a product version.
So when should you use logging? To record errors and unexpected conditions - especially non-fatal ones where you don't want to get the user's attention, you simply want to know about it yourself such that you can check your assumptions and improve your program quality later.
If you're not convinced and still want to use logging, here's a tip for how to do it efficiently. There's always the option of statically compiling out all logging:
private static final boolean LOGGING_ENABLED = false;
...
if (LOGGING_ENABLED) {
log("foo = " + bar);
}
By using a final boolean here which is false, the compiler knows that everything, including the if block, can never be executed, so it will be completely eliminated - the class file will not contain any of the byte code; the class String table will not include the string in the log call, etc. This is zero-overhead logging, when logging is disabled. The disadvantage of this approach of course is that it cannot be enabled by the customer, so it's only useful internally. And for that of course, use a debugger!
A modification to this scheme which works well, is to use the assertion facility. This is what I used before I finally removed all non-error-related logging from my code. Create a log function like this:
public boolean log(String message) {
// log calls here - perhaps delegate to a logging library
return true;
}
Now you can use logging as follows in your code:
assert log("foo = " + bar);
This is using assertions for a side effect: the assertion will always be true (since the logging call unconditionally returns true) so no AssertionError will be thrown. However, the logging method parameters get evaluated and the logging method called.
This solution gets the best of both worlds: When turned off it has nearly zero overhead (because the class loader will throw away the assertion statements when loading the class), but it can still be turned on in those rare scenarios where you need a customer to provide you with logging calls. (It does however increase the size of your String pools etc. so there is a small cost.)
But again,
Don't forget - logging is appropriate for logging errors! Don't start writing empty catch blocks and blaming me! For example, in an application built on top of the NetBeans RCP (see a tutorial of the new RCP support), a catch block that doesn't do anything else should at least call
} catch (WhateverException ex) {
ErrorManager.getDefault().notify(ex);
}
(2005-09-04 00:02:53.0)
Permalink
Comments [9]
Posted by kodeape on September 04, 2005 at 02:56 AM PDT #
Posted by henrik on September 04, 2005 at 07:46 AM PDT #
Even when it does your existing logging calls probably aren't sufficient anyway. Even if the logging statement does not hit the nail exactly, it usually gives you a hint where the problem occured.
You can get the data you need by providing the customer with a special enhanced logging build. Having frameworks like log4j that allow you to change the logging level easily even at runtime, there is no need to have specially enhanced logging builds. And if you followed your own advice you don't have those logging calls in your code anyway, so you start to add them every time you get a bug report from your customer that you can't reproduce? Then you build that product and send it to your customer, who is going to shut down her web site for half an hour to deploy your "enhanced" application? Get real!
I have been working at a company that had two different main products, one with extensive logging, easily configurable at the customer site, and one with almost no logging, following the usual arguments you repeat here once again.
Man, how the support department hated the second product because of the missing logging! They considered it almost unsupportable. <p/><p/> Conditional compilation Actually, by doing this the semantics of your code may change in very subtle ways. From a principal point of view you end up with two different products. <p/> If you're still able to find your bugs using the debugger you are still fixing the obvious bugs. May be one day Creator really takes off and you are finally moving into the realm of non-obvious, non-repeatable bugs, you will be grateful of every single logging statement you put into your code. <p/> Cheers, Georg
Posted by giorgio42 on September 05, 2005 at 01:50 AM PDT #
It sounds like you're saying most of your bugs are found by customers, not your QA department. That certainly would explain why our conclusions are different; in your case debugging is less of an option. But I don't think the solution is more logging; I think the solution is more QA.
So I disagree that I'm using wrong assumptions. We apparently have had very different experiences. Yes, Creator is a young product, but I'm in my 10th year at Sun and have worked on very stable product lines, and the scenario was generally the same:
Regarding conditional compilation possibly changing the code semantics in subtle ways: the same point can be made about your conditional logging. Any time the code path changes you are potentially changing timing order which affects race conditions. I've seen this first hand (granted, in C++ code), where adding a printf statement made the bug go away because the I/O call made the thread wait. (But note that I don't advocate conditional compilation; I don't advocate event-logging at all. But if you must, I prefer the assertions-based approach.)
Thank you very much for your feedback though! Coding practice blog entries always generate a lot of interest, and I knew this one definitely would!
Posted by Tor Norbye on September 05, 2005 at 10:23 AM PDT #
Furthermore, we use logging for non-diagnostic purposes as well. If the customer wants to do transaction tracking, there's often no other way.
Posted by Rich Unger on September 05, 2005 at 01:22 PM PDT #
Transaction tracking is not what I'm talking about when rejecxting program event logging. If your program processes transactions, and you intend to support listing these as a user feature, then clearly your program needs to have targeted log calls around the transactions.
That's different than sprinkling logging calls all over your code in case you'll need them later. And presumably you wouldn't want the customer asking for transaction tracking to see all of that output, so you'll be using different logging categories. I'm arguing that all the superfluous logging calls unrelated to transaction tracking (data interesting to the user, unlike your program-internals logging) is what needs to go.
Problems with other vendors' software and misconfiguration should show up in diagnostic logging or assertions, which I hope I have clearly said I not only support - I use it myself.
Posted by Tor Norbye on September 05, 2005 at 01:45 PM PDT #
Posted by My Own Little World on September 06, 2005 at 09:21 AM PDT #
Posted by LuigiG on September 07, 2005 at 11:46 AM PDT #
Posted by Alex on December 14, 2005 at 01:57 AM PST #