zoukankan      html  css  js  c++  java
  • 项目架构开发:异常处理及日志

    上一篇我们完善了多层开发的效率问题,传送门:项目架构开发:展现层(下)

    这次我们完成架构的异常处理功能,异常处理一般都与日志分不开的,因为分析及定位问题需要一些详细信息;

    稍微正规一点的公司,都会分开发、测试及生产环境。在本地及测试环境出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; 500px; margin:20px;">
    6     <h2>NotFound</h2>
    7     一般人看不出来,这是一个美化了的NotFound页面。
    8 </div>

    2.6 Error.cshtml改得更友好一些

    1 <div style=" margin:0px auto; 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         }

    测试未通过

    看看数据库有没有记录到异常

    看到没?有了这些日志信息,什么问题都无法遁形。。

  • 相关阅读:
    Eclipse快捷键大全(转载)
    IE9浏览Flash页面时显示错位并不停地闪烁
    flash全屏事件和键盘按下事件部分不能触发问题
    AS3摘要(转载)
    【as3手册小记】ActionScript 中处理全屏模式的注意事项
    巧用FlashPaper 让Word文档变Flash
    AS3视频照相截图(转载)
    Json串到json对象的转换
    映射文件详解(转)
    Jquery .ajax方法分析(一)
  • 原文地址:https://www.cnblogs.com/lanxiaoke/p/6574210.html
Copyright © 2011-2022 走看看