zoukankan      html  css  js  c++  java
  • Asp.NetCore依赖注入和管道方式的异常处理及日志记录

    前言

        在业务系统,异常处理是所有开发人员必须面对的问题,在一定程度上,异常处理的能力反映出开发者对业务的驾驭水平;本章将着重介绍如何在 WebApi 程序中对异常进行捕获,然后利用 Nlog 组件进行记录;同时,还将介绍两种不同的
    异常捕获方式:管道捕获/服务过滤;通过本练习,将学习到如何捕获异常、处理异常跳转、记录异常信息。

    1. 搭建框架

        首先,创建一个 WebApi 项目,选择 Asp.Net Core Web 应用程序;

    1.1 进一步选择 Api 模板,这里使用的 .netcore 版本为 2.1

    • 取消勾选 “启用 Docker 支持(E)” 和 “为 Https 配置(C)”,点击确定,得到一个完整的 WebApi 项目框架,如图

    1.2 直接按 F5 运行项目,一切正常,程序启动后进入默认路由调用,并输出结果

    2. 异常路由

    2.1 一切看起来都非常正常和美好,但,祸之福所倚;接下来我们在 接口 Get() 中人为的制造一点麻烦。
            [HttpGet]
            public ActionResult<IEnumerable<string>> Get()
            {
                throw new Exception("出错了.....");
    
                return new string[] { "value1", "value2" };
            }
    
    2.2 这是由于项目配置了运行环境变量 ASPNETCORE_ENVIRONMENT=Development 后,Startup.cs 中配置了开发环境下,使用系统默认页,所以我们才可以看到上面的异常信息

    如果你把环境变量设置为 ASPNETCORE_ENVIRONMENT=Production ,你会发现,在异常发生的时候,你得到了一个空白页。

    3. 异常处理方式一:服务过滤

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

    public class CustomerExceptionFilter : Attribute, IExceptionFilter
    {
        private readonly ILogger logger = null;
        private readonly IHostingEnvironment environment = null;
        public CustomerExceptionFilter(ILogger<CustomerExceptionFilter> logger, IHostingEnvironment environment)
        {
            this.logger = logger;
            this.environment = environment;
        }
    
        public void OnException(ExceptionContext context)
        {
            Exception exception = context.Exception;
            string error = string.Empty;
    
            void ReadException(Exception ex)
            {
                error += string.Format("{0} | {1} | {2}", ex.Message, ex.StackTrace, ex.InnerException);
                if (ex.InnerException != null)
                {
                    ReadException(ex.InnerException);
                }
            }
    
            ReadException(context.Exception);
            logger.LogError(error);
    
            ContentResult result = new ContentResult
            {
                StatusCode = 500,
                ContentType = "text/json;charset=utf-8;"
            };
    
            if (environment.IsDevelopment())
            {
                var json = new { message = exception.Message, detail = error };
                result.Content = JsonConvert.SerializeObject(json);
            }
            else
            {
                result.Content = "抱歉,出错了";
            }
            context.Result = result;
            context.ExceptionHandled = true;
        }
    }
    
    3.1 CustomerExceptionFilter 继承自 IExceptionFilter 接口,并实现 void OnException(ExceptionContext context) 方法,在 CustomerExceptionFilter

    构造方法中,定义了两个参数,用于记录异常日志和获取程序运行环境变量

        private readonly ILogger logger = null;
        private readonly IHostingEnvironment environment = null;
        public CustomerExceptionFilter(ILogger<CustomerExceptionFilter> logger, IHostingEnvironment environment)
        {
            this.logger = logger;
            this.environment = environment;
        }
    
    3.2 在接下来的 OnException 方法中,利用 environment 进行产品环境的判断,并使用 logger 将日志写入硬盘文件中,为了将日志写入硬盘,

    需要引用 Nuget 包 NLog.Extensions.Logging/NLog.Web.AspNetCore ,并在 Startup.cs 文件的 Configure 方法中添加扩展

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory)
            {
                // 将 NLog
                factory.AddConsole(Configuration.GetSection("Logging"))
                       .AddNLog()
                       .AddDebug();
    
                var nlogFile = System.IO.Path.Combine(env.ContentRootPath, "nlog.config");
                env.ConfigureNLog(nlogFile);
    
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseMvc();
            }
    
    3.3 上面的代码读取了配置文件 nlog.config 并设置为 NLog 的配置,该文件定义如下
    <?xml version="1.0" encoding="utf-8" ?>
    <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="info">
    
      <!-- Load the ASP.NET Core plugin -->
      <extensions>
        <add assembly="NLog.Web.AspNetCore"/>
      </extensions>
    
      <!-- Layout: https://github.com/NLog/NLog/wiki/Layout%20Renderers -->
      <targets>
        <target xsi:type="File" name="errorfile" fileName="/data/logs/logfilter/error-${shortdate}.log" layout="${longdate}|${logger}|${uppercase:${level}}|  ${message} ${exception}|${aspnet-Request-Url}" />
        <target xsi:type="Null" name="blackhole" />
      </targets>
    
      <rules>
        <logger name="Microsoft.*" minlevel="Error" writeTo="blackhole" final="true" />
        <logger name="*" minlevel="Error" writeTo="errorfile" />
      </rules>
    </nlog>
    
    3.4 为了在 WebApi 控制器中使用 CustomerExceptionFilter 过滤器,我们还需要在 Startup.cs 将 CustomerExceptionFilter 注入到容器中
            // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                // 将异常过滤器注入到容器中
                services.AddScoped<CustomerExceptionFilter>();
    
                services.AddMvc()
                    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    
            }
    
    3.5 最后,在控制器 ValuesController 中应用该异常过滤器
        [ServiceFilter(typeof(CustomerExceptionFilter))]
        [Route("api/[controller]"), ApiController]
        public class ValuesController : ControllerBase
        {
            // GET api/values
            [HttpGet]
            public ActionResult<IEnumerable<string>> Get()
            {
                throw new Exception("出错了.....");
                return new string[] { "value1", "value2" };
            }
        }
    
    3.6 现在,按 F5 启动程序,如预期所料,报错信息被 CustomerExceptionFilter 捕获,并转换为 json 格式输出

    3.7 同时,NLog 组件也将日志信息记录到了硬盘中

    4. 异常处理方式二:中间件捕获

    接下来利用 .NetCore 的管道模式,在中间件中对异常进行捕获,首先,创建一个中间件

    public class ExceptionMiddleware
    {
        private readonly RequestDelegate next;
        private readonly ILogger logger;
        private IHostingEnvironment environment;
    
        public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger, IHostingEnvironment environment)
        {
            this.next = next;
            this.logger = logger;
            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 = "";
    
            void ReadException(Exception ex)
            {
                error += string.Format("{0} | {1} | {2}", ex.Message, ex.StackTrace, ex.InnerException);
                if (ex.InnerException != null)
                {
                    ReadException(ex.InnerException);
                }
            }
    
            ReadException(e);
            if (environment.IsDevelopment())
            {
                var json = new { message = e.Message, detail = error };
                error = JsonConvert.SerializeObject(json);
            }
            else
                error = "抱歉,出错了";
    
            await context.Response.WriteAsync(error);
        }
    }
    
    4.1 代码比较简单,在管道中使用 try/catch 进行捕获异常

    创建 HandleException(HttpContext context, Exception e) 处理异常,判断是 Development 环境下,输出详细的错误信息,非 Development 环境仅提示调用者“抱歉,出错了”,同时使用 NLog 组件将日志写入硬盘;同样,在 Startup.cs 中将 ExceptionMiddleware 加入管道中

            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory)
            {
                // 将 NLog
                factory.AddConsole(Configuration.GetSection("Logging"))
                       .AddNLog()
                       .AddDebug();
    
                var nlogFile = System.IO.Path.Combine(env.ContentRootPath, "nlog.config");
                env.ConfigureNLog(nlogFile);
    
                // ExceptionMiddleware 加入管道
                app.UseMiddleware<ExceptionMiddleware>();
    
                //if (env.IsDevelopment())
                //{
                //    app.UseDeveloperExceptionPage();
                //}
    
                app.UseMvc();
            }
    
    4.2 一切就绪,按 F5 运行程序,网页中输出了期望中的 json 格式错误信息,同时 NLog 组件也将日志写入了硬盘

    结语

    在本例中,通过依赖注入和管道中间件的方式,演示了两种不同的全局捕获异常处理的过程;值得注意到是,两种方式对于 NLog 的使用,都是一样的,没有任何差别,代码无需改动;实际项目中,也是应当区分不同的业务场景,输出不同的日志信息,不管是从安全或者是用户体验友好性上面来说,都是非常值得推荐的方式,全局异常捕获处理,完全和业务剥离。

    源码下载

    https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.LogFilter

  • 相关阅读:
    [GO]使用map生成 json
    [GO]通过结构体生成json
    [GO]正则表达式
    [GO]字符串的使用
    [GO]revoer的应用
    [GO]panic的应用
    微信公众平台自定义菜单及高级接口PHP SDK
    论MySQL何时使用索引,何时不使用索引
    MYSQL explain详解
    Mysql两种存储引擎的优缺点
  • 原文地址:https://www.cnblogs.com/viter/p/10013195.html
Copyright © 2011-2022 走看看