I'm currently refactoring a large subsystem with a multi layered architecture, and I'm struggling to design an effective error logging \ handling strategy.
Let's say that my architecture consists of the following three layers:
- Public Interface (I.E an MVC Controller)
- Domain Layer
- Data Access Layer
My source of confusion is where I should implement the error logging \ handling:
-
The easiest solution would be to implement the logging at the top level (I.E the Public Interface \ MVC Controller). However this feels wrong because it means bubbling up the exception through the different layers, and then logging it; rather than logging the exception at it's source.
-
Logging the exception at it's source is obviously the best solution because I have the most information. My problem with this is that I can't catch every exception at source without catching ALL exceptions, and in the domain / public interface layer, this will lead to catching exceptions that have already been caught, logged and re-thrown by the layer below.
-
Another possible strategy is a mix of #1 and #2; whereby I catch specific exceptions at the layer they are most likely to be thrown (I.E Catching, logging and re-throwing
SqlExceptions
in the Data Access Layer) and then log any further uncaught exceptions at the top level. However this would also require me to catch and relog every exception at the top level, because I can't distinguish between errors that have already been logged \ handled vs those that have not.
Now, obviously this is an issue in most software applications, so there must be a standard solution to this problem that results in exceptions being caught at source, and logged once; however I just can't see how to do this myself.
Note, the title of this question is very similar to 'Logging exceptions in a multi tier application"', however the answers in that post are lacking detail and are not sufficient to answer my question.
Best Answer
To your questions:
Having the exception bubble up to the top level is an absolutely correct and plausible approach. None of the higher layer methods tries to continue some process after the failure, which typically can't succeed. And a well-equipped exception contains all the information necessary for logging. And doing nothing about exceptions helps you keep your code clean and focussed on the main task instead of the failures.
That's half correct. Yes, most useful information is available there. But I'd recommend to put all this into the exception object (if it isn't already there) instead of immediately logging it. If you log at a low level, you still need to throw an exception up to tell your callers that you didn't complete your job. This ends up in multiple logs of the same event.
Exceptions
My main guideline is to catch and log exceptions at the top level only. And all the layers below should make sure that all necessary failure information gets transported up to the top level. Within a single-process application e.g. in Java, this mostly means not to try/catch or log at all outside the top level.
Sometimes, you want some context information included in the exception log that's not available in the original exception, e.g. the SQL statement and parameters that were executed when the exception was thrown. Then you can catch the original exception and re-throw a new one, containing the original one plus the context.
Of course, real life sometimes interferes:
In Java, sometimes you have to catch an exception and wrap it into a different exception type just to obey some fixed method signatures. But if you re-throw an exception, make sure the re-thrown one contains all information needed for later logging.
If you are crossing an inter-process border, often you technically can't transfer the full exception object including the stack trace. And of course the connection might get lost. So here's a point where a service should log exceptions and then try its best to transmit as much of the failure information as possible across the line to its client. The service must make sure the client gets a failure notice, either by receiving a failure response or by running into a timeout in case of a broken connection. This will typically result in the same failure to be logged twice, once inside the service (with more detail) and once in the client's top level.
Logging
I'm adding some sentences about logging in general, not only exception-logging.
Besides exceptional situations, you want important activities of your application to get recorded in the log as well. So, use a logging framework.
Be careful about log levels (reading logs where debug information and serious errors are not flagged with different accordingly is a pain!). Typical log levels are:
In production, set the log level to INFO. The results should be useful to a system administrator so he knows what's going on. Expect him to call you for assistance or bug-fixing for every ERROR in the log and half of the WARNINGs.
Enable the DEBUG level only during real debugging sessions.
Group the log entries into appropriate categories (e.g. by the fully-qualified classname of the code that generates the entry), allowing you to switch on debug logs for specific parts of your program.