異常處理及日誌

異常處理一般都與日誌分不開的,因爲分析及定位問題需要一些詳細信息;

稍微正規一點的公司,都會分開發、測試及生產環境。在本地及測試環境出BUG了,問題很好解決

調試跟蹤問題,三下五除二就搞完了;但是在生產環境出問題,基本上是不允許直連數據庫調試的;

這時候如何沒有足夠的異常信息參考,那你就悲催了,你等着加班熬夜吧。

爲了解決這個問題,所以異常信息的捕捉及記錄就顯得非常重要了,一個完善的系統,出問題後不可能要去調試才能知道具體原因的

 

1、解決展現層的異常

1.1 其實ASP.NET MVC已經支持全局異常的處理,就是這個:HandleErrorAttribute,這裏我們只是簡單介紹他的使用方法

詳情可以看看這篇文章:http://shiyousan.com/post/635838881238204198,下面我們一步步來。

FilterConfig.cs,這是系統默認生成的

複製代碼
 1 using System.Web;
 2 using System.Web.Mvc;
 3 
 4 namespace Presentation.MVC
 5 {
 6     public class FilterConfig
 7     {
 8         public static void RegisterGlobalFilters(GlobalFilterCollection filters)
 9         {
10             filters.Add(new HandleErrorAttribute());
11         }
12     }
13 }
複製代碼

1.2 要在Web.config中開啓customErrors,不然沒有效果

1 <customErrors mode="On" defaultRedirect="~/Error/Index">
2  </customErrors>

1.3 設置好後,系統發生異常後會自動跳轉到默認的Error.cshtml界面

複製代碼
 1 <div class="container">
 2     <h1 class="text-danger">錯誤。</h1>
 3     <h2 class="text-danger">處理你的請求時出錯。</h2>
 4 
 5     @if (Model != null)
 6     {
 7         <p class="bg-danger text-danger">
 8             異常類型:@Model.Exception.GetType().Name
 9         </p>
10         <p class="bg-danger text-danger">
11             觸發異常的控制器:@Model.ControllerName
12         </p>
13         <p class="bg-danger text-danger">
14             觸發異常的操作方法:@Model.ActionName
15         </p>
16         <p class="bg-danger text-danger">
17             錯誤信息:@Model.Exception.Message
18         </p>
19         <p class="bg-info text-info">
20             頁面路徑:~/Views/Shared/Error.cshtml
21         </p>
22     }
23 </div>
複製代碼

1.4 我們再Home/Index初始頁觸發一個異常試試看

複製代碼
1         public ActionResult Login()
2         {
3             string str = null;
4             str.GetType();//空引用
5 
6             return View();
7         }
複製代碼

 

可以看到已經跳轉到默認錯誤顯示頁面了

但是這樣是不夠的,一般這個頁面會美化,客戶端用戶會看到更加友好的提示信息

而且這裏並沒有異常堆棧,看不到異常的具體信息,這樣定位問題就困難;

所以還需要加工一下,我們設計一個自定義的異常處理類

 

2、自定義異常處理

2.1 LjrExecptionAttribute.cs,很簡單啊,就不解釋了

複製代碼
 1 using Infrastructure.Core;
 2 using System.Web.Mvc;
 3 
 4 namespace Presentation.MVC
 5 {  
 6     public class LjrExecptionAttribute : HandleErrorAttribute  
 7     {  
 8         public override void OnException(ExceptionContext filterContext)  
 9         {
10             Logger.Error(filterContext.Exception.Message, filterContext.Exception);
11 
12             base.OnException(filterContext);  
13         }  
14     }  
15 }  
複製代碼

2.2 然後FilterConfig.cs 要改一下

複製代碼
1     public class FilterConfig
2     {
3         public static void RegisterGlobalFilters(GlobalFilterCollection filters)
4         {
5             //filters.Add(new HandleErrorAttribute());
6             filters.Add(new LjrExecptionAttribute());
7         }
8     }
複製代碼

2.3 web.config也改一下,因爲HandleErrorAttribute 處理不了HTTP404

