代码改变世界

一个自己写的组件--异常报告(1):组件介绍与简单架构

2008-07-01 15:27  GUO Xingwang  阅读(2673)  评论(16编辑  收藏  举报
 

        大家是否有时感觉在程序调试时对于无法获得异常信息而苦恼,尤其是当程序已经脱离调试环境部署到客户机上时更是如此,对于客户机上的程序出现的异常无法确定异常的详细信息,结果找不到问题所在.反正我是经历过这种无奈.前一阶段的项目基本已经结了,这才抽出时间把这个组件整理一下,实际上已经有很多而且类似的共享组件可以使用,但是可能由于种种原因不是很理想,还不如自己写一个,这样可以更好的解决问题,满足使用上的需求.现在我把这个组件分享给大家,并在文末提供源码下载,希望大家提出宝贵意见以便改进.这是我发的第一篇技术文章,在语言和水平上的缺陷还请见谅,相信慢慢我就会习惯这里的生活.
             

        闲话少说,可能大家都已经迫不及待了,先让我们看看这个报告异常的组件吧!这个组件使用VS2005开发,相信很容易就可以转成VS2008的项目,解决方案结构如下图:


   

        首先让我们认识一下IOutput这个接口,这个接口主要描述了报告日志的输出形式,里面只定义了一个方法(如果以后使用时发现不能满足需求,可以多加入一些描述信息和方法处理):

IOutput.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace MyDebuger
{
    /// <summary>
    /// 此接口提供一个方法用于输出异常信息与相应参数值
    /// </summary>
    interface IOutput
    {
        /// <summary>
        /// 输出日志方法
        /// </summary>
        /// <param name="moduleName">抛出异常方法名</param>
        /// <param name="parametersList">方法参数列表,调用对象的ToString方法完成</param>
        /// <param name="exceptionMsg">异常的Message字符串</param>
        /// <param name="traceDetail">异常的堆栈信息</param>
        bool OutputLog(string moduleName,
                                      string parametersList,
                                      string exceptionMsg,
                                      string traceDetail);
    }
}



























 

        不过,在目前的这个组件中我对这个接口只有一个实现,就是报告异常信息到Windows日志管理器,以后我们可以增加更多的实现IOutput接口的类来完成日志的输出,异常报告使用了.Net类库中的System.Diagnostics命名空间,将一些参数通过调用EventLog.CreateEventSource方法写入日志,这一部分的代码如下,很简单的:

EventLogsOutput.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using System.Diagnostics;

namespace MyDebuger
{
    /// <summary>
    /// 将异常信息写入到事件日志
    /// </summary>
    class EventLogsOutput: IOutput
    {
        public bool OutputLog(string moduleName,
                                      string parametersList,
                                      string exceptionMsg,
                                      string traceDetail)
        {
            string _message;

            string _source = "";
            string _log = "";
            string _event = "";

            _source = "Debuger";
            _log = "Application";
            _event = "==========begin==========\n";
            _event += DateTime.Now.ToString("f", DateTimeFormatInfo.InvariantInfo) + "\n";
            _event += "Module:" + moduleName + "\n";
            _event += "Parameters:" + parametersList + "\n";
            _event += "Message:" + exceptionMsg + "\n";
            _event += "Trace:" + traceDetail + "\n";
            _event += "===========end===========\n";

            try
            {
                if (!EventLog.SourceExists(_source))
                    EventLog.CreateEventSource(_source, _log);

                EventLog.WriteEntry(_source, _event,
                    EventLogEntryType.Error);
                return true;
            }
            catch (Exception ex)
            {
                _message = ex.Message;
                return false;
            }
        }
    }
}

        我在文章中会把类似这样的实现称为日志输出处理器(简称处理器,有点像CPU啊),我们还可以实现类似写日志到数据库,写日志到文件系统,甚至发送日志到管理员的邮箱中等,这一部分如果有时间的话都可以进行扩展.当然这也就给组件带来一定的扩展功能,抽象出接口就有这样的好处.如果继续完善的话,可以把这一部分做成一个Config来让开发者自己配置完成,这一部分我打算在本系列的第二部分进行介绍.
 

        让我们接下来看看组件的主要部分异常发掘机吧,它是主要将异常整理并通过上面的接口发送出去的类,这一部分在封装时同样考虑了以后的扩展问题,定义一个output,可以将任何实现了IOutput接口的对象赋予它,在实际上执行时output.OutputLog是稳定的,此外关于output变量的赋值我会在本系列的第二部分通过定义Config由用户自己指定,在这里我就使用了组件中的唯一的处理器EventLogsOutput来完成.异常发掘机使用了.Net的委托,主要是为了使用上的方便而采用,用户甚至可以定义自己的处理程序来完成日志的提交.默认情况下使用EventLogsOutput提交,当Exception属性改变时执行提交,类中对一些描述异常的成员使用了Static定义,主要想保存最后一个异常信息,同样是为以后扩展与修改方便,此外注意它对外提供了两个构造器来完成,没有参数的是采用默认的方式,带有参数的是为了扩展提供的:

Detector.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace MyDebuger
{
    /// <summary>
    /// 发现异常,并将异常提交相应实现IOutput接口的类处理
    /// </summary>
    public class Detector
    {
        //检测系统中出现的最后一个异常信息
        private static string module = null;//异常方法名称
        private static Exception exception = null;//异常
        private static Hashtable parameters = null;//用于存储抛出异常时的方法参数值

        //定义事件处理委托,内部处理使用事件模型处理
        public delegate void ExceptionHandler(object sender, System.EventArgs e);
        public event ExceptionHandler ExceptionChangedEvent;//可供外部使用

        private IOutput output;//用于存储一个实现接口的实例

        //构造函数,初始化委托处理,使用默认处理方式
        public Detector()
        {
            this.ExceptionChangedEvent += new ExceptionHandler(this.ProcessExceptionChangedEvent);
            output = new EventLogsOutput();//可扩展部分,以后涉及一个Config配置处理
        }

        //可以使用外部提供的处理器
        public Detector(ExceptionHandler myExceptionHandler)
        {
            this.ExceptionChangedEvent += myExceptionHandler;
        }

        //抛出异常方法名访问器
        public string Module
        {
            set
            {
                module = value;
            }
        }

        //异常访问器,新设置异常时触发了处理事件
        public Exception Exception
        {
            set
            {
                exception = value;
                RaiseExceptionChangedEvent();
            }
        }

        //异常发生时参数列表访问器
        public Hashtable Parameters
        {
            set
            {
                parameters = value;
            }
        }

        //触发事件
        private void RaiseExceptionChangedEvent()
        {
            System.EventArgs e = new System.EventArgs();
            ExceptionChangedEvent(this, e);
        }

        //默认处理器
        private void ProcessExceptionChangedEvent(object sender, System.EventArgs e)
        {
            ExObjectInfo myExObjectInfo = new ExObjectInfo();
            myExObjectInfo.Module = module;
            myExObjectInfo.Exception = exception;
            myExObjectInfo.Parameters = parameters;

            //报告异常,属于稳定部分
            output.OutputLog(myExObjectInfo.Module,
                myExObjectInfo.GetParametersList(),
                myExObjectInfo.Exception.Message,
                myExObjectInfo.Exception.StackTrace);
        }
    }
}

        这一部分使用了事件处理模型对异常进行提交 ExObjectInfo是对异常信息进行的一个简单的封装,主要是为参数传递方便使用:

Common.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

using Debug = System.Diagnostics.Debug;//断言使用
using StackTrace = System.Diagnostics.StackTrace;

namespace MyDebuger
{
    //补充一个系统错误实体,以对错误进行封装处理
    internal class ExObjectInfo
    {
        private string module = null;
        private Exception exception = null;
        private Hashtable parameters = null;

        //抛出异常方法名访问器
        public string Module
        {
            set
            {
                module = value;
            }
            get
            {
                return module;
            }
        }

        //异常访问器
        public Exception Exception
        {
            set
            {
                exception = value;
            }
            get
            {
                return exception;
            }
        }

        //异常发生时参数列表访问器
        public Hashtable Parameters
        {
            set
            {
                parameters = value;
            }
            get
            {
                return parameters;
            }
        }

        //获得方法参数列表
        public string GetParametersList()
        {
            Debug.Assert(parameters != null);
            string tempList = "[";
            foreach (DictionaryEntry de in parameters)
            {
                tempList += de.Key.ToString() + "=" + de.Value.ToString() + ";";
            }
            tempList += "]";
            return tempList;
        }
    }
}

      在解决方案中还有一个TestDebuger项目,这是我的一个Demo测试项目,启动这个项目执行以后调用一个方法将抛出的异常发送出去:

Program.cs

using System;
using System.Collections.Generic;
using System.Text;

using MyDebuger;

namespace TestDebuger
{
    class Program
    {
        public static void Main(string[] args)
        {
            RL();
            string para1 = "thricing.country";
            int para2 = 24;
            Person para3 = new Person("thricing.country", 24);
            Program program = new Program();
            program.CatchExceptionmMethod(para1, para2, para3);
            Console.WriteLine("请查看日志");
            RL();
        }

        private void CatchExceptionmMethod(string para1, int para2, Person para3)
        {
            try
            {
                throw new Exception("这是一个异常");
            }
            catch(Exception e)
            {
                Object[] obj = new Object[] { para1, para2, para3 };

                //报告异常
                Debuger.Debug(e,obj);
            }
        }

        #region Helper methods

        private static void WL(object text, params object[] args)
        {
            Console.WriteLine(text.ToString(), args);
        }

        private static void RL()
        {
            Console.ReadLine();
        }

        private static void Break()
        {
            System.Diagnostics.Debugger.Break();
        }

        #endregion
    }

    class Person
    {
        string name;
        int age;

        public Person(string name, int age)
        {
            this.name = name;
            this.age = age;
        }

        public override string  ToString()
        {
            return "Person'name is " + name + ", person'age is " + age.ToString();
        }
    }
}

  
    其中:
              

        这就是异常报告组件的使用
,将提交的参数付给一个Object的数组,如果参数是引用的类型,就会调用对象的ToString方法转换成字符串输出.执行以后我们可以打开Windows日志查看器进行日志的查看:


  

        选择程序抛出的错误信息可以看到:


 

==========begin==========

Tuesday, 01 July 2008 11:34

Module:CatchExceptionmMethod

Parameters:[para2=24;para3=Person'name is thricing.country, person'age is 24;para1=thricing.country;]

Message:这是一个异常

Trace: TestDebuger.Program.CatchExceptionmMethod(String para1, Int32 para2, Person para3) 位置 D:\work\MyDebuger\TestDebuger\Program.cs:行号 27

===========end===========



上面的信息已经可以让程序员找到问题的所在了吧.


    这个组件在设计时考虑到以下几个可以扩展的地方:

1.异常发掘机中的几个异常描述参数使用Static关键字进行定义,主要考虑到这里如果经过简单修改可以存储系统中最后抛出的异常以便进行跟中.

2.异常发掘机提供了两个构造器,没有参数的实现的是默认的处理方式,使用第二个带有一个委托的构造器可以对异常的报告采用一种用户自定义的方式完成.

3.我们可以对实现IOutput接口的处理器配置指派,用户还可以自定义一个全新的处理器进行使用.


在第二部分我将主要对这个组件的配置部分进行开发,用以完成使用者可以通过配置轻松的完成处理器的选择.

源代码下载:
MyDebugerAndDemo_SRC20080701.rar