• 日志记录升级(中间件全局日志)


    1.继承IExceptionFilter只是用于记录全局异常异常日志,现在我想记录每个请求的日志并且入库。

    需要用到IAsyncActionFilter,继承该接口,用于记录每一个action方法的请求信息,作用是记录每个操作的记录,简单点来讲就是记录哪个人调用了哪个方法。

    添加一个继承该接口的过滤器,并添加所需操作,这里就是记录每一个请求的操作记录

    复制代码
        public class LogActionFilter(ILoggingService loggingService) : IAsyncActionFilter
        {
            private readonly ILoggingService _loggingService = loggingService;
    
            /// 
            /// 日志记录-Action层级
            /// 
            public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
            {
                var result = await next();
                var log = new LogModel
                {
                    Content = result.Exception == null ? "操作记录" : result.Exception.Message,
                    CreateTime = DateTime.Now,
                    Level = result.Exception == null ? 0 : 1,
                    Controller = result.HttpContext.Request.RouteValues["controller"] as string ?? "",
                    Action = result.HttpContext.Request.RouteValues["action"] as string ?? "",
                    Source = result.HttpContext.Request.Path,
                    Name = "测试用户"
                };
                await _loggingService.InsertAsync(log);
            }
        }
    复制代码

    注入该过滤器到全局。这里我是为了简洁program,把注入的操作添加到静态类,也可以在program里面build.services…注入到全局控制器…

    复制代码
            /// 
            /// 注册记录日志过滤器
            /// 
            public static void AddActionFilterModule(this IServiceCollection services)
            {
                services.AddControllers(option =>
                {
                    option.Filters.Add(typeof(LogActionFilter));
                });
            }
    复制代码

    本身并没有太多操作,但是没用过难免就不知道,比如我…以前都是哪里记录日志就单独写一下,没有使用过全局的。


     

    分割线上面的可以记录到正常的操作记录,后面我添加了jwt登录验证,就引发了另外的问题。

    假如token错误,或者未输入,我做了处理,让错误异常返回统一的格式,而不是单纯的把异常抛出到前端。但是因为此时还没有进入控制器,属于是中间件级别的异常,上面的IExceptionFilter和IAsyncActionFilter过滤器只是过滤action级别的。因此异常和日志无法记录。

    解决方式是通过中间件!

    不知道大家是不是经常使用中间件,不算难,但是不经常用,就总会忘。

    中间件的添加是app.UseMiddleware<>()

    也可以直接

    app.Use(async (context, next)=>{}); 

     

     中间件的执行顺序是执行完第一个再执行第二个。依次执行

    添加一个类,RequestDelegate 是必要的一个委托条件,作用就是执行下一个中间件。ILoggingService是我记录日志入库的接口,注入进来

     要添加一个Invoke方法。参数为:HttpContext。 这个参数就是HTTP请求的一些信息。

    通过代码可知:_next(context)  就是把http请求内容放到委托,用于执行下一个中间件操作。

    因为中间件异常通过过滤器获取不到,这里就通过trycatch来在中间件里拦截,进行处理。

    复制代码
            public class LogAopFilter(ILoggingService loggingService, RequestDelegate next)
            {
                private readonly RequestDelegate _next = next;
                private readonly ILoggingService _loggingService = loggingService;
    
                public async Task Invoke(HttpContext context)
                {
                    try
                    {
                        await _next(context);
                    }
                    catch (HttpException ex)
                    {
                        #region 过滤中间件异常并自定义返回结果
    
                        context.Response.StatusCode = ex.StatusCode;
                        context.Response.ContentType = "application/json";
                        var errorResponse = new R
                        {
                            Code = StateCode.ERROR,
                            Data = null,
                            Msg = ex.Message
                        };
                        var jsonErrorResponse = JsonConvert.SerializeObject(errorResponse);
                        await context.Response.WriteAsync(jsonErrorResponse);
    
                        #endregion 过滤中间件异常并自定义返回结果
    
                        #region 异常日志记录
    
                        var log = new LogModel
                        {
                            Content = ex.Message,
                            CreateTime = DateTime.Now,
                            Level = 1,
                            Source = ex.Source ?? "",
                            Name = "测试用户"
                        };
                        await _loggingService.InsertAsync(log);
    
                        #endregion 异常日志记录
                    }
                }
            }
    复制代码

     

     最后就是注入中间件即可。还是为了简洁program。做一个静态类注入直接调用即可

    复制代码
            /// 
            /// 统一添加中间件
            /// 1.捕捉中间件级别异常
            /// 
            /// 
            public static void UseAopModule(this IApplicationBuilder builder)
            {
                builder.UseMiddleware();           
            }
    复制代码

     

     以下是另一种写法:在program里面直接使用app.use ,一开始是在program里面写的,嫌弃太长了,搞得program看着不好看,就没这么写,看个人喜欢吧

    复制代码
                app.Use(async (context, next) =>
                {
                    try
                    {
                        await next.Invoke(context);
                    }
                    catch (HttpException ex)
                    {
                        #region 过滤中间件异常
    
                        context.Response.StatusCode = ex.StatusCode;
                        context.Response.ContentType = "application/json";
                        var errorResponse = new R
                        {
                            Code = StateCode.ERROR,
                            Data = null,
                            Msg = ex.Message
                        };
                        var jsonErrorResponse = JsonConvert.SerializeObject(errorResponse);
                        await context.Response.WriteAsync(jsonErrorResponse);
    
                        #endregion 过滤中间件异常
                    }
                });
    复制代码

     

     下面就是效果截图。不输入token让他报错,一般不处理他就是401。因为我想更人性化,会分辨出是token没有或者是token错误,或者是token过期,我认证的部分有处理,丢出相应的错误信息,为的就是方便好处理,故此我的这个接口正常情况下是401+错误信息。

    错误信息我经过中间件的处理,和正常接口返回的结构是一样的,这样方便前端去处理,只记住一种返回结构就行了

     日志内容截图

     

    大致就是如此啦


     

    追加一部分,原本token认证那边我是手动抛异常来让程序的全局异常来捕捉。调试的时候总是会中断程序,虽然实际发布了并不会影响什么,但是目前太影响了。所以稍微改了一下,大体上没什么变化。代码如下 。

    这是token认证里面的代码,OnChallenge 就是token异常的时候会执行,在此修改状态码,和返回内容即可。不懂这块儿可以搜一下jwt相关内容,或者用不到可以不看

    这里的处理是用于返回自定义内容。

    复制代码
    //当JWT Bearer认证失败时,即请求未包含有效的JWT令牌或令牌验证失败,该事件会被触发
    OnChallenge = context =>
    {
        context.HandleResponse();
        context.Response.StatusCode = 401;
        context.Response.ContentType = "application/json";
        var errMsg = $"Token无效: {(string.IsNullOrEmpty(context.ErrorDescription) ? "请输入正确的Token" : context.ErrorDescription)}";
        var errSource = "Token认证失败";
        var errorResponse = new R
        {
            Code = StateCode.ERROR,
            Data = null,
            Msg = errMsg
        };
        context.HttpContext.Items.Add("errror", errMsg);
        context.HttpContext.Items.Add("source", errSource);
    
        var jsonErrorResponse = JsonConvert.SerializeObject(errorResponse);
        return context.Response.WriteAsync(jsonErrorResponse);
    }
    复制代码

    前文说过了, Token认证这一部分的异常算是中间件级别,不会触发全局异常过滤器。只能在中间件处理(也许有别的方法),但取消掉了手动异常,日志可能就无法完整记录,因此…看代码!

    其实就是加上finally,最后判断下是不是200状态码,如果不是就记录日志,200的状态码那就是成功的请求,自有异常过滤器处理,这里主要针对中间件级别的异常处理的。

    复制代码
                    try
                    {
                        await _next(context);
                    }
                    catch (HttpException ex)
                    {
                    }
                    finally
                    {
                        if (context.Response.StatusCode != 200)
                        {
                            #region 异常日志记录
                            var log = new LogModel
                            {
                                Content = context.Items["errror"] as string ?? "操作失败",
                                CreateTime = DateTime.Now,
                                Level = 1,
                                Source = context.Items["source"] as string ?? "中间件异常",
                                Name = "测试用户"
                            };
                            await _loggingService.InsertAsync(log);
                            #endregion 异常日志记录
                        }
                    }
    复制代码

     

    真的拜拜了

  • 相关阅读:
    nginx关闭重启和配置检查
    Vue源码学习(十四):diff算法patch比对
    【推荐】javaweb JAVA JSP家政服务管理系统服务网站jsp服务信息管理jsp保姆月嫂招聘系统案例设计与实现源码
    Sentinel 热点规则 (ParamFlowRule)
    【VS Code】根据时间和文件名查看增删改的历史记录
    Java面试题基础第十一天
    【ONE·Linux || 多线程(一)】
    Vue3 学习笔记 —— 局部/全局组件、递归组件、动态组件、异步组件
    MySQL大小版本升级步骤
    【虹科干货】什么是信号调制?
  • 原文地址:https://www.cnblogs.com/zhang-3/p/17943024