1 <customErrors mode="On" defaultRedirect="~/Error/Index">
2       <error redirect="~/Error/NotFound" statusCode="404" />
3 </customErrors>

2.4 新建ErrorController.cs

複製代碼
 1     public class ErrorController : Controller
 2     {
 3         public ActionResult Index()
 4         {
 5             return View();
 6         }
 7 
 8         public ActionResult NotFound()
 9         {
10             return View();
11         }
12     }
複製代碼

2.5 NotFound.cshtml

複製代碼
1 @{
2     Layout = null;
3 }
4 
5 <div style=" margin:0px auto; width:500px; margin:20px;">
6     <h2>NotFound</h2>
7     一般人看不出來,這是一個美化了的NotFound頁面。
8 </div>
複製代碼

2.6 Error.cshtml改得更友好一些

1 <div style=" margin:0px auto; width:500px; margin:20px;">
2     <h2>默認異常頁面</h2>
3     你好,這是系統默認異常界面,已經美化過了,請放心使用。
4 </div>

2.7 在Home控制器中手動觸發異常

複製代碼
 1         public ActionResult ThrowHttp500()
 2         {
 3             throw new HttpException(500, "服務器錯誤");
 4         }
 5 
 6         public ActionResult ThrowHttp404()
 7         {
 8             throw new HttpException(404, "頁面未找到");
 9         }
10 
11         [HandleError(ExceptionType = typeof(NullReferenceException))]
12         public ActionResult ThrowNullReferenceException()
13         {
14             throw new NullReferenceException();
15         }
16 
17         public ActionResult ThrowFormatException()
18         {
19             string str = "";
20             int count = Convert.ToInt32(str);
21             return View("Index");
22         }
複製代碼

2.8 運行以下幾種異常會跳到之前的默認異常頁面

http://localhost:5572/Home/ThrowHttp500
http://localhost:5572/Home/ThrowNullReferenceException
http://localhost:5572/Home/ThrowFormatException

2.9 頁面未找到會轉至:http://localhost:5572/Home/ThrowHttp404

 2.10 當然了,別忘了在2.2 中我們還在中記錄了日誌功能(LjrExecptionAttribute),我們去看看

 

堆棧信息都有了,這就很好定位BUG位置了,通過分析其日誌,大概可以知道問題原因

上邊已經解決了WEB中的異常信息,但是一個項目不可能只有WEB,還有很多類庫,WebService等

3、類庫異常處理

3.1 看看捕捉異常的一般做法

複製代碼
 1         public bool CommonMethod(LoginUserCURequest entity)
 2         {
 3             try
 4             {
 5                 this.repository.Add(new LoginUser()
 6                 {
 7                     Id = entity.Id,
 8                     LoginName = entity.LoginName,
 9                     Password = entity.Password,
10                     IsEnabled = entity.IsEnabled,
11                     CreateTime = DateTime.Now
12                 });
13 
14                 foreach (var Id in entity.Roles)
15                 {
16                     this.roleUserMappingRepository.Add(new RoleUserMapping()
17                     {
18                         Id = Guid.NewGuid(),
19                         RoleId = Id,
20                         LoginUserId = entity.Id,
21                         CreateTime = DateTime.Now
22                     });
23                 }
24 
25                 this.unitOfWork.Commit();
26 
27                 return true;
28             }
29             catch (Exception ex)
30             {
31                 Logger.Error(ex.Message, ex);
32                 return false;
33             }
34         }
複製代碼

 

大部分人應該都是像上邊一樣處理異常,這本身沒有問題

但是仔細想想,還是有點問題的,我們來看看

1、每個方法都要寫try{}catch{},到最後整個類庫都是try catch,這很醜。。,顏值太低了,就提不起多少興趣了。

2、處理異常這本身就不應該屬於業務邏輯的一部分,得把他弄走,因爲他污染了業務邏輯

3、只記錄了異常堆棧,輸入參數沒有,如果要記錄參數值,還得寫一堆日誌代碼,那就更醜了。

 

3.2 有困難,攔截器來幫忙;對於這種現象,於是一些聰明的開發者就搞出了AOP編程

