Un-swallowing Swallowed Exceptions with Fody and IL Weaving
You ever take over a codebase where the previous developer liked to ignore exceptions? You ever have to track down a production issue while the useful stack traces from those swallowed exceptions disappeared into the abyss?…
A basic example would be something like this:
public void DoSomething() {
try {
//do stuff
} catch (Exception ex) {
Debug.WriteLine("Error message: " + ex.Message);
}
return;
}
Or this:
public void DoSomething() {
try {
//do stuff
} catch (Exception ex) { }
}
How can you clean this up in an app where this is done all over the place?
Don’t let anyone be lazier than you
Let’s show your predecessor who the real lazy developer is in this town.
Enter Fody, a powerful IL (Intermediate Language) Weaving tool by Simon Cropp.
Fody is a layer on top of Mono.Cecil that makes it easy to manipulate IL during the build process without the need for “plumbing code [that] involves knowledge of both the MSBuild and Visual Studio APIs.” (Fody Github page)
Mono.Cecil, written by Jb Evain, allows you to:
- Analyze .NET binaries using a simple and powerful object model, without having to load assemblies to use Reflection.
- Modify .NET binaries, add new metadata structures and alter the IL code.
“In simple English, with Cecil, you can load existing managed assemblies, browse all the contained types, modify them on the fly and save back to the disk the modified assembly.” (Mono.Cecil project page)
So Fody lets us add, remove, and manipulate compiled IL code using Mono.Cecil’s APIs. With it’s add-in model we can use, extend, and write our own “weavers” to help us be even lazier than we already are.
Leveling up our laziness (in a good way)
So if we wanted to start cleaning up the swallowed exception mess without any help, we’d probably go through each method and delete the try/catch
blocks, allowing the exceptions to bubble-up and make themselves known.
After that, we might implement a logging framework of choice. My logging framework of choice happens to be Serilog. Or we might add a monitoring solution like Stackify.
What if we could just add an attribute to “un-swallow” exceptions while you needed to fix something quickly?…
I decided to write a Fody add-in for that called UnSwallowExceptions.Fody.
Install via Nuget:
Install-Package UnSwallowExceptions.Fody
To un-swallow an exception for a method, simply add the attribute [UnSwallowExceptions]
:
[UnSwallowExceptions]
public void Swallowed_exception_to_be_unswallowed() {
try {
throw new Exception();
}
catch (Exception) {
}
}
The add-in in will locate any method with this attribute and weave an additional rethrow
instruction into the IL code generated by the compiler. So originally, the IL code for our swallowed exception looked like this:
.method public hidebysig instance void
Swallowed_exception() cil managed
{
.maxstack 1
IL_0000: nop
.try
{
IL_0001: nop
// [18 9 - 18 31]
IL_0002: newobj instance void [mscorlib]System.Exception::.ctor()
IL_0007: throw
} // end of .try
catch [mscorlib]System.Exception
{
// [20 7 - 20 27]
IL_0008: pop
IL_0009: nop
IL_000a: nop
IL_000b: leave.s IL_000d
} // end of catch
IL_000d: ret
} // end of method OnException::Swallowed_exception
With the [UnSwallowExceptions]
attribute applied, the IL code looks like this:
.method public hidebysig instance void
Swallowed_exception_to_be_unswallowed() cil managed
{
.custom instance void [UnSwallowExceptionsReferenceAssembly]UnSwallowExceptions.Fody.UnSwallowExceptionsAttribute::.ctor()
= (01 00 00 00 )
.maxstack 1
IL_0000: nop
.try
{
IL_0001: nop
// [53 9 - 53 31]
IL_0002: newobj instance void [mscorlib]System.Exception::.ctor()
IL_0007: throw
} // end of .try
catch [mscorlib]System.Exception
{
// [55 7 - 55 27]
IL_0008: pop
IL_0009: nop
IL_000a: nop
// [57 9 - 57 15]
IL_000b: rethrow
} // end of catch
IL_000d: ret
} // end of method OnException::Swallowed_exception_to_be_unswallowed
Notice the rethrow
instruction inserted before the // end of catch
line.
The C# method, if you were to decompile it, would look like this:
public void Swallowed_exception_to_be_unswallowed()
{
try
{
throw new Exception();
}
catch (Exception ex)
{
throw;
}
}
One step further with Logging
So now that you have un-swallowed the exceptions in a method, what can you do with them? Let’s get them to start logging to the team’s logging infrastructure. I use Serilog with Seq.
With IL Weaving, we can keep being lazy and still add this additional logging code using just one more Fody add-in - Anotar.
Anotar “Simplifies logging through a static class and some IL manipulation.”
So we can install Anotar’s Serilog package with:
Install-Package Anotar.Serilog.Fody
We want to make sure UnSwallowExceptions.Fody
comes before Anotar.Serilog
in our FodyWeavers.xml file (created when we install the Nuget package):
<?xml version="1.0" encoding="utf-8"?>
<Weavers>
<UnSwallowExceptions />
<Anotar.Serilog />
</Weavers>
And finally, we can add the [LogToErrorOnException]
attribute after setting up a Serilog logger:
class Program {
static Program() {
var log = new LoggerConfiguration()
.WriteTo.LiterateConsole()
.CreateLogger();
Log.Logger = log;
}
static void Main(string[] args) {
DivideByZero();
Console.ReadKey();
}
[UnSwallowExceptions, LogToErrorOnException]
public static void DivideByZero() {
try {
int a = 10, b = 0;
Console.WriteLine(a / b);
}
catch (Exception) { }
}
}
I’ll spare you the IL code, but after applying both IL weavers to the DivideByZero()
method, it looks like this:
public static void DivideByZero()
{
try
{
try
{
Console.WriteLine(10 / 0);
}
catch (Exception ex)
{
throw;
}
}
catch (Exception ex)
{
string messageTemplate = string.Format("Exception occurred in 'Void DivideByZero()'. ");
if (Program.AnotarLogger.IsEnabled(LogEventLevel.Error))
Program.AnotarLogger.Error(ex, messageTemplate, (object[]) null);
throw;
}
}
This can save you some time if there is a lot of exception swallowing throughout your app. Rather than having to delicately search around the code and remove try/catch
blocks, all you have to do is add a couple attributes and start fixing the exceptions being thrown.
Summary
We started with inheriting an application that had tons of swallowed exceptions. Doing this was hiding potentially valuable information that could be addressed if these exceptions were otherwise allowed to bubble up to let us work on them.
One way to resolve this in the short term is to use IL Weaving provided by the Fody library by Simon Cropp. Fody makes it easy for us to modify IL code during the build process and do some really neat things. Check out the list of available add-ins here.
I wrote a Fody add-in that can be used to address our problem with swallowed exceptions called UnSwallowExceptions.Fody.
Check out the Github repo here. It can be used alongside Simon Cropp’s Anotar for a convenient blow-everything-up-and-log-it approach to debugging.
Hope you enjoy and this helps you out some day! As always, make sure you sign up for my newsletter to get helpful articles like this sent weekly to your inbox!
Related Posts:
Sources
- Dolinka Márk Gergely. Reweaving IL code with Mono.Cecil
- Simon Cropp. Fody. Github
- Simon Cropp. Anotar. Github
- Mono.Cecil
- Duane Edwards. SwallowExceptions.Fody
- mono-cecil Mailing List