开发.Net异常发布器的步骤
开发.Net异常发布器的步骤
.net中有着很完善的异常系统,在发生了异常之后,我们可以写一些代码来处理异常,从另外一方面来说,我们需要一个很灵活而方便的异常发布器来向用户显示一些友好信息和记入日志。因为很明显的,我们不希望用户看到的是一些诸如“违反了 UNIQUE KEY 约束 'IX_Country'。不能在对象 'Country' 中插入重复键。”之类的信息,如果让用户看到的是“系统错误:您不能插入重复记录!本条记录某些信息违反了数据库唯一性原则,请检查您的纪录以保持信息的唯一性。 请与系统管理员联系!”之类的信息,这会友好的多,即使是除此之外不对异常作任何处理。同时,我们还希望能够在日志中写入异常的相关信息,以备系统管理员来查看这些日志,做出相应的处理。根据需要我们可能会选择是写入操作系统日志里或者是一个XML文件里。
需求分析
了解了以上一些基本需求之后,我们来做个分析,看看做出这么一个异常发布器来,还可以加入一些什么功能,也就是,完整地需求是什么。
1. 发生异常后,向用户显示一些友好信息,而不是只有程序员或系统管理员才看得懂的原始信息。友好信息的格式类似于“系统错误:*************。请联系管理员1。
2. 异常的有关信息记入日志,因为这些信息是供程序员或系统管理员处理用的,所以这里要有尽可能详细的信息,至少,异常原始信息、出错时间等等要有。
3. 日志记录的位置,要可由系统管理员来决定写入操作系统的日志或者是一个XML文件中,如果是XML文件中,要可以定义存放位置。
4. 向用户显示友好信息信息的时候,应当不只是不论什么异常都千篇一律显示相同的友好信息,而应当根据不同的异常显示不同的友好信息。
5. 异常应当分等级,根据不同等级的异常进行显示和记录,以方便用户和系统管理员。
6. 应当考虑多语言特性,不同语言的用户看到的有好信息和日志记录也是不同的。
7. 系统管理员定义的异常匹配资料应该是结构化的、分层次的,比如,系统可能找不不到关于“违反了 UNIQUE KEY 约束 'IX_Country'。不能在对象 'Country' 中插入重复键。”相匹配的友好信息,但是应该可以找到“违反了 UNIQUE KEY 约束”相匹配的信息,甚至只找到“System.Data.SqlClient.SqlException”类新的异常,这样,系统管理员定义得越详细,则匹配越精确,而如果不够详细,发布器一样能够正常工作。
解决思路
当一个异常发生时,我们首先通过该异常的Type和Message来在系统管理员定义的异常匹配资料中查询匹配异常,如果查询到了匹配异常,那么就像用户显示匹配异常中定义的友好信息,同时,根据设置将异常有关信息记入操作系统的日志或者指定的XML文件中。由于异常的Message是根据当前线程的文化来显示相应语言的信息的,所以异常匹配资料要考虑到这种情况,并且显示的用户友好信息也要根据线程的文化属性来采用不同语言显示。日志文件记录的位置,可在应用程序配置文件中设定即可。异常的等级也是在异常匹配资料中记录,这就OK了。而结构化、分层次的异常匹配资料,则需要设计一定的算法来进行搜索。
实现过程
在写具体算法来实现以上功能时,我们先来看一下其中用到的基本枚举和数据表结构:
第一个枚举是异常严重等级,这些枚举与操作系统日志的类型相匹配,为的是方便的按类别记入系统日志中:
///
/// 事件等级
///
public enum EventLevel
{
///
/// 错误事件。它指示用户应该知道的严重问题(通常是功能或数据的丢失)。
///
Error,
///
/// 失败审核事件。它指示当审核访问尝试失败(例如打开文件的尝试失败)时发生的安全事件。
///
FailureAudit,
///
/// 信息事件。它指示重要、成功的操作。
///
Information,
///
/// 成功审核事件。它指示当审核访问尝试成功(例如成功登录)时发生的安全事件。
///
SuccessAudit,
///
/// 警告事件。它指示并不立即具有重要性的问题,但此问题可能表示将来会导致问题的条件。
///
Warning
}
第二个枚举是日志保存位置,它用于定义程序的异常日志将保存在哪里,系统日志、XML文件抑或是两者:
///
/// 异常日志类型
///
public enum CanUseExceptionLogType
{
///
/// 日志保存于XML文件中
///
XMLFile,
///
/// 日志保存于系统日志中
///
SystemLog,
///
/// 日志保存位置为全部:XML文件和系统日志
///
All
}
一个数据集是自定义通用信息,针对系统支持的每一种语言会有一些记录,记录中是一些通用的信息,比如可能每个异常都回出现的“系统异常1、“请与系统管理员联系”之类的,还有就是日志中记录的信息的信息头,如“用户名:”、“出错时间:”等等,这些都可以由系统管理员来自定义。结构如下:
下面详细解释一下各个字段:
Language:语言代码,如en-US,zh-CN PK
UnchartedExceptionMessage:未找到匹配异常时显示的信息,如“未知错误1
EorrorExceptionMessage:找到错误等级为"Error"的匹配异常时通知用户发生异常时显示的信息,如“系统错误”
EorrorPleaseDoSomethingMessage:找到错误等级为"Error"的匹配异常时通知用户后续操作时显示的信息,如“请联系管理员1
FailureAuditExceptionMessage:找到错误等级为"FailureAudit"的匹配异常时通知用户发生异常时显示的信息,如“系统错误”
FailureAuditPleaseDoSomethingMessage:找到错误等级为"FailureAudit"的匹配异常时通知用户后续操作时显示的信息,如“请联系管理员1
InformationExceptionMessage:找到错误等级为"Information"的匹配异常时通知用户发生异常时显示的信息,如“系统错误”
InformationPleaseDoSomethingMessage:找到错误等级为"Information"的匹配异常时通知用户后续操作时显示的信息,如“请联系管理员1
SuccessAuditExceptionMessage:找到错误等级为"SuccessAudit"的匹配异常时通知用户发生异常时显示的信息,如“系统错误”
SuccessAuditPleaseDoSomethingMessage:找到错误等级为"SuccessAudit"的匹配异常时通知用户后续操作时显示的信息,如“请联系管理员1
WarningExceptionMessage:找到错误等级为"Warning"的匹配异常时通知用户发生异常时显示的信息,如“系统错误”
WarningPleaseDoSomethingMessage:找到错误等级为"Warning"的匹配异常时通知用户后续操作时显示的信息,如“请联系管理员1
LogTimeHeader:异常发生时间的信息头
ExceptionTypeHeader:异常类型信息头
ExceptiomOriginalMessageHeader:异常原始信息信息头
ExceptionSourceHeader:发生异常的应用程序或对象信息头
ExceptionStackTraceHeade:发生异常的堆栈信息头
ExceptionTargetSiteHeader:引发异常的方法信息头
ExceptionHelpLinkHeader:异常的帮助链接信息头
CustomMessageHeader:异常的系统管理员自定义信息信息头
CustomHelpLinkHeader:异常的系统管理员系统管理员自定义帮助链接信息头
UserNameHeader:引发异常的用户名信息头
ExceptionInducedPathHeader:发生异常的模块路径信息头
一个数据集是异常匹配表,由系统管理员定义,这样发生异常时便是匹配这张表中的记录来取得用户友好信息:
下面将每个字段解释一下:
Guid:全句唯一标识符 PK
Language:语言代码,如en-US,zh-CN
EventLevel:事件等级,必须是EventLevel枚举中的值
Type:异常类型,采用完全限定名
InMessage1:异常原始信息关键字1
InMessage2:异常原始信息关键字2
InMessage3:异常原始信息关键字3
InMessage4:异常原始信息关键字4
OutMessage:异常的管理员定义用户提示信息
HelpLink:异常的管理员定义帮助链接
在这个地方可能会觉得上面这种数据结构设计并没有体现出层次化的要求,在这里,我的层次化设计是这样的:错误的分析和匹配依靠ExceptionDetail中当前线程语言相关记录的Type和InMessage1、InMessage2、InMessage3、InMessage4来判定,其中Type是异常的类型,采用完全限定名,InMessage1~4是错误信息中的关键字。
以上五个匹配条件构成层次化关系:
Type.InMessage1.InMessage2.InMessage3.InMessage4;
这样做可能有点不太符合层次化的要求,但是却有一个好处,那就是灵活。客户方的系统管理员要定义一个新的异常匹配记录TypeA.Message1.Message2.Message3.Message4时,他并不需要从顶层一层层的定义下来,而只需要直接定义这一个就可以了,所以权衡之后,决定采用这种数据结构,以给各户带来最大的方便。
还有一个数据集是定义的异常日志的结构,当发生异常时,便以此结构记入日志:
下面解释一下字段:
Guid:全句唯一标识符 PK
EventLevel:异常等级,是EventLevel枚举中的值