异常信息处理是任何网站必不可少的一个环节,怎么有效显示,记录,传递异常信息又成为重中之重的问题。本篇将基于上篇介绍的,实现mvc自定义全局异常处理。先看一下最终实现效果:
阅读目录
我理解中好的异常处理
好的异常信息处理应该具有以下几个优点
- 显示效果佳,而不是原生黄页
- 能够从异常中直接分析出异常源
- 能够记录传递异常信息给开发人员
1.第一点显示效果方面可以自定义页面,常见的包括404和500状态码页面。在mvc中404页面可以通过以下两种方式进行自定义
//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" }); }}
自定义异常处理
这里采用mvc的过滤器进行异常处理,分别为接口500错误和页面500错误进行处理,接口部分异常需要记录请求参数,方便分析异常。
首先定义了异常信息实体,异常实体包含了 请求地址类型(页面,接口),服务器相关信息(位数,CPU,操作系统,iis版本),客户端信息(UserAgent,HttpMethod,IP)
异常实体代码如下
////// 系统错误信息 /// 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; } ////// 消息类型 /// public string MsgType { get; set; } ////// 消息内容 /// public string Message { get; set; } ////// 请求路径 /// public string Path { get; set; } ////// 程序集名称 /// public string Assembly { get; set; } ////// 异常参数 /// public string ActionArguments { get; set; } ////// 请求类型 /// public string HttpMethod { get; set; } ////// 异常堆栈 /// public string StackTrace { get; set; } ////// 异常源 /// public string Source { get; set; } ////// 服务器IP 端口 /// public string IP { get; set; } ////// 客户端浏览器标识 /// public string UserAgent { get; set; } ////// .NET解释引擎版本 /// public string DotNetVersion { get; set; } ////// 应用程序池位数 /// public string DotNetBit { get; set; } ////// 操作系统类型 /// public string OSVersion { get; set; } ////// 操作系统位数 /// public string OSBit { get; set; } ////// CPU个数 /// public string CPUCount { get; set; } ////// CPU类型 /// public string CPUType { get; set; } ////// IIS版本 /// public string IISVersion { get; set; } ////// 请求地址类型 /// public string Type { get; set; } ////// 是否显示异常界面 /// public bool ShowException { get; set; } ////// 异常发生时间 /// public string Time { get; set; } ////// 异常发生方法 /// 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; } ////// 是否有效IP地址 /// /// IP地址 ///bool private static bool IsEffectiveIP(string ipAddress) { return !(string.IsNullOrEmpty(ipAddress) || "unknown".Equals(ipAddress, StringComparison.OrdinalIgnoreCase)); } }
上面代码中用到了获取客户端请求IP的方法,用于获取请求来源的真实IP。
基础异常信息定义完后,剩下的是异常记录和页面跳转了,mvc中的异常过滤器实现如下。
////// 全局页面控制器异常记录 /// 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(msg) }; } } /// /// 全局API异常记录 /// 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) }; } } ////// 异常信息显示 /// public class MvcException { ////// 是否已经获取的允许显示异常 /// private static bool HasGetExceptionEnabled = false; private static bool isExceptionEnabled; ////// 是否显示异常信息 /// ///是否显示异常信息 public static bool IsExceptionEnabled() { if (!HasGetExceptionEnabled) { isExceptionEnabled = GetExceptionEnabled(); HasGetExceptionEnabled = true; } return isExceptionEnabled; } ////// 根据Web.config AppSettings节点下的ExceptionEnabled值来决定是否显示异常信息 /// ///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); } }});
总结
通过一点小小的改造,我们完成了一个既美观又方便拓展的错误处理方式。看到上面萌萌的图片你是否心动了,想马上下载代码体验一把呢。下面就给出本文所有的源代码:
git代码地址:
预告一下,下一篇将会对之前的T管理平台进行升级,主要实现管理界面方便查看当前运行的所有任务和管理任务。讲解管理平台运用到的技术,敬请期待!