Uniquely identify .NET exceptions
I am looking to improve our .NET applications logging by grouping exceptions. Ideally we would group by where they occurred in the code and what type of exception they were. So essentially we would end up with a list of occurrences of each single exception (with different dates, server variables etc).
For example if I have a method on my register use page that has thrown a null reference exception 5 times I would list that exception just once w开发者_运维问答ith a count of 5 occurrences. Another null reference exception being thrown on a different page would be listed separately.
Is it possible to use the stacktrace to reliably identify exceptions or is there something else I have missed?
If an exception is caught and then rethrown, then its stack trace will appear as though the exception occured at the point of the rethrow rather than the original place. Hopefully, there is not much of that stuff going on in your code. If not, then the stack trace alone should be sufficient to distinguish the exception locations.
Another issue is whether the stack traces will be identical so that they do not appear to be two different exception when they are really the same. This, also, should not be a problem, since the stack traces are, in my experience, identical. (The ToString() results on the exception object will not, in general, be identical.)
UPDATED
There has been some discussion about exactly what the circumstances are that the Exception.StackTrace
will be corrupted. The basic rule is that any time
throw *expression*;
is executed, then the Exception
object that *expression*
identifies will have its StackTrace
property set. If the *expression*
is omitted
throw;
then the StackTrace will not be effected.
I could not find any terminology for these two forms, so I will call them "explicit" and "implicit" respectively. Note that it doesn't matter whether the exception object that *expression*
resolves to is new
or an already-existing object.
Here is a program to illustrate this behavior:
using System;
namespace FunWithExceptions
{
class Program
{
static void Main(string[] args)
{
try { Replace(); }
catch (InvalidOperationException ex) { DisplayResult("Replace resulted in", ex); }
try { RethrowExplicit(); }
catch (InvalidOperationException ex) { DisplayResult("RethrowExplicit resulted in", ex); }
try { RethrowImplicit(); }
catch (InvalidOperationException ex) { DisplayResult("RethrowImplicit resulted in", ex); }
InvalidOperationException myException = new InvalidOperationException();
DisplayResult("myException starts with", myException);
try { throw myException; }
catch (InvalidOperationException) { }
DisplayResult("myException changes to", myException);
Console.ReadLine();
}
static void ThrowAnException()
{ throw new InvalidOperationException("You messed up!"); }
static void Replace()
{
try { ThrowAnException(); }
catch (InvalidOperationException ex)
{
DisplayResult("Replace caught", ex);
throw new InvalidOperationException("Another mistake.");
}
}
static void RethrowExplicit()
{
try { ThrowAnException(); }
catch (InvalidOperationException ex)
{
DisplayResult("RethrowExplicit caught", ex);
throw ex;
}
}
static void RethrowImplicit()
{
try { ThrowAnException(); }
catch (InvalidOperationException ex)
{
DisplayResult("RethrowImplicit caught", ex);
throw;
}
}
static void DisplayResult(string context, Exception ex)
{
Console.WriteLine("{0} exception thrown at {1}", context, FirstMethodName(ex.StackTrace));
}
private const string methodNamePrefix = " at FunWithExceptions.Program.";
private static object FirstMethodName(string stackTrace)
{
stackTrace = stackTrace ?? string.Empty;
if (stackTrace.StartsWith(methodNamePrefix))
stackTrace = stackTrace.Substring(methodNamePrefix.Length);
int methodNameEndIndex = stackTrace.IndexOf(')');
if (methodNameEndIndex != -1)
stackTrace = stackTrace.Substring(0, methodNameEndIndex + 1);
if (stackTrace.Length > 0)
return stackTrace;
else
return "--empty--";
}
}
}
This program results in the following output:
Replace caught exception thrown at ThrowAnException()
Replace resulted in exception thrown at Replace()
RethrowExplicit caught exception thrown at ThrowAnException()
RethrowExplicit resulted in exception thrown at RethrowExplicit()
RethrowImplicit caught exception thrown at ThrowAnException()
RethrowImplicit resulted in exception thrown at ThrowAnException()
myException starts with exception thrown at --empty--
myException changes to exception thrown at Main(String[] args)
The sixth line is the interesting one.
There, I have now beat this point completely to death. :-)
Ok, so you basically want to reliably identify the exception PLUS location of the exception. Stack trace seems good to me. But you can also base your location identification using the method name
System.Reflection.MethodBase.GetCurrentMethod()
You can derive all your exception types from a base exception that will call GetCurrentMethod() in the constructor and expose the method name through a readonly property, so you know where was the point where the exception was created anywhere you want to catch the exception(s).
Example:
public class BaseException : ApplicationException {
public BaseException() {
_originalMethod = System.Reflection.MethodBase.GetCurrentMethod().Name;
}
private string _originalMethod;
public string OriginalMethod { get { return _originalMethod; } }
}
//now, create tons of custom exceptions:
public class MyException1 : BaseException {
public MyException1()
: base() {
}
}
//create more custom exceptions...
public class MyExceptionNNN : BaseException {
public MyExceptionNNN()
: base() {
}
}
As long as you are not swallowing exceptions and then throwing a new exception (as in the bad example below) you should be OK.
try
{
... your code
}
catch (ExceptionA exA)
{
... some error handling
throw new ExceptionZ();
}
catch (ExceptionB exB)
{
... some error handling
throw new ExceptionZ();
}
This code is bad in this instance as throwing an exception replaces the stack trace from the original exception. It's not good practice in general anyway, but in this case it would stop you finding and logging your exceptions uniquely.
In your example (and in most cases, really), I would have two different custom exception types thrown, depending on what the null value was. The code that catches those exceptions can log the necessary data.
精彩评论