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

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

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

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

复制代码
    public class LogActionFilter(ILoggingService loggingService) : IAsyncActionFilter
    {
        private readonly ILoggingService _loggingService = loggingService;

        /// <summary>
        /// 日志记录-Action层级
        /// </summary>
        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...注入到全局控制器...

复制代码
        /// <summary>
        /// 注册记录日志过滤器
        /// </summary>
        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。做一个静态类注入直接调用即可

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

以下是另一种写法:在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+错误信息。

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

日志内容截图

大致就是如此啦