异常信息处理是任何网站必不可少的一个环节,怎么有效显示,记录,传递异常信息又成为重中之重的问题。本篇将基于上篇介绍的html2cancas截图功能,实现mvc自定义全局异常处理。先看一下最终实现效果:http://yanweidie.myscloud.cn/Home/Index
阅读目录
- 我理解中好的异常处理
- 自定义异常处理
- 问题拓展
- 总结
回到顶部
我理解中好的异常处理
好的异常信息处理应该具有以下几个优点
- 显示效果佳,而不是原生黄页
- 能够从异常中直接分析出异常源
- 能够记录传递异常信息给开发人员
1.第一点显示效果方面可以自定义页面,常见的包括404和500状态码页面。在mvc中404页面可以通过以下两种方式进行自定义
<system.web> <!--添加customErrors节点 定义404跳转页面--> <customErrors mode="On"> <error statusCode="404" redirect="/Error/Path404" /> </customErrors> </system.web>
//Global文件的EndRequest监听Response状态码 protected void Application_EndRequest() { var statusCode = Context.Response.StatusCode; var routingData = Context.Request.RequestContext.RouteData; if (statusCode == 404 || statusCode == 500) { Response.Clear(); Response.RedirectToRoute("Default", new { controller = "Error", action = "Path404" }); } }
2.第二点 异常信息应该详细,能够记录下请求参数,请求地址,浏览器版本服务器和当前用户等相关信息,这就需要对异常信息记录改造加工
3.第三点 常见的异常信息都是记录在日志文件里面,日志文件过大时也不太好分析。发生异常时要是能马上将异常信息通过邮件或者图片等方式发给开发者,可以加快分析速度。
回到顶部
自定义异常处理
这里采用mvc的过滤器进行异常处理,分别为接口500错误和页面500错误进行处理,接口部分异常需要记录请求参数,方便分析异常。
首先定义了异常信息实体,异常实体包含了 请求地址类型(页面,接口),服务器相关信息(位数,CPU,操作系统,iis版本),客户端信息(UserAgent,HttpMethod,IP)
异常实体代码如下
/// <summary> /// 系统错误信息 /// </summary> public class ErrorMessage { public ErrorMessage() { } public ErrorMessage(Exception ex,string type) { MsgType = ex.GetType().Name; Message = ex.InnerException != null ? ex.InnerException.Message : ex.Message; StackTrace = ex.StackTrace; Source = ex.Source; Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); Assembly = ex.TargetSite.Module.Assembly.FullName; Method = ex.TargetSite.Name; Type = type; DotNetVersion = Environment.Version.Major + "." + Environment.Version.Minor + "." + Environment.Version.Build + "." + Environment.Version.Revision; DotNetBit = (Environment.Is64BitProcess ? "64" : "32") + "位"; OSVersion = Environment.OSVersion.ToString(); CPUCount = Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS"); CPUType = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER"); OSBit = (Environment.Is64BitOperatingSystem ? "64" : "32") + "位"; var request = HttpContext.Current.Request; IP = GetIpAddr(request) + ":" + request.Url.Port; IISVersion = request.ServerVariables["SERVER_SOFTWARE"]; UserAgent = request.UserAgent; Path = request.Path; HttpMethod = request.HttpMethod; } /// <summary> /// 消息类型 /// </summary> public string MsgType { get; set; } /// <summary> /// 消息内容 /// </summary> public string Message { get; set; } /// <summary> /// 请求路径 /// </summary> public string Path { get; set; } /// <summary> /// 程序集名称 /// </summary> public string Assembly { get; set; } /// <summary> /// 异常参数 /// </summary> public string ActionArguments { get; set; } /// <summary> /// 请求类型 /// </summary> public string HttpMethod { get; set; } /// <summary> /// 异常堆栈 /// </summary> public string StackTrace { get; set; } /// <summary> /// 异常源 /// </summary> public string Source { get; set; } /// <summary> /// 服务器IP 端口 /// </summary> public string IP { get; set; } /// <summary> /// 客户端浏览器标识 /// </summary> public string UserAgent { get; set; } /// <summary> /// .NET解释引擎版本 /// </summary> public string DotNetVersion { get; set; } /// <summary> /// 应用程序池位数 /// </summary> public string DotNetBit { get; set; } /// <summary> /// 操作系统类型 /// </summary> public string OSVersion { get; set; } /// <summary> /// 操作系统位数 /// </summary> public string OSBit { get; set; } /// <summary> /// CPU个数 /// </summary> public string CPUCount { get; set; } /// <summary> /// CPU类型 /// </summary> public string CPUType { get; set; } /// <summary> /// IIS版本 /// </summary> public string IISVersion { get; set; } /// <summary> /// 请求地址类型 /// </summary> public string Type { get; set; } /// <summary> /// 是否显示异常界面 /// </summary> public bool ShowException { get; set; } /// <summary> /// 异常发生时间 /// </summary> public string Time { get; set; } /// <summary> /// 异常发生方法 /// </summary> public string Method { get; set; } //这段代码用户请求真实IP private static string GetIpAddr(HttpRequest request) { //HTTP_X_FORWARDED_FOR string ipAddress = request.ServerVariables["x-forwarded-for"]; if (!IsEffectiveIP(ipAddress)) { ipAddress = request.ServerVariables["Proxy-Client-IP"]; } if (!IsEffectiveIP(ipAddress)) { ipAddress = request.ServerVariables["WL-Proxy-Client-IP"]; } if (!IsEffectiveIP(ipAddress)) { ipAddress = request.ServerVariables["Remote_Addr"]; if (ipAddress.Equals("127.0.0.1") || ipAddress.Equals("::1")) { // 根据网卡取本机配置的IP IPAddress[] AddressList = Dns.GetHostEntry(Dns.GetHostName()).AddressList; foreach (IPAddress _IPAddress in AddressList) { if (_IPAddress.AddressFamily.ToString() == "InterNetwork") { ipAddress = _IPAddress.ToString(); break; } } } } // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照‘,‘分割 if (ipAddress != null && ipAddress.Length > 15) { if (ipAddress.IndexOf(",") > 0) { ipAddress = ipAddress.Substring(0, ipAddress.IndexOf(",")); } } return ipAddress; } /// <summary> /// 是否有效IP地址 /// </summary> /// <param name="ipAddress">IP地址</param> /// <returns>bool</returns> private static bool IsEffectiveIP(string ipAddress) { return !(string.IsNullOrEmpty(ipAddress) || "unknown".Equals(ipAddress, StringComparison.OrdinalIgnoreCase)); } }
上面代码中用到了获取客户端请求IP的方法,用于获取请求来源的真实IP。
基础异常信息定义完后,剩下的是异常记录和页面跳转了,mvc中的异常过滤器实现如下。
/// <summary> /// 全局页面控制器异常记录 /// </summary> public class CustomErrorAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { base.OnException(filterContext); ErrorMessage msg = new ErrorMessage(filterContext.Exception, "页面"); msg.ShowException = MvcException.IsExceptionEnabled(); //错误记录 LogHelper.WriteLog(JsonConvert.SerializeObject(msg, Formatting.Indented), null); //设置为true阻止golbal里面的错误执行 filterContext.ExceptionHandled = true; filterContext.Result = new ViewResult() { ViewName = "/Views/Error/ISE.cshtml", ViewData = new ViewDataDictionary<ErrorMessage>(msg) }; } } /// <summary> /// 全局API异常记录 /// </summary> public class ApiHandleErrorAttribute : ExceptionFilterAttribute { public override void OnException(HttpActionExecutedContext filterContext) { base.OnException(filterContext); //异常信息 ErrorMessage msg = new ErrorMessage(filterContext.Exception, "接口"); //接口调用参数 msg.ActionArguments = JsonConvert.SerializeObject(filterContext.ActionContext.ActionArguments, Formatting.Indented); msg.ShowException = MvcException.IsExceptionEnabled(); //错误记录 string exMsg = JsonConvert.SerializeObject(msg, Formatting.Indented); LogHelper.WriteLog(exMsg, null); filterContext.Response = new HttpResponseMessage() { StatusCode = HttpStatusCode.InternalServerError, Content = new StringContent(exMsg) }; } } /// <summary> /// 异常信息显示 /// </summary> public class MvcException { /// <summary> /// 是否已经获取的允许显示异常 /// </summary> private static bool HasGetExceptionEnabled = false; private static bool isExceptionEnabled; /// <summary> /// 是否显示异常信息 /// </summary> /// <returns>是否显示异常信息</returns> public static bool IsExceptionEnabled() { if (!HasGetExceptionEnabled) { isExceptionEnabled = GetExceptionEnabled(); HasGetExceptionEnabled = true; } return isExceptionEnabled; } /// <summary> /// 根据Web.config AppSettings节点下的ExceptionEnabled值来决定是否显示异常信息 /// </summary> /// <returns></returns> private static bool GetExceptionEnabled() { bool result; if(!Boolean.TryParse(ConfigurationManager.AppSettings["ExceptionEnabled"],out result)) { return false; } return result; } }
值得注意的是上面的MvcException类的GetExceptionEnabled方法,该方法从web.config appsetting中读取节点"ExceptionEnabled"来控制异常信息是否初始化显示。异常信息除了显示在页面,还使用了log4net组件记录在错误日志中,方便留痕。
过滤器定义完成后,需要在filterconfig添加引用
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new CustomErrorAttribute()); filters.Add(new HandleErrorAttribute()); } }
回到顶部
问题拓展
后台异常处理代码完成以后,前台还需进行相应的处理。这里主要针对api接口,因为请求页面后台可以直接转向500错误页面,而api接口一般是通过ajax或者客户端httpclient请求的,如果错误了跳转到500页面,这样对客户端来说就不友好了。基于这点所以api请求异常返回了异常的详细json对象,让客户端自己进行异常处理。我这里给出ajax处理异常的方式。
在jquery中全局ajax请求可以设置相应默认参数,比如下面代码设置了全局ajax请求为异步请求,不缓存
//ajax请求全局设置 $.ajaxSetup({ //异步请求 async: true, //缓存设置 cache: false });
ajax请求完成会触发Complete事件,在jquery中全局Complete事件可以通过下面代码监听
$(document).ajaxComplete(function (evt, request, settings) { var text = request.responseText; if (text) { try { //Unauthorized 登录超时或者无权限 if (request.status == "401") { var json = $.parseJSON(text); if (json.Message == "logout") { //登录超时,弹出系统登录框 } else { layer.alert(json.ExceptionMessage ? json.ExceptionMessage : "系统异常,请联系系统管理员", { title: "错误提醒", icon: 2 }); } } else if (request.status == "500") { var json = $.parseJSON(text); $.ajax({ type: "post", url: "/Error/Path500", data: { "": json }, data: json, dataType: "html", success: function (data) { //页面层 layer.open({ title: ‘异常信息‘, type: 1, shade: 0.8, shift: -1, area: [‘100%‘, ‘100%‘], content: data, }); } }); } } catch (e) { console.log(e); } } });
红色部分代码就是我用来处理500错误的代码,重新发请求到异常显示界面渲染成html后显示。其实这么做无疑增加了一次请求,最好的实现方式,直接通过异常信息json,通过js绘制出html。至此完成了mvc全局的页面,接口异常信息处理。通过结合上面的前端截图插件,快速截图留证,方便后续程序员分析异常信息。
回到顶部
总结
通过一点小小的改造,我们完成了一个既美观又方便拓展的错误处理方式。看到上面萌萌的图片你是否心动了,想马上下载代码体验一把呢。下面就给出本文所有的源代码:
svn代码浏览:http://code.taobao.org/p/MyCustomGlobalError/src/trunk svn工具代码checkout地址:http://code.taobao.org/svn/MyCustomGlobalErro
预告一下,下一篇将会对之前的TaskManager管理平台进行升级,主要实现管理界面方便查看当前运行的所有任务和管理任务。讲解管理平台运用到的技术,敬请期待!
如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】按钮。
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【关注我】。
因为,我的写作热情也离不开您的肯定支持。
感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是焰尾迭 。