AOP是基於特性(Attribute)的,不過自己搞的話貌似還挺複雜的,簡單的還行,複雜的我也不會

於是我就盯上了PostSharp,版本1.5以上是收費的,這點要注意。我們先來搞一個簡單的異常攔截器

複製代碼
 1     [Serializable]
 2     [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
 3     public class ExceptionAttribute : PostSharp.Aspects.OnExceptionAspect
 4     {
 5         public override void OnException(MethodExecutionArgs args)
 6         {
 7             StringBuilder sb = new StringBuilder();
 8             sb.AppendLine(args.Exception.Message);
 9             sb.AppendFormat("位置:{0}.{1}", args.Method.ReflectedType, args.Method).AppendLine();
10 
11             sb.AppendLine("參數:");
12             foreach(var item in args.Arguments)
13             {
14                 sb.AppendFormat(item.ToString()).AppendLine();
15             }
16 
17             Logger.Error(sb.ToString(), args.Exception);
18 
19             args.FlowBehavior = FlowBehavior.ThrowException;
20         }
21 }
複製代碼

標紅的得注意了,必須要寫清楚的,不然沒有效果

還有一個小坑就是PostSharp不支持實體參數的,傳普通類型的string、int之類的在OnException中是可以拿到值的

如果是實體,比如這種

複製代碼
 1     public class LoginUserCURequest
 2     {
 3         /// <summary>Id</summary>    
 4         public Guid Id { get; set; }
 5 
 6         /// <summary>登錄賬戶名</summary>    
 7         public string LoginName { get; set; }
 8 
 9         /// <summary>登錄密碼</summary>    
10         public string Password { get; set; }
11 
12         /// <summary>是否有效</summary>    
13         public short? IsEnabled { get; set; }
14 
15         /// <summary>所屬角色</summary>    
16         public IEnumerable<Guid> Roles { get; set; }
17     }
複製代碼

這種是取不到值的(橙色部分),因爲args.Arguments返回的是object數組,取不到實體屬性

這是很不好的一點,不過可以克服的,我的方法就是重寫ToString(),如下

複製代碼
 1     public class LoginUserCURequest
 2     {
 3         /// <summary>Id</summary>    
 4         public Guid Id { get; set; }
 5 
 6         /// <summary>登錄賬戶名</summary>    
 7         public string LoginName { get; set; }
 8 
 9         /// <summary>登錄密碼</summary>    
10         public string Password { get; set; }
11 
12         /// <summary>是否有效</summary>    
13         public short? IsEnabled { get; set; }
14 
15         /// <summary>所屬角色</summary>    
16         public IEnumerable<Guid> Roles { get; set; }
17 
18         public override string ToString()
19         {
20             return string.Format("Id:{0},LoginName:{1},Password:{2},IsEnabled:{3}", 
21                 Id.ToString(), LoginName, Password, IsEnabled.ToString());
22         }
23     }
複製代碼

這樣就沒問題了,還能按照自己的格式輸出;

 

3.3  使用異常攔截器

攔截器我們已經搞好了,直接寫在方法或類名上邊就可以,如下圖

然後我們再Add方法裏邊觸發一個異常

我們測試一下,還是之前的LoginUserApplicationTest.cs,這裏再貼一次

複製代碼
 1         [TestMethod]
 2         public void Add()
 3         {
 4             var list = new List<Guid>();
 5             list.Add(Guid.NewGuid());
 6             list.Add(Guid.NewGuid());
 7 
 8             var flag = this.loginUserApplication.Add(new LoginUserCURequest()
 9             {
10                 Id = Guid.NewGuid(),
11                 LoginName = "lanxiaoke-" + Guid.NewGuid().ToString(),
12                 Password = "123456",
13                 IsEnabled = 1,
14                 Roles = list
15             });
16 
17             Assert.AreEqual(true, flag);
18         }
複製代碼

測試未通過

看看數據庫有沒有記錄到異常

 

看到沒?有了這些日誌信息,什麼問題都無法遁形。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章