最近、例外から可能な限り多くの詳細を取得するという問題が発生しました。理由?さて、出荷された製品の問題を解決する必要がある場合、通常はログだけがあります。
明らかに
Exception.ToString()
かなりうまく機能しますが、FaultExceptionを処理し、カスタム例外がどのような驚きをもたらすかを誰が知っているかはあまり役に立ちません。
では、まともなレベルのパラノイアで例外の詳細を取得するための最良の方法は何ですか?
最近、例外から可能な限り多くの詳細を取得するという問題が発生しました。理由?さて、出荷された製品の問題を解決する必要がある場合、通常はログだけがあります。
明らかに
Exception.ToString()
かなりうまく機能しますが、FaultExceptionを処理し、カスタム例外がどのような驚きをもたらすかを誰が知っているかはあまり役に立ちません。
では、まともなレベルのパラノイアで例外の詳細を取得するための最良の方法は何ですか?
私は周りを見回して、トピックについてグーグル検索しました。驚くべきことに、これに関する議論はそれほど多くありません。とにかく真髄をここに合成してみました。
ここに、例外をスローするサンプル コードがあります。
protected void TestExceptionDetails()
{
try
{
int zero = 0;
try
{
int z = zero / zero;
}
catch (Exception e)
{
var applicationException = new ApplicationException("rethrow", e);
// put some hint why exception occured
applicationException.Data.Add("divider_value", zero);
throw applicationException;
}
}
catch (Exception e)
{
var extendedexceptionDetails = GetExtendedexceptionDetails(e);
log.ErrorFormat("Detailed:{0}", extendedexceptionDetails);
}
}
GetExtendedExceptionDetails メソッドは次のとおりです。
/// <summary>
/// This utility method can be used for retrieving extra details from exception objects.
/// </summary>
/// <param name="e">Exception.</param>
/// <param name="indent">Optional parameter. String used for text indent.</param>
/// <returns>String with as much details was possible to get from exception.</returns>
public static string GetExtendedexceptionDetails(object e, string indent = null)
{
// we want to be robust when dealing with errors logging
try
{
var sb = new StringBuilder(indent);
// it's good to know the type of exception
sb.AppendLine("Type: " + e.GetType().FullName);
// fetch instance level properties that we can read
var props = e.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead);
foreach (PropertyInfo p in props)
{
try
{
var v = p.GetValue(e, null);
// in case of Fault contracts we'd like to know what Detail contains
if (e is FaultException && p.Name == "Detail")
{
sb.AppendLine(string.Format("{0}{1}:", indent, p.Name));
sb.AppendLine(GetExtendedexceptionDetails(v, " " + indent));// recursive call
}
// Usually this is InnerException
else if (v is Exception)
{
sb.AppendLine(string.Format("{0}{1}:", indent, p.Name));
sb.AppendLine(GetExtendedexceptionDetails(v as Exception, " " + indent));// recursive call
}
// some other property
else
{
sb.AppendLine(string.Format("{0}{1}: '{2}'", indent, p.Name, v));
// Usually this is Data property
if (v is IDictionary)
{
var d = v as IDictionary;
sb.AppendLine(string.Format("{0}{1}={2}", " " + indent, "count", d.Count));
foreach (DictionaryEntry kvp in d)
{
sb.AppendLine(string.Format("{0}[{1}]:[{2}]", " " + indent, kvp.Key, kvp.Value));
}
}
}
}
catch (Exception exception)
{
//swallow or log
}
}
//remove redundant CR+LF in the end of buffer
sb.Length = sb.Length - 2;
return sb.ToString();
}
catch (Exception exception)
{
//log or swallow here
return string.Empty;
}
}
ご覧のとおり、Reflection を使用してインスタンス プロパティを取得し、その値を取得します。コストがかかることはわかっていますが、具体的な例外が公開する可能性のあるプロパティについてはよくわかりません。また、アプリケーションでエラーが頻繁に発生してパフォーマンスが低下しないことを願っています。
では、実際に得たものを見てみましょう。
これは Exception.ToString が返すものです:
System.ApplicationException: rethrow ---> System.DivideByZeroException: Attempted to divide by zero.
at NET4.TestClasses.Other.TestExceptionDetails() in c:\tmp\prj\NET4\TestClasses\Other.cs:line 1116
--- End of inner exception stack trace ---
at NET4.TestClasses.Other.TestExceptionDetails() in c:\tmp\prj\NET4\TestClasses\Other.cs:line 1123
これにより、新しいメソッドが返されます。
Type: System.ApplicationException
Message: 'rethrow'
Data: 'System.Collections.ListDictionaryInternal'
count=1
[divider_value]:[0]
InnerException:
Type: System.DivideByZeroException
Message: 'Attempted to divide by zero.'
Data: 'System.Collections.ListDictionaryInternal'
count=0
InnerException: ''
TargetSite: 'Void TestExceptionDetails()'
StackTrace: ' at NET4.TestClasses.Other.TestExceptionDetails() in c:\tmp\prj\NET4\TestClasses\Other.cs:line 1116'
HelpLink: ''
Source: 'NET4'
TargetSite: 'Void TestExceptionDetails()'
StackTrace: ' at NET4.TestClasses.Other.TestExceptionDetails() in c:\tmp\prj\NET4\TestClasses\Other.cs:line 1123'
HelpLink: ''
Source: 'NET4'
ロギングにはlog4netを使用し、パフォーマンスのオーバーヘッドを削減するためにILog.IsErrorEnabledプロパティがあります。拡張例外処理を呼び出す前にチェックするだけです。
例外からのログ情報は役立つ場合がありますが、トランザクション ログのアプローチにより、より多くの情報が得られる場合があります。
アイデアは、できるだけ多くのログ メッセージをトレースすることですが、それらをログ ファイルに書き出すのではなく、メモリ内のキューに蓄積することです。ユーザーがコミットした操作が正常に完了した場合は、トレース メッセージのコレクションを破棄するだけです。ただし、例外がスローされた場合は、実行中に蓄積した例外とトレース メッセージの両方をログに記録できます。
その利点は明らかです。何か問題が発生したという情報を入手できるだけでなく、システム内で発生したプロセスによって例外が発生したこともわかります。