Logging Exceptions: Where to Log Exceptions?
It is often a requirement that exceptions occurring in business applications must be logged, so they can be further examined by a human if necessary. Especially in web or server applications where the console output may not be available for examination. The exception log can be helpful at determining what went wrong, by either containing enough information to reveal the cause of the error, or aid in reproducing the exception.
When designing the logging of an application the question often arise: Where in the code should the exceptions be logged? Basically you have three different options:
- Bottom Level Logging
Logging in the component where the exception occurs
- Mid Level Logging
Logging somewhere in the middle of the call stack, where sufficient information is available (the context of the component call)
- Top Level Logging
Logging centrally at the top of the call stack
The listing below shows a call stack where component A calls B. B calls other components, which in turn call the component F. Component A is the top level component, B etc. are a mid level components, and F is a bottom level component.
A B ... F
Bottom Level Logging
The first option you have is to log the exception in the component where it occurs. If you cannot alter the source of that component, then you will log as close as possible to the component method call throwing the exception. At first glance it may seem like a good design choice to encapsulate logging inside the component that throws the exception. However, this approach has a few drawbacks.
First of all logging has to be coded into every component capable of throwing an exception or calling a third party component that throws exceptions. This is a lot of logging code to write and maintain.
Second, if the component is to be reused across different applications, the component has no way of knowing how exceptions is to be logged in each application. You will have to let the logging strategy be pluggable in the component, and then the logging isn't really encapsulated anyways.
Third the component that throws the exception may not really have sufficient information available to write a detailed and sensible log message. Say you have a component that writes an object to database. An exception is thrown because one of the fields is null. Can the component tell exactly what field was null in the log? Can it tell why this field was null? Perhaps the object was loaded with that field as null, or some action taken by the user caused it to become null. Or perhaps a bug in some code caused the field to become null. Does the component know what user was logged in when the exception ocurred? All this information might be needed for logging. The bottom level component may not have all that information available. The rest of the needed information may be available further up the call stack. This information cannot be logged if the logging is done inside the component, at bottom level.
Mid Level Logging
As alternative to logging at the bottom level, you can log at the mid level wherever enough information is available to write a satisfactory log message. This keeps the bottom level components free of logging code, but it has some drawbacks too.
At mid level you may not have all details available from the bottom level component that threw the exception. The bottom level component must then supply a detailed error text and perhaps error code, to make sensible logging possible at the mid level.
When logging at the mid level there is still a lot of logging code to write. At every place you catch and log an exception, you will have to insert almost the same logging code. If you later need to change the logging strategy it will take a lot of work. A shortcut would be to inject the logging code using aspect oriented programming, but I won't get into that here.
Top Level Logging
Logging at the top level means that you have a single, central place in the code that catches all thrown exceptions and logs them. In a Java web application that could be a control servlet or perhaps a servlet filter. In a desktop application perhaps you your event handlers would extend a base event handler capable of catching and logging all exceptions thrown.
The advantage of top level logging is that you only have a single spot in the application where logging code is written and maintained. In addition to being easier to implement, this also makes it easier to postpone the logging implementation until later in the development process if needed. The logging code will only have to be implemented in one place, and most likely won't change the total application call structure.
The disadvantage of top level logging is of course that this single central place doesn't know anything about what went wrong in the bottom level component that threw the exception, or what the mid level code calling that component was trying to do. That makes writing sensible log messages somewhat harder.
To overcome the lack of details available at the top level (and also to some degree at the mid level), you can enrich the exceptions as they propagate up the call stack towards the top level. Enriching exceptions is done by catching them, adding extra information and rethrowing them again. Rethrowing a caught Java exception will not change the embedded stack trace. The stack trace remains the same as when the exception was thrown first, by the bottom level component.
You will have to code your own exception class in order to make exception enrichment possible. Either that, or you'll have to wrap the caught exception in a new exception and throw the new exception.
Summary and Recommendation
We have looked at three different logging strategies: Bottom, mid and top level logging.
I recommend using top level logging wherever possible, since it is easiest to code and maintain, and also to add later in the development process if you do not want to be bothered with it in the beginning. You may run into situations where an exception must be caught and dealt with at a lower level, but these situations don't happen often in my experience. Most often, when an exception occurs that request or event handling is just skipped, and the exception logged along with sufficient information to determine the cause of the error, or reproduce it at least.