7

I've implemented a custom exception filter based on this blog post https://blog.kloud.com.au/2016/03/23/aspnet-core-tips-and-tricks-global-exception-handling/ .

I now noticed that I cannot get the file name or the line number where the exception occurred from StackFrame; the value for method GetFileLineNumber is always 0.

A quick example of what I tried:

StackTrace trace = new StackTrace(exception, true);
StackFrame[] stackFrames = trace.GetFrames();
if (stackFrames != null) {
 foreach (StackFrame traceFrame in stackFrames) {
   sourceFile = traceFrame.GetFileName();
   sourceLineNumber = traceFrame.GetFileLineNumber();
  }
}

Just to mention: GetFileName returns also null, but I can get the namespace and class name from various locations, so it's not a problem.

Noticed somewhere that I should have the PDB file on the folder for this to work and I checked that I already have it. And even if it worked, this should work in production environment as well.

Tried also using Caller Information (https://msdn.microsoft.com/en-us/library/mt653988.aspx) but there's a bit different problem. The exception filter has to implement IExceptionFilter interface and use it's method. Therefore I cannot add the attributes even if they are optional.

Here's the code part from Startup.cs:

public void ConfigureServices(IServiceCollection services) {
 ...
 services.AddMvc(config => {
  config.Filters.Add(new ApplicationExceptionFilter());
 });
}

And here's the custom Filter

public class ApplicationExceptionFilter : IExceptionFilter {
 public void OnException(ExceptionContext context) {
  ApplicationExceptionHandler.handleException(context.HttpContext, context.Exception);
 }
}

And what I tried for getting the Caller Information was:

public void OnException(ExceptionContext context,
 [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0) {

But that will not work since it doesn't implement the interface anymore.

How can I get the line number while still handling the exceptions in a single place?


Assumed that my problem is the fact that I'm not getting line number from stack trace and reproduced the problem with:

public ActionResult ServerError() {
 Int32 x = 0;
 try {
  return Content((1 / x).ToString());
 } catch (Exception ex) {
  StackTrace trace = new StackTrace(ex, true);
  StackFrame[] stackFrames = trace.GetFrames();
  if (stackFrames != null) {
   foreach (StackFrame traceFrame in stackFrames) {
    String sourceFile = traceFrame.GetFileName();
    int sourceLineNumber = traceFrame.GetFileLineNumber();
   }
  }
 }
 return Content("");
}

Still not getting values for sourceFile (it's null) or sourceLineNumber (it's 0).

mepsi
  • 101
  • 6
  • In optimized code aka. Release builds, the assembly code / IL code may no longer match line numbers. – Thomas Weller Jul 12 '16 at 11:38
  • @Thomas There's a difference between not matching and them being completely null. – DavidG Jul 12 '16 at 11:40
  • @Thomas there is no line number in release builds – Zein Makki Jul 12 '16 at 11:41
  • Now I'm interested in DavidG's and user3185569's answers. – Thomas Weller Jul 12 '16 at 11:43
  • @user3185569: Some people may disagree: http://stackoverflow.com/a/935898/4136325 – Thomas Weller Jul 12 '16 at 11:50
  • @Thomas I mean in optimized code. – Zein Makki Jul 12 '16 at 11:56
  • @user3185569: it's simply not true. Try yourself: write a C# program, set PDB info to "full", build in Release mode. Open the executable in ILDASM. Check [x] "show source lines" - there they are. – Thomas Weller Jul 12 '16 at 12:05
  • That'd mean I can't rely on the stack trace even if I got something from there. What about Caller Information? That however seems to require different handling. – mepsi Jul 12 '16 at 12:08
  • 1
    As long as you have the PDB file in place, you should be able to get line numbers. Are you sure you have the right files in place and there's no permission issues with them? – DavidG Jul 12 '16 at 12:43
  • Along with @DavidG last comment the PDB files must match the .dll/.exe file. That is you cant rebuild the solution later and only take the compiled .PDB files and dump with in a folder with existing .DLL/.EXE files as they would not match (based on a GUID believe). You should have the options None, Pdb Only, Full. For release builds where you still want line numbers in the stack trace Pdb Only is usually a good choice, again if you are optimizing code the actual line number will probably be off. – Igor Jul 12 '16 at 17:00

1 Answers1

-1

Take a look at the DeveloperErrorPageMiddleware implementation:

    private IEnumerable<ErrorDetails> GetErrorDetails(Exception ex)
    {
        for (var scan = ex; scan != null; scan = scan.InnerException)
        {
            var stackTrace = ex.StackTrace;
            yield return new ErrorDetails
            {
                Error = scan,
                StackFrames = StackTraceHelper.GetFrames(ex)
                    .Select(frame => GetStackFrame(frame.MethodDisplayInfo.ToString(), frame.FilePath, frame.LineNumber))
            };
        };
    }

    // make it internal to enable unit testing
    internal StackFrame GetStackFrame(string method, string filePath, int lineNumber)
    {
        var stackFrame = new StackFrame
        {
            Function = method,
            File = filePath,
            Line = lineNumber
        };

        if (string.IsNullOrEmpty(stackFrame.File))
        {
            return stackFrame;
        }

        IEnumerable<string> lines = null;
        if (File.Exists(stackFrame.File))
        {
            lines = File.ReadLines(stackFrame.File);
        }
        else
        {
            // Handle relative paths and embedded files
            var fileInfo = _fileProvider.GetFileInfo(stackFrame.File);
            if (fileInfo.Exists)
            {
                // ReadLines doesn't accept a stream. Use ReadLines as its more efficient
                // relative to reading lines via stream reader
                if (!string.IsNullOrEmpty(fileInfo.PhysicalPath))
                {
                    lines = File.ReadLines(fileInfo.PhysicalPath);
                }
                else
                {
                    lines = ReadLines(fileInfo);
                }
            }
        }

        if (lines != null)
        {
            ReadFrameContent(stackFrame, lines, stackFrame.Line, stackFrame.Line);
        }

        return stackFrame;
    }
Victor Hurdugaci
  • 28,177
  • 5
  • 87
  • 103
  • How does this help with the question asked? – DavidG Jul 12 '16 at 17:06
  • Helps a bit forward anyhow. I now noticed that I do get the line numbers to DeveloperExceptionPage. However, looking at the sources, it seems to get it from the stack trace, which I'm not. – mepsi Jul 13 '16 at 05:46