20130910 一些想法,关于项目中异常处理的解决方案,以及Elmah

  项目中的Elmah

  我在现在这公司,3年来所作项目的整体架构一脉相承。Linq,DBML,jQuery,Log4net。还有一个经常会在web.config文件中看到的"Elmah",一直不知道是做什么的,只知道系统一发生未捕获的异常,它就会发送黄页的邮件给项目的负责人,所以为了平时local debug方便,我都在web.config中将一句话注释掉。

  这两天研究了一下,原来是.NET中为未捕获异常做错误日志的插件,使用很方便,至少在我所做的项目里是不自己写代码的,只配置。

  说实话最开始3年(到现在也差不多)做的项目都很小,对所谓“日志”一直就没有太重视。后来做了网优,才知道一个项目,日志所占的重要位置(这时候其实所谓的日志还是“错误日志”而已)。

  目前从conductor 项目 branch出来的项目,都是使用Log4net作为一些重要数据变更的日志,对于预期内的异常,在后台cs文件中捕获后,用项目自己写的一个简单的类,卸载根目录log文件夹下的一个txt文件中,每天一份。而Elmah,虽然也配有了MS SQL数据库进行异常记录,则主要是用来发邮件的。

  

    Elmah简介

  Elmah的文章比较好找,我主要是看了园子里博友搜集的两篇英文的:

  http://www.cnblogs.com/88223100/archive/2009/01/14/1375578.html 

  http://www.cnblogs.com/cqcmdwym/archive/2012/08/28/ELMAH.html

  另外在code.google.com上下了其源码看了两眼。

  只简略说两句:

  Elmah其实是5个单词的首字母拼写:Error Logging Modules And Handlers。这个插件——之所以叫插件就是因为是很轻量级的,添加和删除都很简单。在NuGet里online搜索Elmah有一大堆,除了core以外都是其分支功能。

  Elmah捕获从Web页面中未处理的Exception, 进行可选的记录日志,过滤错误,和发送邮件处理。记录日志原作者开发了XML文件形式,和MS SQL形式。在NuGet中添加Elmah的Core,只是具备了基本的功能,可以过滤和发送邮件。你还可以选择其他功能,XML文件是在web.config中指定的路径下写入xml文件,记录形式很怪异,我没深入研究,不推荐使用。MS SQL的包一旦添加,会附有数据库schema和存储过程的sql文件,说实话表这个结构我觉得用得也不太方便,不太推荐。你还能搜到网友开发的各种形式的包,MongoDB,SQLite等等。在项目中日志记录方式只能选择一种。

  Elmah还提供了一个功能,查看根目录下elmah.axd可以列出其所记录的所有异常信息,点进去有细节。这些异常信息是记录在内存中的。此axd通过在web.config中注册HttpHandler实现。

  在我们的项目中,由于有自己写的记录错误的方法,故此只利用Elmah的发送邮件功能。这样不用自己写代码,只要在web.config中配置好就可以了,十分便利。而这2年多的工作经验给我的感觉,在production上,针对非预期异常,通过邮件发送给负责和开发者,是十分有必要的。我们项目中也配置了MS SQL方式的日志记录,但很少有人查看而已。

  Elmah本质上是通过在web.config中注册HttpModule,对HttpApplication的异常事件进行监听,原理和Global中的Application_Error一样。所以你要注意一旦你的代码中没有捕获异常,Http应用程序级别,很多人习惯在Global的Error方法里捕捉,处理,记录异常,但你也无法阻止elmah的HttpModule记录这些错误了。我们的项目中就犯了这样的错误。

  Elmah还提供了error signal的方法,意在人工记录日志,来配合一些开发者捕捉了预期内异常,但也想将其记录下来的场景。

  详细内容请见上面连接中的文章。

  MVC中的异常处理

  实际上MVC也是ASP.NET WEB技术中的一种模式,也要遵循HTTP管道那一套原理。但单独提一下也是有原因的:一则是在我做的MHC MVC化的项目中,会格外注重客户体验方面的东西,对于各种异常信息都要有比较好的解决方案才行。二则是此项目中Ajax使用的地方会比较多,Ajax的异常处理机制又和普通刷新或跳转页面的异常处理机制有些区别了。三是由于MVC中提供了Exception Filter,另外还有Route的概念等等,原来WebForm项目中的异常处理的思路,有些可以摒弃掉,有些又不太适用了。

  在NuGet上你也可以搜到专门为MVC提供的Elmah,google code上有源码。我简略的看了看,原理是注销掉MVC系统自带的Global ExceptionFilter——HandleErrorFilter,而将自己写的一个Filter放在第一位使用。此Filter中负责使用Elmah提供的方法,上文提到的error signal来“抛出”异常。这个抛出只是告诉Elmah,有异常了,要做你该做的事了,但真正的Exception已经被自己吃掉了(方法开都就调用了base的onError方法,这个base正是原生的HandleErrorFilter)。

  这个思路非常简单,甚至不需要你使用这个插件,自己写出来的也差不多,你甚至还可以在自己的代码中再多加一些功能。但它并不能真正让我摆脱掉一直以来的苦恼。项目中异常的种类繁多,如果要照顾用户的体验,其处理方式也是五花八门不尽相同。

  在此我把我能想到的各种异常以及针对异常的解决方案罗列一下:

  Exception们

  如果从后台代码的角度看,异常可以分为可预期与非预期的。

  这是一种主观上的分类,可预期的说白了就是你自己代码写的异常,比如你的一个读取用户列表的方法,在Service层你catch了这个方法,将所有Exception都捕获,最后转化成一个LoadUserException抛出。可预期异常往往具有业务相关性。我们封装其的目的就是为了捕捉,区别对待,或记录,或发信,或干脆以文字形式反馈给用户。

  非预期的异常往往就是那些总给你superise的,让你始料未及的。这些异常往往是你所使用的框架抛出来的,具有业务逻辑无关性。

   可预期与非预期只是一种”相对的“分类方法,有些异常,你考虑到了就是预期的,没考虑到就是非预期的,完全取决于开发者的素养。另外有些时候为了省事,一些功能中,非预期的异常也会直接反应在UI上。

  Http异常在不同项目中,重视程度不同。在MVC中由于URL尤其意义,用户手敲代码的几率大大增加了,所以我认为404是要在MVC中着重考虑的。

  Ajax又带来了以上三种错误的”翻版“,并且有些错误有必要细化处理,例如考虑Ajax的非预期错误,以下场景:点击按钮异步获取用户列表,可能Timeout要使用一种解决方案,而如果”未将对象引用到实例了“,则又要采用另一种方案了。

  解决方案们

  让我们来看看我们有什么手段来对抗这些异常:

  错误日志记录,无论是写txt,写xml,写数据库,主要目的是好查阅,追踪其中的细节。

  发送邮件,主要目的是及时通知。

  显示给当前用户UI,要保证足够的友好,目的在于亲切的告诉用户:亲,别点了,点了也白点哦~...

  直接跳转到错误页,不用现实给用户明确的错误信息,只要提供恢复机制(例如跳转到主页,上一页)就好了

  思考,具体解决方案

  普通非Ajax功能:

  首先对于非预期的异常,我建议直接用一个Global的ExeptionFilter解决最好。重写那个HandleErrorFilter应该就能解决,记录日志,发信,全由你,最后记得将ActionResult改写为一个专门现实异常的View,提供用户后退的链接就OK了。由于是最后再Exception过滤器中改写的View,本质上还是用户的那次请求,所以点后退可以跳转到用户上一个安全的页面。这种页面要专门排除掉Ajax请求和Child Action。

  有了全局的异常处理机制,你还可以定一些针对特定Exception类型的异常处理机制,这就无须赘述了。

  如果你想讲预期的异常现实在当前页面上,还是建议你在Controller里try catch吧,异常过滤器帮不了你的,因为它被调用的时候往往Action还都没找到呢就中断了,何谈最后的View呢?现在我一般在Controller中捕获的异常会添加到ModelState的Error Summary中,不知道这样是否合理,将就用,有机会自己扩展一套类似这种ModelState的Exception State接口。

  404:

  Area,Controller,Action找不到的情况,系统都会封装这些异常为HttpException,并且是404。Controller找不到,就谈不到ExceptionFilter(甚至是Global的),过滤器的方案也就不行了。一种解决方案是我现在采用的,在Application_Error中捕获处理,将页面Redirect到一个NotFound的Action。这种方案未经过大量测试,可能对于一些特殊情况会不适应,因为毕竟你跳转页面了,比不上前面提到的直接修改ActionResult那种方式安全,另一方面Redirect的参数直接写URL路径的方式比较Hard。

  最好的方式是直接能从ControllerFactory那里解决。GitHub上,搜索MVC可以找到一个专门针对404的小项目,我没有太细看,大概原理就是Wap了MVC原生的ControllerFacotry等一些列东西,在发生找不到的情况下,导向专门为404准备的Controller和Action。

  所以说目前我的项目针对404没有特别完美的方案。下一步要仔细研究一下。

  

  Ajax种种:

  我比较推荐针对Ajax请求的action不和非Ajax的action混用,一下思考的问题都以此作为基础。

  采用异步的方式获取信息,虽然给用户的感觉很好,但因为一旦发生非预期错误,不能采用跳转到错误页这种简单的形式,而如果是预期错误,又要通过HTTP相应返回给请求者,所以整个处理起来处处是麻烦。

  直接Load一个Child Action,这种异步读取页面的方式,好写,MVC框架或是jQuery支持得又好,所以用得比较频繁。但就是这种方式的错误处理最不好办,ajax的可能还好,如果我自己写框架,还可以判断一下反馈回来的数据,我可以设定契约:如果返回的不是一个HTML字符串而是带有特殊标记的字符串,则认为其返回的是错误信息,这样你就可以读取你缓存的那部分页面(请求之前通过各种手段缓存),至于错误信息显示不显示就由你了。 但如果是不采用Ajax的子Action发生错误...目前我还没有去具体分析,也没有解决方案。

  通过点击链接,增删改数据此类的Ajax功能最简单,这类功能本身除了成功或失败以外,不期待其他数据的反馈。这种情况直接返回Json就可以了,异常了返回错误信息{‘error’:'xxxxxxxx'},没错误就返回一个空对象{},甚至可以返回一个带有成功信息的对象{'success':'xxxxxx'}. 更简单一点的,如果不期待成功后的信息,那返回ContentResult就可以了,空字符串为成功,有值则是异常的信息。

  同理,获取json的Ajax请求也可以采用这种方式,只是要注意error还是success,不要和当前功能所期待的json对象里的属性重名。

  

  针对Ajax的以上方案,可以采用action中返回各种ActionResult的方式(JsonResult, JavascriptResult,ContentResult),也可以和上面的非Ajax处理一样,定义另外一个针对Ajax请求的全局的Exception Filter,返回Json信息,这样先把大的基调定下来,一些例外的情况可以再定义其他Filter。注意Ajax里没有跳转页面,所以也针对预期和非预期,都只有返回错误信息这同一种处理方式。 全局Filter里将一些非预期的异常封装成类似”Ajax Error“这样的信息,而把预期异常(比如”Get User Info Failed)“如实的返回,是不错的选择。针对404,其实Ajax如果不是用户恶意而为,是不会发生的,为了预防,还是在前文的Application_Error中特殊对待一下,直接Response.write点什么(记得要先把错误都清除了)比较好。

  至此为止暂告一段落。其实上面的东西有的我已经实现,有的还只是构想,因为很多错误凭想象是想象不到的,我需要大量的MVC项目经验。还有一些为解决的问题,比如跳转页面会不会返回到了一个错误的位置,又跳了回来,这种情况怎么办?针对Ajax处理的方式是否微软MVC Ajax框架目前很难支持得好,还要自己用jQuery去写?这都要等我一点点试。希望过不了多久就把我一套比较成熟的解决方案摆上来。

  

  明天的任务,Log4net,写Ordr的ajax页面,在项目中添加三个针对普通,ajax和child的Global Exception Filter,另外Application_Error添加AJAX处理,看github上 404 MVC源码,有空读读EF的文章。

  

原文地址:https://www.cnblogs.com/apodemakeles/p/3310695.html