zoukankan      html  css  js  c++  java
  • .NET Core开发实战(第22课:异常处理中间件:区分真异常与逻辑异常)--学习笔记(下)

    接下来介绍使用代理方法的方式,也就是说把 ErrorController 整段逻辑直接定义在注册的地方,使用一个匿名委托来处理,这里的逻辑与之前的逻辑是相同的

    app.UseExceptionHandler(errApp =>
    {
        errApp.Run(async context =>
        {
            // 在 Features 里面获取异常
            var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
            // 识别异常是否为 IKnownException
            IKnownException knownException = exceptionHandlerPathFeature.Error as IKnownException;
            if (knownException == null)
            {
                // 如果不是则记录并且把错误的响应码响应成 Http 500
                var logger = context.RequestServices.GetService<ILogger<MyExceptionFilterAttribute>>();
                logger.LogError(exceptionHandlerPathFeature.Error, exceptionHandlerPathFeature.Error.Message);
                knownException = KnownException.Unknown;
                context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            }
            else
            {
                // 如果捕获到的是一个业务逻辑的异常,Http 响应码应该给是 200
                knownException = KnownException.FromKnownException(knownException);
                context.Response.StatusCode = StatusCodes.Status200OK;
            }
            // 然后再把响应信息通过 json 的方式输出出去
            var jsonOptions = context.RequestServices.GetService<IOptions<JsonOptions>>();
            context.Response.ContentType = "application/json; charset=utf-8";
            await context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(knownException, jsonOptions.Value.JsonSerializerOptions));
        });
    });
    

    为什么对于未知的异常要输出 Http 500,而对于业务逻辑的异常,建议输出 Http 200?

    因为监控系统实际上会对 Http 的响应码进行识别,当监控系统识别到 Http 响应是 500 的比例比较高的情况下,会认为系统的可用性有问题,这个时候告警系统就会发出警告

    对于已知的业务逻辑的这种正常的识别的话,用正常的 Http 200 来处理是一个正常的行为,这样就可以让监控系统更好的工作,正确的识别出系统的一些未知的错误信息,错误的告警,让告警系统更加的灵敏,也避免了业务逻辑的异常干扰告警系统

    接下来看一下第三种,通过异常过滤器的方式

    这种方式实际上是作用在 MVC 的整个框架的体系下面的,它并不是在中间件的最早期发生作用的,它是在 MVC 的整个生命周期里面发生作用,也就是说它只能工作在 MVC Web API 的请求周期里面

    首先自定义一个 MyExceptionFilter

    namespace ExceptionDemo.Exceptions
    {
        public class MyExceptionFilter : IExceptionFilter
        {
            public void OnException(ExceptionContext context)
            {
                IKnownException knownException = context.Exception as IKnownException;
                if (knownException == null)
                {
                    var logger = context.HttpContext.RequestServices.GetService<ILogger<MyExceptionFilterAttribute>>();
                    logger.LogError(context.Exception, context.Exception.Message);
                    knownException = KnownException.Unknown;
                    context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
                }
                else
                {
                    knownException = KnownException.FromKnownException(knownException);
                    context.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
                }
                context.Result = new JsonResult(knownException)
                {
                    ContentType = "application/json; charset=utf-8"
                };
            }
        }
    }
    

    处理逻辑与之前的相同

    接着注册 Filters

    services.AddMvc(mvcOptions =>
    {
        mvcOptions.Filters.Add<MyExceptionFilter>();
    }).AddJsonOptions(jsonoptions =>
    {
        jsonoptions.JsonSerializerOptions.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
    });
    

    启动程序,输出如下:

    {"message":"未知错误","errorCode":9999,"errorData":null}
    

    输出与之前的一致,因为这是在 Controller 里面输出了错误

    如果在 MVC 的中间件之前输出错误的话,它是没办法处理的

    这个场景一般情况下是指需要对 Controller 进行特殊的异常处理,而对于中间件整体来讲的话,又要用另一种特殊的逻辑来处理的时候,可以用 ExceptionFilter 的方式处理

    这种方式还可以通过 Attribute 的方式

    自定义一个 MyExceptionFilterAttribute

    namespace ExceptionDemo.Exceptions
    {
        public class MyExceptionFilterAttribute : ExceptionFilterAttribute
        {
            public override void OnException(ExceptionContext context)
            {
                IKnownException knownException = context.Exception as IKnownException;
                if (knownException == null)
                {
                    var logger = context.HttpContext.RequestServices.GetService<ILogger<MyExceptionFilterAttribute>>();
                    logger.LogError(context.Exception, context.Exception.Message);
                    knownException = KnownException.Unknown;
                    context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
                }
                else
                {
                    knownException = KnownException.FromKnownException(knownException);
                    context.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
                }
                context.Result = new JsonResult(knownException)
                {
                    ContentType = "application/json; charset=utf-8"
                };
            }
        }
    }
    

    在 Controller 上面标注 MyExceptionFilter

    [MyExceptionFilter]
    public class WeatherForecastController : ControllerBase
    

    启动运行之后效果相同

    这两种方式的效果是对等的,区别在于说可以更细粒度的对异常处理进行控制,可以指定部分的 Controller 或者 Exception,来决定我们的异常处理,也可以在全局注册 ExceptionFilter

    当然因为 ExceptionFilterAttribute 也实现了 IExceptionFilter,所以它也可以注册到全局,也可以把它当作全局异常处理的过滤器来使用,Controller 上面也就不需要标记了

    注册 Filters

    services.AddMvc(mvcOptions =>
    {
        //mvcOptions.Filters.Add<MyExceptionFilter>();
        mvcOptions.Filters.Add<MyExceptionFilterAttribute>();
    }).AddJsonOptions(jsonoptions =>
    {
        jsonoptions.JsonSerializerOptions.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
    });
    

    在 Controller 上面取消标注 MyExceptionFilter

    //[MyExceptionFilter]
    public class WeatherForecastController : ControllerBase
    

    启动程序,输出结果一致

    这个场景对于我们定义一些 API,然后对 API 进行定义我们的异常处理的约定是很有帮助的

    总结一下

    首先我们需要定义特定的异常类或者接口,我们可以定义抽象类,也可以用接口的方式,例子中是通过接口的方式表示业务逻辑的异常

    对于业务逻辑的异常,实际上需要定义全局的错误码

    对于未知的异常,应该输出特定的输出信息和错误码,然后记录完整的日志,我们不应该把系统内部的一些比如说异常堆栈这些信息输出给用户

    对于已知的业务逻辑的异常,用 Http 200 的方式,对于未知的异常,用 Http 500 的方式,这样可以让监控系统更好的工作

    另外一个建议就是尽量记录所有的异常的详细信息,以供后续对日志进行分析,也供监控系统做一些特定的监控警告

    知识共享许可协议

    本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

    欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

    如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

  • 相关阅读:
    jQuery--.wrap()方法
    ECharts学习(4)--仪表盘
    ECharts学习(3)--toolbox(工具栏)
    jQuery之核心API
    STM32片上Flash内存映射、页面大小、寄存器映射
    typedef struct bit0 : 1
    ***WARNING L15: MULTIPLE CALL TO SEGMENT
    C/C++ 打印文件名、行号、函数名的方法
    ISP与IAP的区别
    sprintf函数 %6.2f
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/12483923.html
Copyright © 2011-2022 走看看