前回の記事C#でアスペクト指向 の続きで、アスペクトを有効に活用しようという内容です。
サンプルプログラム
開発したプログラムをリリースし、運用しているときに例外が発生した場合、通常のアプリケーションでは、ログファイルなりイベントログなりに例外情報を出力すると思います。しかし、その出力した情報から例外を解析するのが困難な場合が存在します。例えば、特定の入力の場合のみ発生する場合です。
下記のサンプルコードは入力が「A」の場合、例外が発生します。しかし、ログからはその例外の再現方法がわからないとしましょう。
class Program
{
static void Main(string[] args)
{
SampleClass cls = new SampleClass();
cls.SampleMethod("A");
Console.ReadLine();
}
}
[MyAspect]
public class SampleClass : ContextBoundObject
{
public string SampleMethod(string str)
{
int a, b, c;
if (str == "A")
{
a = 0;
b = 1;
c = b / a;
}
else
{
a = 1;
b = 1;
c = b / a;
}
}
アスペクト
例外を再現させたいけど再現できない。そんな時にアスペクトの登場です。下図の感じですね。(アスペクトについては前回の記事C#でアスペクト指向 を参照ください。)
以下のようなアスペクトを実装してみました。
実行後処理の引数の「IMethodReturnMessage」にはExceptionプロパティがあります。このプロパティには発生した例外オブジェクトが設定されます。ですので、このプロパティがNULL以外の場合に対象メソッドの引数等の情報を取得すればよいのです。対象メソッドの情報は何か?それは引数の「IMethodCallMessage」に格納されています。この中には対象のメソッド名から引数、メソッドが定義されているクラス情報まで持っています。「IMethodCallMessage」の内容をファイル出力して起き、その情報をもとにエラーを再現させてやればよいのです。んなわけで下記のコードが「IMethodCallMessage」をSerializeしてファイル出力するものになります。
public class MyReproduceException : IMyParts
{
public void PreProcessing(IMethodCallMessage call)
{
//前処理は何も実施しない。
}
public void PostProcessing(IMethodCallMessage call, IMethodReturnMessage res)
{
if(res.Exception != null)
{
string filePath = @"D:\temp\ReproduceExceptionData_" +
DateTime.Now.ToString("yyyyMMddhhmmss") + ".dat";
using (FileStream stream = new FileStream(filePath,
FileMode.CreateNew,
FileAccess.Write))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, call);
}
}
}
}
再現実行
上記でファイル出力した情報をもとに例外が発生した時の処理を再現させます。まず、Serializeしてファイル出力した情報を読み取ります。その後「IMethodCallMessage」オブジェクトに格納されている情報をもとに再現させます。
下記が再現実行用のプログラムです。このプログラムをVisual Studioでステップ実行することで、解析できます。
class Program
{
private string baseDir = null;
static void Main(string[] args)
{
Program prg = new Program();
prg.Execute(args);
}
private void Execute(string[] args)
{
string filePath = args[0]; //再現情報
baseDir = args[1]; //アセンブリパス
byte[] bytes = File.ReadAllBytes(filePath);
IMethodCallMessage message = null;
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream(bytes))
{
message = (IMethodCallMessage)bf.Deserialize(stream);
}
Type typ = Type.GetType(message.TypeName);
object target = Activator.CreateInstance(typ);
object retObject = message.MethodBase.Invoke(target, message.Args);
}
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs eArgs)
{
string asmPath = Path.Combine(baseDir, eArgs.Name.Split(',')[0] + ".dll");
if (File.Exists(asmPath))
{
Assembly asm = Assembly.LoadFrom(asmPath);
return asm;
}
asmPath = Path.Combine(baseDir, eArgs.Name.Split(',')[0] + ".exe");
if (File.Exists(asmPath))
{
Assembly asm = Assembly.LoadFrom(asmPath);
return asm;
}
return null;
}
}