十年河东,十年河西,莫欺少年穷

学无止境,精益求精

背景

  作为开发者,你兴高采烈地完成了新系统的功能开发。并且顺利经过验收,系统如期上线,皆大欢喜。

  但是,有些bug就是在生产环境如期而至了。半夜梦酣之时,你被运维童鞋的电话惊醒了,系统不能正常运行了。接下来,他打包了一堆日志文件给你...

  干了多年开发越来越觉得,异常处理和定位的能力反映出开发者硬核能力。如果开发人员能够在对系统中异常进行捕获,然后记录日志,并对日志进行划分等级,然后通过邮件或者短信等提醒,是不是能够做到提前预判呢。

在 asp.net core中全局异常处理,这里介绍两种不同的处理方式:过滤器捕获和中间件过滤。

过滤器

ASP.NET Core 有以下五种Filter 可以使用:

  • Authorization Filter:
    Authorization是五种Filter中优先级最高的,通常用于验证Request合不合法,不合法后面就直接跳过。
  • Resource Filter:Resource是第二优先,会在Authorization之后,Model Binding之前执行。通常会是需要对Model加工处理才用。
  • Exception Filter:异常处理的Filter。
  • Action Filter:最常使用的Filter,封包进出都会经过它,使用上没什么需要特别注意的。跟Resource Filter很类似,但并不会经过Model Binding。
  • Result Filter:当Action完成后,最终会经过的Filter。

 今天探讨异常过滤器、异常处理中间件、及NetCore结合 Log4Net 进行日志记录

使用ExceptionFilter

  前面提到,过滤器可以处理错误异常。这里可以实践一把。

  新建一个.NET Core MVC控制器(.net WebAPI也类似)。
  我在Test/Index Action方法中故意制造一个异常(我们知道在被除数不能为0).

        public IActionResult Index()
        {
           
            try
            {
                int a = 0, b = 5;
                var result = b / a;
            }
            catch (Exception)
            {
                throw new ArgumentException("被除数不能为0", "路径:Manger/index");
            }
            return View();
        }

我们运行这个页面,如下:

NetCore 异常处理过滤器、中间件 、并整合Log4Net

 

 

但是每个方法都这样加会不会觉得很烦?有没有想过一劳永逸的办法。从架构层面应该这样思考。

  在传统的 Asp.Net MVC 应用程序中,我们一般都使用服务过滤的方式去捕获和处理异常,这种方式 非常常见,而且可用性来说,体验也不错,幸运的是 Asp.Net Core也完整的支持该方式。 新建一个全局异常过滤器GlobalExceptionFilter.cs,继承自IExceptionFilter。

代码如下(这里面结合了Log4Net ,用于一旦发生异常,则记录相关日志):

using log4net;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NetCoreXXMS.NetCoreFilter
{
    public class GlobalExceptionFilter : Attribute, IExceptionFilter
    {
        private ILog log;
        private readonly IHostingEnvironment _hostingEnvironment;
        private readonly IModelMetadataProvider _modelMetadataProvider;

        public GlobalExceptionFilter(
            IHostingEnvironment hostingEnvironment,
            IModelMetadataProvider modelMetadataProvider)
        {
            this.log = LogManager.GetLogger(Startup.repository.Name, typeof(GlobalExceptionFilter));
            _hostingEnvironment = hostingEnvironment;
            _modelMetadataProvider = modelMetadataProvider;
        }
        /// <summary>
        /// 发生异常进入
        /// </summary>
        /// <param name="context"></param>
        public void OnException(ExceptionContext context)
        {
            ContentResult result = new ContentResult
            {
                StatusCode = 500,
                ContentType = "text/json;charset=utf-8;"
            };

            if (_hostingEnvironment.IsDevelopment())
            {
                var json = new { message = context.Exception.Message };
                log.Error(json);
                result.Content = JsonConvert.SerializeObject(json);
            }
            else
            {
                result.Content = "抱歉,出错了";
            }
            context.Result = result;
            context.ExceptionHandled = true;
        }
    }
}

我们在startup.cs方法:ConfigureServices 中进行注册

services.AddSingleton<GlobalExceptionFilter>();

然后在需要的控制器上加上特性**ServiceFilter(typeof(GlobalExceptionFilter))]

    [ServiceFilter(typeof(GlobalExceptionFilter))]
    public class MangerController : Controller
    {
        public IActionResult Index()
        {
           
            try
            {
                int a = 0, b = 5;
                var result = b / a;
            }
            catch (Exception)
            {
                throw new ArgumentException("被除数不能为0", "路径:Manger/index");
            }
            return View();
        }

      
    }

这样。我们运行项目,则会出现如下效果:

NetCore 异常处理过滤器、中间件 、并整合Log4Net

 同时,我们的日志也会记录,如下:

NetCore 异常处理过滤器、中间件 、并整合Log4Net

 这样,异常过滤器就完成了。

既然有了异常过滤器,那么我们是否还需要异常中间件呢?异常中间件和异常过滤器的区别和联系是什么呢?

1、首先,两者的功能类似,都是用于异常拦截处理

2、过滤器作用于具体的控制器,或者Action,过滤器关注具体的点 ,而中间件则作用于整个应用系统,这是两者作用域的范围差别。

3、使用过滤器,我们必须显式⑩引入,譬如上述代码中的:

[ServiceFilter(typeof(GlobalExceptionFilter))]
    public class MangerController : Controller
而中间件无需显式引入,它是基于AOP的切面拦截

Net Core中使用中间件方式

首先建一个中间件,如下:

using log4net;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NetCoreXXMS.NetCoreFilter
{
    public class ExceptionMiddlewares
    {
        private ILog log;
        private readonly RequestDelegate next;
        private IHostingEnvironment environment;

        public ExceptionMiddlewares(RequestDelegate next, IHostingEnvironment environment)
        {
            this.log = LogManager.GetLogger(Startup.repository.Name, typeof(ExceptionMiddlewares));
            this.next = next;
            this.environment = environment;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await next.Invoke(context);
                var features = context.Features;
            }
            catch (Exception e)
            {
                await HandleException(context, e);
            }
        }

        private async Task HandleException(HttpContext context, Exception e)
        {
            context.Response.StatusCode = 500;
            context.Response.ContentType = "text/json;charset=utf-8;";
            string error = "";

            if (environment.IsDevelopment())
            {
                var json = new { message = e.Message };
                log.Error(json);
                error = JsonConvert.SerializeObject(json);
            }
            else
                error = "抱歉,出错了";

            await context.Response.WriteAsync(error);
        }
    }
}

然后,在启动类Configure方法中注册该中间件

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }
            //注册异常中间件
            app.UseMiddleware<ExceptionMiddlewares>();
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
View Code

相关文章: