How does one properly implement WebApi 2.1's ExceptionLogger so that log4net logs the correct values for method, location and line?
What I'm trying to achieve is a global exception logger to log all unhandled exceptions in a WebAPI 2.1 v5.1.2 app running .NET 4.5.1. I've read quite a few articles including the one linked below explaining how to implement the ExceptionLogger, and it works great, except that I can't get log4net to output the values I really want to record.
For example, if I log an exception from a controller, everything is correct. When I log from the ExceptionLogger, I'm getting the values from the Logger itself, and not the method that initiated the exception. I tried a few things listed in my code below, but they're not quite right. Here's what I get.
I know the text is small, but the table shows the different values log4net writes. The first log is from the controller, everything is great. The 2nd entry is from log.Logger.Log in the code snippet. The last entry is from log.Error in the snippet.
The final method in the snippet attempts to use a limiting type as I've read from other implementations of log4net wrappers, but it just throws an error, as described in the snippet.
So, HOW CAN I GET THE VALUES LIKE THE ONES I WOULD RECEIVE IF CALLING FROM A CONTROLLER, WHEN USING A GLOBAL ExceptionLogger ?
public class GlobalExceptionLogger: ExceptionLogger
{
//private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public override void Log(ExceptionLoggerContext context)
{
StackTrace stackTrace = new StackTrace(context.Exception);
Type methodDeclaringType = stackTrace.GetFrame(2).GetMethod().DeclaringType;
ILog log = LogManager.GetLogger(methodDeclaringType);
string message = context.ExceptionContext.Exception.Message;
//this methods works but writes the location, method name and line from this method, not the caller
//location: System.Web.Http.ExceptionHandling.ExceptionLogger.LogAsync(:0)
//method: LogAsync
//line: 0
log.Logger.Log(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType, log4net.Core.Level.Error, message, context.ExceptionContext.Exception);
//this methods works but writes the location, method name and line from this method, not the caller
//location: Company.AppName.Api.GlobalExceptionLogger.Log(c:\TFS\AppName\AppName.Api\GlobalExceptionLogger.cs:38)
//method: Log
//line: 38
log.Error(message, context.ExceptionContext.Exception);
//this method throws an error in the log4net debug: log4net:ERROR loggingEvent.LocationInformation.StackFrames was null or empty.
log.Logger.Log(methodDeclaringType, log4net.Core.Level.Error, message, context.ExceptionContext.Exception);
}
}
Your method of getting the stacktrace is not recommended, because the code will behave differently on debug/release or precessor architecture. The method stackTrace.GetFrame(2).GetMethod() will give you the method on the real stack, with taking into consideration the optimalizations of the runtime for processor architecture, linq rewrites etc.
An alternative method of getting the member name:
You should have a look at this question:
stackframe-behaving-differently-in-release-mode