2

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.

enter image description here

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);
    }

}

http://weblogs.asp.net/jongalloway//looking-at-asp-net-mvc-5-1-and-web-api-2-1-part-4-web-api-help-pages-bson-and-global-error-handling

Bill
  • 2,382
  • 2
  • 24
  • 27
  • Interesting note. if I use Type methodDeclaringType = new StackTrace().GetFrame(2).GetMethod().DeclaringType; , this will prevent the last method from error. However, this stacktrace is different then when creating it ala StackTrace(context.Exception). This leads me to believe Logger.Log is not using the stacktrace that is part of the passed in exception, but from some other source. – Bill Jun 17 '14 at 23:16

1 Answers1

4

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:

public static string LogError(Exception ex, [CallerMemberName] string callerName = "")

You should have a look at this question:

stackframe-behaving-differently-in-release-mode

Community
  • 1
  • 1
Peter
  • 27,590
  • 8
  • 64
  • 84
  • 1
    Understood about the stacktrace, but that came from other posts explaining how to manipulate log4net inside a wrapper. Unfortunately, your alternative won't work because you can't add a new parameter when overriding the Log method of IExceptionLogger. It's becoming apparent to me that attempting to use log4net for global error logging is not a good idea. I've already begun using Elmah for error logging, although it seems like it would be nice if my general purpose logger and error logger could record to the same database so that I can view the history and not just the error. – Bill Jun 18 '14 at 21:39