Why have multiple exception policies?

Topics: Exception Handling Application Block, Logging Application Block
Apr 26, 2007 at 9:21 PM
I don't understand the need for multiple policies. I wrote this use case...Comments?

Microdot – The company developing the application
Codak – Developer who is writing code
Pointy – Codak’s boss
Northwind – The company using the application
Annie Admin – The end user
Steve – Network Administrator for Northwind

Exceptions in Microdot’s applications should always be considered unexpected and treated as a failure in the application. Obviously, some exceptions are not as critical as others, but all exceptions should be at least sent to a logging framework. Whether they are actually logged and to where will be the responsibility of the logging framework.

Microdot will use a logging framework to decide where to route the exception and what exception details will be logged. The importance of logging different exceptions and the methodology of logging those different exceptions can/will change according to the business model and thus needs to be easily configurable outside of the code itself.

Developers like Codak should not have to decide how to log each exception that his code is written to handle or whether actually logging a particular exception is necessary. Codak can not know in advance that FileNotFound exceptions should be logged to a database and ArgumentNull exceptions should be written to a log file. Nor can he know that Northwind might consider FileNotFoundExceptions unimportant, and not necessary to log. Nor can he know if Northwind might want to log to an Oracle database or a log file or send e-mails.

All of the code examples provided in the following example are assumed to be written at the “outermost” level of the application. Any exceptions that occur inside the business layer or data layer will not be handled inside the business layer or data layer – exceptions must be allowed to propagate out to the outermost layers.

Assume Codak rights the following code:

try
{
//execute some command to update the database
}
catch (Exception ex)
{
//we caught an exception but that’s ok because sometimes the //database gets busy and it times out. So we won’t rethrow the //exception…we’ll just do nothing
}

So, Codak has made the decision it is acceptable the update failed. Perhaps he thinks occasionally the database is going to be busy because Northwind doesn’t have a very powerful server and sometimes it will fail. Well, what happens later when Steve upgrades the server? Is it still acceptable that it fails occasionally? More importantly, the developer shouldn’t have to make decisions like this. A failure is a failure. Codak shouldn’t be deciding how to log certain failures based on the underlying hardware architecture nor should he be making the executive decision of whether the exception will ever be considered log-worthly.

Let’s suppose Codak does his best to handle exceptions where he thinks they might occur but while giving a demo to Pointy, the application blows up due to an unforseen problem. Resolving not to let that happen again, he wraps all of his code in one giant exception (ignore for now that this is not the best approach for global error handling)

try
{
//1000 lines of code that represents the entire application or page
}
catch (Exception ex)
{
//just in case anything went wrong in the above 1000 lines of code, //we don’t want the user to see this error but do nothing with the //error
}

This one is obvious. If the application blows up due to a completely unexpected behavior, someone has to be notified. Codak realizes this and adds some code:

try
{
//1000 lines of code that represents the entire application or page
}
catch (Exception ex)
{
//We need to let someone know about this
System.Net.Mail.MailMessage mm = new System.Net.Mail.MailMessage();
mm.To = "someone@somewhere.com";
mm.Subject = "Hello";
System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient();
smtp.Send(mm);
}

Now we are sending an e-mail whenever an error occurs. Still there are problems with Codak’s approach. Now we are locked into sending all exception logging via e-mail. What if this needs to change? What if the hardware on which the application is deployed doesn’t have access to SMTP or an Exchange server? Codak doesn’t think about this initially but he does realize that his other code that caught a database exception should probably be e-mailed to someone as well. So, he copies and pastes his lines of code to send an e-mail into the catch block of the database update. Soon he decides other exceptions he is catching should be e-mailed as well. So he copies and pastes the 5-10 lines of code all over the application.

Luckily, Codak realizes this is a bad idea and creates one method to send all e-mails. Now if the e-mail address needs to change or additional information needs to be added to the e-mails, he can do it in one place. But this approach doesn’t solve the problem stated before: what if the application isn’t on hardware capable of sending e-mails? Further, what if Annie Admin or Pointy decide they really can’t handle receiving thousands of e-mails every time the database goes down? Now they want all exceptions to be logged to a database which they can browse at their pleasure.

After speaking with Pointy, Codak sees the error of his ways. So, he sits down to create an entire logging framework. The framework will be fully configurable at runtime and will allow Annie Admin or Pointy to configure how they want exceptions to be logged and more specifically, how they want each different type of exception to be logged. In addition, they will be able to do things like configure the layout (format) of the exception details, turn logging on and off altogether, and enable multiple logging sources for each exception. 6 months later, Codak completes the project and receives many accolades.

Rather than spending 6 months, Codak could have simply implemented the Enterprise Library Exception Management and Logging Block.

The following code shows how to handle (log) exceptions with ELB.

try
{
//execute some command to update the database
}
catch(Exception ex)
{
ExceptionPolicy.HandleException(ex, “Default”);
}

When throwing an exception, two parameters must be passed. The first is the exception itself; the second is the name of the exception policy. The exception policy must match one of the exception policies that Codak has defined in the configuration file. Exception policies are simply categories. For each category Codak or Annie or Pointy can decide (by editing a configuration file) what actions should be taken when an exception is thrown. ELB allows multiple exception policies. Example:

ExceptionPolicy.HandleException(new ArgumentNullException (), “File Access”);

ExceptionPolicy.HandleException(new ArgumentNullException (), “Data Access”);

The first policy (Default) might log the error to the database. The second policy (Data Access) might write the error to a log file. But why have two different policies? In the above example, by asking Codak to specify the policy while he codes the application, we are breaking our own rule that the developer should not have to decide where or how the error should be logged. Even though he is not directly making a decision as to how the logging should be done, we are asking him to make a logical decision as to which of the categories (policies) the exception probably fits into. No matter how well Microdot tries to predefine the categories (policies) before Codak begins his development, there will always be shades of gray as to which policy a particular exception belongs to. In the above example, suppose a method makes a database call and based on the return of the database call, writes a file. If the return is null, is it a Data Access policy or a File Access policy? Probably a Data Access policy – but the real answer is – why does it matter? The Logging and Exception Management Block is meant to extract these decisions out of the business logic.

An argument could be made for defining multiple exception policies so that different types of exception are handled differently (DivideByZero is e-mailed, FileNotFound is logged to the database). But this still requires the developer to know the above rules; he shouldn’t care. And it assumes Microdot knows ahead of time how Northwind wants to log different exceptions and that they will want to log them that way forever! More importantly, the Enterprise Library already allows you to assign different actions (called TraceListeners) to different types of exceptions under the same exception policy. So, under the “Default” policy, we can assign a Database TraceListener to DivideByZero exceptions and a Log File TraceListener for FileNotFound exceptions. Annie and Pointy can, at any time, change the TraceListeners for each type of exception.

Another argument might be to create policies like “Critical”, “Important”, “Not So Important.” Suppose Codak wrote this code:

try
{
//build a dynamic SQL statement
//Execute SQL statement

try
{
//Update a tracking database that logs the sql statement //string in a table in case we want to look at the actual SQL //statement that was created
}
catch(Exception ex)
{
//if we can't reach our tracking database, it's ok, it's not //crucial we have the SQL
ExceptionPolicy.HandleException(ex, “Not So Important”);
}
}
catch(Exception ex)
{
//if the update failed, it's bad!
ExceptionPolicy.HandleException(ex, “Critical”);
}

This is an unnecessary judgment call by Codak. The framework can decide which exceptions are important and which are not. If updating the tracking database is truly not an “important exception,” then simply write directly to the log using the application block.