我期待通過對異常進行分組來改進我們的.NET應用程序日誌記錄。理想情況下,我們會根據代碼中出現的位置以及它們是哪種類型的異常進行分組。所以基本上,我們最終會得到每個單一異常(具有不同日期,服務器變量等)的事件列表。唯一標識.NET異常
例如,如果我在我的註冊使用頁面上有一個方法,它引發了一個空引用異常5次,我會列出該異常只有一次,計數爲5次。另一個頁面上引發的另一個空引用異常將單獨列出。
是否有可能使用堆棧跟蹤來可靠地識別異常或是否有其他我已經錯過的東西?
我期待通過對異常進行分組來改進我們的.NET應用程序日誌記錄。理想情況下,我們會根據代碼中出現的位置以及它們是哪種類型的異常進行分組。所以基本上,我們最終會得到每個單一異常(具有不同日期,服務器變量等)的事件列表。唯一標識.NET異常
例如,如果我在我的註冊使用頁面上有一個方法,它引發了一個空引用異常5次,我會列出該異常只有一次,計數爲5次。另一個頁面上引發的另一個空引用異常將單獨列出。
是否有可能使用堆棧跟蹤來可靠地識別異常或是否有其他我已經錯過的東西?
如果一個異常被捕獲然後重新拋出,那麼它的堆棧跟蹤就會出現,就好像在重新拋出的地方發生了異常,而不是原來的地方。希望在你的代碼中沒有太多的東西在進行。如果不是,那麼堆棧跟蹤應該足以區分異常位置。
另一個問題是堆棧跟蹤是否相同,以便它們看起來不是兩個不同的異常,當它們確實相同時。這也不應該成爲一個問題,因爲根據我的經驗,堆棧軌跡是相同的。 (異常對象上的ToString()結果不會,一般是相同的。)
修訂
已經有關於什麼情況是,該Exception.StackTrace
將被損壞了一些討論。基本規則是,任何時候
throw *expression*;
被執行,那麼Exception
對象*expression*
標識將其StackTrace
屬性設置。如果省略了*expression*
throw;
那麼StackTrace將不會生效。
我找不到這兩種形式的術語,所以我會分別稱它們爲「顯式」和「隱式」。請注意,*expression*
解析爲的異常對象是new
還是已存在的對象都沒有關係。
這裏是一個程序來說明這個問題:
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--";
}
}
}
此程序將導致下面的輸出:
更換)在ThrowAnException(拋出捕獲的異常
更換導致異常扔在Replace()
RethrowExplicit捕獲的異常拋出ThrowAnException()
RethrowExplicit導致()在RethrowExplicit拋出的異常
在ThrowAnException()拋出RethrowImplicit捕獲的異常
RethrowImplicit導致()在ThrowAnException拋出的異常
myException與拋出的異常啓動at --empty--
myException更改爲在Main(String [] args)引發的異常
第六行是有趣的一個。
在那裏,我現在已經徹底擊敗了這一點。 :-)
*「如果一個異常被捕獲然後被重新拋出,那麼它的堆棧跟蹤就會出現,就好像在重新拋出的地方發生了異常,而不是原來的地方。」* - 除非你使用'throw;'而不是'throw ex ;' – 2011-03-29 23:55:39
@Maxim Gueivandov - 是的,這就是我的意思。 (正如你在我對@ChrisF的評論中可以看到的那樣)我試圖簡潔一點,因爲那是一個旁白。 – 2011-03-30 00:02:20
真棒回答隊友,謝謝堆 – 2011-03-31 01:38:01
只要你不吞嚥異常,然後拋出一個新的異常(如下面的壞例子),你應該沒問題。
try
{
... your code
}
catch (ExceptionA exA)
{
... some error handling
throw new ExceptionZ();
}
catch (ExceptionB exB)
{
... some error handling
throw new ExceptionZ();
}
這段代碼在這個實例中是壞的,因爲拋出一個異常將從原始異常中取代堆棧跟蹤。無論如何,這並不是一個好習慣,但在這種情況下,它會阻止你找到並記錄你的例外。
請注意'throw exA;'而不是拋出新的ExceptionZ();'也會*替換堆棧跟蹤。只有'throw;'(沒有指定異常對象)將保存在一個catch-and-throw scenerio中的堆棧跟蹤。 – 2011-03-29 23:45:43
@傑弗裏 - 那是我的觀點。我會說清楚。 – ChrisF 2011-03-30 07:51:52
我只是做了一個單獨的點,但現在你的更正已經使答案錯了!拋出任何異常對象 - 「new」或舊的 - 都會在該異常上設置堆棧跟蹤。 – 2011-03-30 16:59:51
在您的示例中(並且在大多數情況下,確實如此),根據null值的不同,我將有兩個不同的自定義異常類型被拋出。捕獲這些異常的代碼可以記錄必要的數據。
好吧,所以你基本上想要可靠地識別例外PLUS位置的異常。 堆棧跟蹤對我來說似乎很好。 但是,您也可以根據使用位置信息的識別方法名
System.Reflection.MethodBase.GetCurrentMethod()
可以得到從基地外,所有的異常類型,將在構造函數中調用GetCurrentMethod(),並通過只讀屬性暴露的方法名稱,所以你知道在哪裏想要捕捉異常的地方創建異常的地方。
例子:
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() {
}
}
空引用異常(OP中的示例)不會使用自定義異常基類。另外,如果拋出方法是從多個位置調用的,那麼異常的多個原因可能看起來是相同的,儘管它們非常不同,如果只使用方法名稱。 – 2011-03-29 23:36:37
爲什麼你需要的堆棧跟蹤?你難道不能用他們的名字來識別他們嗎?或者我在這裏錯過了你的觀點? – Adi 2011-03-29 23:04:26
增加了一個例子,希望讓自己更清晰。 – 2011-03-29 23:12:53