mvc自定義全局異常處理

  異常信息處理是任何網站必不可少的一個環節,怎麼有效顯示,記錄,傳遞異常信息又成爲重中之重的問題。本篇將基於上篇介紹的html2cancas截圖功能,實現mvc自定義全局異常處理。先看一下最終實現效果:http://yanweidie.myscloud.cn/Home/Indexhtml

閱讀目錄前端

我理解中好的異常處理

    好的異常信息處理應該具備如下幾個優勢jquery

  • 顯示效果佳,而不是原生黃頁
  • 可以從異常中直接分析出異常源
  • 可以記錄傳遞異常信息給開發人員

     1.第一點顯示效果方面能夠自定義頁面,常見的包括404和500狀態碼頁面。在mvc中404頁面能夠經過如下兩種方式進行自定義git

  
<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)github

     異常實體代碼以下web

    /// <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。ajax

基礎異常信息定義完後,剩下的是異常記錄和頁面跳轉了,mvc中的異常過濾器實現以下。json

 /// <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組件記錄在錯誤日誌中,方便留痕。api

過濾器定義完成後,須要在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全局的頁面,接口異常信息處理。經過結合上面的前端截圖插件,快速截圖留證,方便後續程序員分析異常信息。

總結

  經過一點小小的改造,咱們完成了一個既美觀又方便拓展的錯誤處理方式。看到上面萌萌的圖片你是否心動了,想立刻下載代碼體驗一把呢。下面就給出本文全部的源代碼:

     git代碼地址:https://github.com/CrazyJson/CustomGlobalError

      預告一下,下一篇將會對以前的TaskManager管理平臺進行升級,主要實現管理界面方便查看當前運行的全部任務和管理任務。講解管理平臺運用到的技術,敬請期待!

 

 

相關文章
相關標籤/搜索