I just spent way too much time figuring out how to add a catch-all logger for exceptions to an ASP.NET Web API project, so I figured I’d write up my experience as a blog post, for anyone else who needs it (and for my own future reference).
The goal, specifically, is to log any unhandled exceptions using Serilog. I don’t want to mess with them in any way, I just want to record them in the log. (For this API, most exceptions are already properly handled, but sometimes something falls through the cracks, so I just want to be able to see when that happens, so I can fix it.)
First, this is an old-fashioned ASP.NET Web API project, not a .NET Core project. I’m using Autofac for dependency injection and Serilog for logging.
And I’m using the Autofac.WebAPI2 package to integrate Autofac into the API. My Autofac configuration looks pretty much just like the example in the “Quick Start” section of the page linked above.
Serilog is linked in like this:
builder.Register((c, p) => { var fileSpec = AppDomain.CurrentDomain.GetData("DataDirectory").ToString() + "\\log\\log-{Date}.log"; var outpTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Properties:j} {Message:lj}{NewLine}{Exception}"; return new LoggerConfiguration() .WriteTo.RollingFile(fileSpec, outputTemplate: outpTemplate) .ReadFrom.AppSettings() .CreateLogger(); }).SingleInstance();
I won’t get into how that works, but you could figure it out from the Serilog docs easily enough.
ASP.NET Web API provides a way to hook into unhandled exceptions using an ExceptionLogger class. This is described a bit here. I found several blog posts describing various permutations on this functionality, but I had to mess around a bit to get it all to work right for me.
I created a class that looks like this:
public class MyExcLogger : ExceptionLogger { public override void Log(ExceptionLoggerContext context) { var config = GlobalConfiguration.Configuration; var logger = (ILogger)config.DependencyResolver.GetService(typeof(ILogger)); if (logger != null) logger.Error("Unhandled exception: {exc}", context.Exception); } }
and I hooked it up to Web API by adding this line to my WebApiConfig Register() method:
config.Services.Add(typeof(IExceptionLogger), new MyExcLogger());
There’s not actually much to it, but I went down the wrong path on this thing several times, trying to get it to work. The (slightly) tricky part was getting the logger instance from the dependency resolver. Constructor injection doesn’t work here, so I had to pull it out of the resolver manually, which I’d never actually tried before.