我们搭建好了项目的整体多数据库环境,实现了项目的多数据库访问,而整个项目中最主要的异常处理却没有进行部署,今天我们就使用企业库中的Exception Handling+Logging模块为项目加上异常处理以及异常日志记录。
(注:关于Exception Handling和Logging模块的相关基本概念可以查看TerryLee的异常处理和日志检测这2篇文章)
首先说一下企业库Logging模块的个人感觉,个人感觉企业库的日志记录太繁琐了,而且要自定义也比较烦,无法通过简单的配置达到我自己的要求,企业库中的日志记录模块在可以记录许多信息如下:
Timestamp: 2010-6-12 3:16:39
Message: HandlingInstanceID: 669fed01-a758-434b-896e-a8e25ebf8c9b
An exception of type 'System.Exception' occurred and was caught.
----------------------------------------------------------------
06/12/2010 11:16:39
Type : System.Exception, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : Test
Source : EntLibStudy.Helper
Help link :
Data : System.Collections.ListDictionaryInternal
TargetSite : System.String Test()
Stack Trace : 在 EntLibStudy.Helper.BasePage.Test() 位置 F:\EntLibStudy\Helper\BasePage.cs:行号 87
在 EntLibStudy.Helper.BasePage.<Page_Load>b__0() 位置 F:\EntLibStudy\Helper\BasePage.cs:行号 81
在 Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionManagerImpl.Process[TResult](Func`1 action, TResult defaultResult, String policyName) 位置
以下省略N行。。。。。。
这些信息很多都不是我想要的,我想要的仅仅是异常的提示信息,异常发生的时间,以及异常发生的位置,好方便我们第一时间到异常发生的源头进行调试检查(可能企业库的这些异常信息更加有用,但是我个人认为很多时候都会干扰我们),所以我们仅仅需要其中的几条有用的信息就够了,比如Message,Timestamp、Stack Trace和Severity这4个就基本上够用了,所以我做了个处理,就是使用企业库中Logging模块提供的自定义CustomerTraceListener来实现我们需要的功能。
首先建立一个异常日志记录表(SQLite版)
1 |
CREATE TABLE [ExceptionLog] ( |
2 |
[Id] integer PRIMARY KEY AUTOINCREMENT NOT NULL, |
3 |
[Message] nvarchar(1024) NOT NULL, |
4 |
[LogDate] nvarchar(1024) NOT NULL, |
5 |
[ExceptionLevel] nvarchar(32) NOT NULL, |
6 |
[Exception] ntext NOT NULL |
我编写了一个类继承自CustomTraceListener,并重写了记录方法,具体代码如下:
002 |
using System.Collections.Generic; |
004 |
using System.Data.Common; |
005 |
using System.Diagnostics; |
006 |
using System.Globalization; |
010 |
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration; |
011 |
using Microsoft.Practices.EnterpriseLibrary.Data; |
012 |
using Microsoft.Practices.EnterpriseLibrary.Logging; |
013 |
using Microsoft.Practices.EnterpriseLibrary.Logging.Configuration; |
014 |
using Microsoft.Practices.EnterpriseLibrary.Logging.Formatters; |
015 |
using Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners; |
017 |
namespace EntLibStudy.Helper.EntLibExtension.ExceptionExtension |
019 |
[ConfigurationElementType( typeof (CustomTraceListenerData))] |
020 |
public class ExceptionCustomerListener : CustomTraceListener |
022 |
string writeLogSQL = String.Empty; |
026 |
public ExceptionCustomerListener() |
029 |
database = DBHelper.CreateDataBase(); |
032 |
public override void TraceData(TraceEventCache eventCache, string source, |
033 |
TraceEventType eventType, int id, object data) |
035 |
if (( this .Filter == null ) || this .Filter.ShouldTrace(eventCache, source, eventType, id, null , null , data, null )) |
037 |
if (data is LogEntry) |
039 |
LogEntry logEntry = data as LogEntry; |
040 |
ExecuteSQL(logEntry); |
042 |
else if (data is string ) |
044 |
Write(data as string ); |
048 |
base .TraceData(eventCache, source, eventType, id, data); |
053 |
public override void Write( string message) |
055 |
ExecuteWriteLogSQL(TraceEventType.Information, DateTime.Now, message, database); |
058 |
public override void WriteLine( string message) |
066 |
/// <param name="logEntry">日志对象</param> |
067 |
private void ExecuteSQL(LogEntry logEntry) |
069 |
using (DbConnection connection = database.CreateConnection()) |
074 |
using (DbTransaction transaction = connection.BeginTransaction()) |
078 |
ExecuteWriteLogSQL(logEntry, database, transaction); |
079 |
transaction.Commit(); |
083 |
transaction.Rollback(); |
098 |
/// <param name="severity">异常等级</param> |
099 |
/// <param name="message">消息</param> |
100 |
/// <param name="db">保存日志的数据库实例</param> |
101 |
private void ExecuteWriteLogSQL(TraceEventType severity, DateTime timeStamp, string message, Database db) |
103 |
writeLogSQL = ( string ) this .Attributes[ "writeLogSQL" ]; |
104 |
DbCommand cmd = db.GetSqlStringCommand(writeLogSQL); |
105 |
string exceptionMessage = Utils.GetBetweenString(message, "Message :" , "Source :" , 9); |
106 |
string exceptionInfo = Utils.GetBetweenString(message, "Stack Trace :" , "Additional Info:" , 13); |
107 |
db.AddInParameter(cmd, "@Message" , DbType.String, exceptionMessage); |
108 |
db.AddInParameter(cmd, "@LogDate" , DbType.DateTime, timeStamp); |
109 |
db.AddInParameter(cmd, "@Level" , DbType.String, message); |
110 |
db.AddInParameter(cmd, "@Exception" , DbType.String, exceptionInfo); |
111 |
db.ExecuteNonQuery(cmd); |
117 |
/// <param name="logEntry">日志对象</param> |
118 |
/// <param name="db">保存日志的数据库实例</param> |
119 |
/// <param name="transaction">事务对象</param> |
120 |
private void ExecuteWriteLogSQL(LogEntry logEntry, Database db, DbTransaction transaction) |
122 |
writeLogSQL = ( string ) this .Attributes[ "writeLogSQL" ]; |
123 |
DbCommand cmd = db.GetSqlStringCommand(writeLogSQL); |
124 |
string exceptionMessage = Utils.GetBetweenString(logEntry.Message, "Message :" , "Source :" , 9); |
125 |
string exceptionInfo = Utils.GetBetweenString(logEntry.Message, "Stack Trace :" , "Additional Info:" , 13); |
126 |
db.AddInParameter(cmd, "@Message" , DbType.String, exceptionMessage); |
127 |
db.AddInParameter(cmd, "@LogDate" , DbType.DateTime, logEntry.TimeStamp.ToLocalTime()); |
128 |
db.AddInParameter(cmd, "@Level" , DbType.String, logEntry.LoggedSeverity); |
129 |
db.AddInParameter(cmd, "@Exception" , DbType.String, exceptionInfo); |
130 |
db.ExecuteNonQuery(cmd, transaction); |
其中在类的初始化的时候获取配置文件的默认数据库对象,通过重写TraceData方法来调用ExecuteSQL方法来执行异常日志插入。
在ExecuteWriteLogSQL方法中有句代码:
1 |
writeLogSQL = ( string ) this .Attributes[ "writeLogSQL" ]; |
这个代码就是从配置文件中Listener的Attributes中获取所配置的执行SQL语句(这里不同于Logging模块自带的数据库以存储过程的记录方式,而是使用配置的SQL语句的方式,因为本项目是面向多数据库的,并不是所有的数据库都有存储过程的,比如SQLite),下面看下具体的配置信息:
配置文件创建主要分为以下2步:
1、在企业库的配置工具添加一个Exception Handle模块,然后添加一个名为Exception Policy的策略,再为这个策略添加异常类型,默认我选择所有异常类型(All Exceptions),Post Handle Action为: NotifyRethow(对不理解Post Handle Action的处理方式的可以看下下面的解释)
PostHandlingAction 决定了在异常处理链完成后将发生什么活动。默认情况下,PostHandlingAction 被设置为 NotifyRethrow 。
None:应用程序块为此异常执行所有的处理程序,然后在 HandleException 方法的调用点上返回 false 给应用程序。应用程序检查此值以继续运行。
NotifyRethrow:应用程序块为此异常执行所有的处理程序,然后在 HandleException 方法的调用点上返回 true 给应用程序。应用程序检查到此值就重新抛出原始异常。
ThrowNewException:应用程序块为此异常执行所有的处理程序,然后在所有处理程序运行后抛出存在的异常。
2、为异常策略创建处理方式,我这边选择Loggin Exception Handler(在创建的同时配置工具会我们自动创建好Logging模块,并自动创建了一个日志分类:General,不过这个日志分类的默认Listener为event log,就是记录到系统的事件中),这时我们再创建一个CustomerTraceListener选择From File->自定义Listener所在DLL。
这边我碰到了一个问题就是添加了CustomerTraceListener,在对话框中我点击From File选择我编写的自定义Listener所在DLL,可惜没任何反应,不知道是不是要在DLL中做什么处理,所以我只能采用老办法:手写配置文件
首先看下Exception Handle模块的配置信息:
03 |
<add name= "ExceptionPolicy" > |
05 |
<add name= "All Exceptions" type= "System.Exception, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" |
06 |
postHandlingAction= "NotifyRethrow" > |
08 |
<add name= "Logging Exception Handler" type= "Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" |
09 |
logCategory= "General" eventId= "100" severity= "Error" title= "Enterprise Library Exception Handling" |
10 |
formatterType= "Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.TextExceptionFormatter, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling" |
接下来是日志模块配置,在日志模块下我配置了3个Listener,其中Custom Trace Listener为我自定义的异常日志记录,Event Log Listener(系统日志记录)和Rolling Flat File Trace Listener(文本文件记录,按天回滚记录)为在日志分类General无法正常记录日志时的记录下日志分类General为何无法记录,因为异常日志默认保存到数据库中,但是如果数据库中存在问题,或者链接被关闭这时就无法正常记录异常,所以:
01 |
<loggingConfiguration name= "" tracingEnabled= "true" defaultCategory= "General" > |
03 |
<add listenerDataType= "Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.CustomTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging" |
04 |
writeLogSQL= "insert into ExceptionLog(Message,LogDate,ExceptionLevel,Exception) values(@Message,@LogDate,@Level,@Exception)" |
05 |
type= "EntLibStudy.Helper.EntLibExtension.ExceptionExtension.ExceptionCustomerListener, EntLibStudy.Helper, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" |
06 |
traceOutputOptions= "None" name= "Custom Trace Listener" initializeData= "" |
07 |
formatter= "Text Formatter" /> |
08 |
<add name= "Event Log Listener" type= "Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" |
09 |
listenerDataType= "Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" |
10 |
source= "Enterprise Library Logging" formatter= "Text Formatter" |
11 |
log= "" machineName= "." traceOutputOptions= "None" /> |
12 |
<add name= "Rolling Flat File Trace Listener" type= "Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.RollingFlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" |
13 |
listenerDataType= "Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.RollingFlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" |
14 |
fileName= "rolling.log" formatter= "Text Formatter" rollInterval= "Day" /> |
17 |
<add type= "Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" |
18 |
template="Timestamp: {timestamp}{newline} |
19 |
Message: {message}{newline} |
20 |
Category: {category}{newline} |
21 |
Priority: {priority}{newline} |
22 |
EventId: {eventid}{newline} |
23 |
Severity: {severity}{newline} |
24 |
Title:{title}{newline} |
25 |
Machine: {localMachine}{newline} |
26 |
App Domain: {localAppDomain}{newline} |
27 |
ProcessId: {localProcessId}{newline} |
28 |
Process Name: {localProcessName}{newline} |
29 |
Thread Name: {threadName}{newline} |
30 |
Win32 ThreadId:{win32ThreadId}{newline} |
31 |
Extended Properties: {dictionary({key} - {value}{newline})}" |
32 |
name= "Text Formatter" /> |
35 |
<add switchValue= "All" name= "General" > |
37 |
<add name= "Custom Trace Listener" /> |
42 |
<allEvents switchValue= "All" name= "All Events" /> |
43 |
<notProcessed switchValue= "All" name= "Unprocessed Category" /> |
44 |
<errors switchValue= "All" name= "Logging Errors & Warnings" > |
46 |
<add name= "Event Log Listener" /> |
47 |
<add name= "Rolling Flat File Trace Listener" /> |
51 |
</loggingConfiguration> |
在配置完后我们就可以进行代码编写,在页面里进行异常控制。
在ASP.NET中,异常处理主要有4种,执行顺序为:Page_Error事件>ErrorPage属性>Application_Error事件> <customErrors>,我这边采用Page_Error,由于在本项目中我已经建立了BasePage,所有的页面都继承这个页面,所以我只需在这个页面中编写Page_Error事件:
01 |
protected void Page_Error( object sender, EventArgs e) |
04 |
var ex = Server.GetLastError(); |
06 |
HandleException(ex, "ExceptionPolicy" ); |
13 |
/// <param name="ex">异常信息</param> |
14 |
/// <param name="policy">异常处理策略</param> |
15 |
protected void HandleException(Exception ex, string policy) |
18 |
var exManager = EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>(); |
19 |
rethrow = exManager.HandleException(ex, policy); |
22 |
this .RedirectPermanent( "~/error.aspx" ); |
其中exManager.HandleException(ex, policy)为根据策略名处理异常,我这边使用的ExceptionPolicy,这个策略的处理方式为异常日志记录,它会帮我们调用到我们自定义的ExceptionCustomerListener 类,进行异常日志记录。
这样我们就完成了统一捕获系统中发生的异常了,本文也到此结束,欢迎大家指点!