一、过滤器简介
ASP.NET Core中的过滤器是一种组件,它可以在请求处理管道中的特定阶段运行代码。过滤器有多种类型,包括授权过滤器、资源过滤器、动作过滤器、异常过滤器和结果过滤器。本文中使用的是动作过滤器(Action Filter),它在动作方法执行前后执行,可以用来记录请求和响应信息。
二、自定义GlobalActionFilter类
1. 类定义
- /// <summary>
- /// Action过滤器
- /// </summary>
- public class GlobalActionFilter : IActionFilter, IOrderedFilter
- {
- /// <summary>
- /// 构造
- /// </summary>
- /// <param name="logger"></param>
- public GlobalActionFilter(SugarDbContext dbContext, ILogger<GlobalActionFilter> logger)
- {
- this.dbContext = dbContext;
- this.logger = logger;
- }
- private readonly SugarDbContext dbContext;
- private readonly ILogger<GlobalActionFilter> logger;
- /// <summary>
- /// 过滤器执行顺序
- /// </summary>
- public int Order => 2; // 设置执行顺序
- private (string Method, string Path, string ClientIp) GetRequestInfo(HttpContext context)
- {
- string method = context.Request.Method;
- string serverIp = context.Connection.LocalIpAddress.GetFormattedIpAddress();
- string serverBaseUrl = $"http://{serverIp}:{context.Connection.LocalPort}";
- string pathSmall = context.Request.Path.ToString();
- string path = serverBaseUrl + pathSmall;
- string clientIp = context.Connection.RemoteIpAddress.GetFormattedIpAddress();
- return (method, path, clientIp);
- }
- private string GetInParam(ActionExecutingContext context)
- {
- return context.ActionArguments.Values.FirstOrDefault()?.ToJson() ?? "Null";
- }
- private string GetOutParam(ActionExecutedContext context)
- {
- JsonResult? response = context.Result as JsonResult;
- return response?.Value != null ? JsonConvert.SerializeObject(response.Value) : "";
- }
- private EmResponseStaus GetResponseStatus(string pathSmall, string outParam)
- {
- if (string.IsNullOrWhiteSpace(outParam))
- {
- return EmResponseStaus.ReqNull;
- }
- try
- {
- //如果是TesTaskNotice接口,则使用TesResponse因为它的返回值和ApiResponse不一样
- if (pathSmall.Contains("TesTaskNotice"))
- {
- var apiResponse = outParam.ToObject<TesResponse>();
- return apiResponse?.ReturnCode == 0 ? EmResponseStaus.Success : EmResponseStaus.Fail;
- }
- else
- {
- var apiResponse = outParam.ToObject();
- return apiResponse?.Code == 0 ? EmResponseStaus.Success : EmResponseStaus.Fail;
- }
- }
- catch (JsonException)
- {
- logger.LogError($"在记录http日志时,序列化返参失败,path:{pathSmall} | outParam:{outParam}");
- return EmResponseStaus.Unknown;
- }
- catch (Exception ex)
- {
- logger.LogError($"在记录http日志时,序列化返参失败,走到了Catch中,msg:{ex.Message},path:{pathSmall} | outParam:{outParam}");
- return EmResponseStaus.Unknown;
- }
- }
- /// <summary>
- /// 执行Action之前
- /// </summary>
- /// <param name="context"></param>
- public void OnActionExecuting(ActionExecutingContext context)
- {
- SkipActionFilterAttribute? skipActionFilterAttribute = context.ActionDescriptor.EndpointMetadata.OfType<SkipActionFilterAttribute>().FirstOrDefault();
- // 判断是否存在 SkipActionFilterAttribute 特性
- bool hasSkipActionFilter = skipActionFilterAttribute != null;
- var (method, path, clientIp) = GetRequestInfo(context.HttpContext);
- if (method == "GET" || path.Contains("Test") || hasSkipActionFilter) return; // 忽略GET请求和测试控制器
- string inParam = GetInParam(context);
- //当前请求的唯一ID
- string requestId = context.HttpContext.TraceIdentifier;
- HttpApiLogEntity httpApiLogEntity = new HttpApiLogEntity()
- {
- Method = Tool.ToMethodEnum(method),
- Url = path,
- IPAddress = clientIp,
- InParam = inParam,
- OutParam = "",
- ResponseStaus = EmResponseStaus.Unknown,
- IsIncomingRequest = true,
- SystemType = EmSystemType.WCS,
- RequestId = requestId,
- CreateTime = DateTime.Now,
- };
- dbContext.HttpApiLogEntity.Insert(httpApiLogEntity);
- if (!context.ModelState.IsValid)//如果在进入Action之前 就已经判断到入参有误 则直接返回不进入Action
- {
- List<string>? errors = context.ModelState.SelectMany(x => x.Value.Errors)
- .Select(x => x.ErrorMessage)
- .ToList();
- ApiResponse? outParam = new ApiResponse
- {
- Code = EmApiResCode.ReqError, //入参有误 返回2
- Msg = string.Join(',', errors),
- Data = false
- };
- logger.LogInformation(
- $"Method: {method}, Path: {path}, IP: {clientIp}, InParam: {inParam}, OutParam: {outParam.ToJson()}");
- httpApiLogEntity.OutParam = outParam.ToJson();
- httpApiLogEntity.ResponseStaus = outParam.Code == 0 ? EmResponseStaus.Success : EmResponseStaus.Fail;
- httpApiLogEntity.EndTime = DateTime.Now;
- dbContext.HttpApiLogEntity.Update(httpApiLogEntity);
- context.Result = new JsonResult(outParam);
- }
- }
- /// <summary>
- /// 执行Action之后
- /// </summary>
- /// <param name="context"></param>
- public void OnActionExecuted(ActionExecutedContext context)
- {
- SkipActionFilterAttribute? skipActionFilterAttribute = context.ActionDescriptor.EndpointMetadata.OfType<SkipActionFilterAttribute>()
- .FirstOrDefault();
- // 判断是否存在 SkipActionFilterAttribute 特性
- bool hasSkipActionFilter = skipActionFilterAttribute != null;
- var (method, path, clientIp) = GetRequestInfo(context.HttpContext);
- string pathSmall = context.HttpContext.Request.Path.ToString();
- if (method == "GET" || pathSmall.Contains("Test") || hasSkipActionFilter) return; // 忽略GET请求和测试控制器
- string requestId = context.HttpContext.TraceIdentifier;
- HttpApiLogEntity? httpApiLogEntity = dbContext.HttpApiLogEntity.GetFirst(x => x.RequestId == requestId);
- if (httpApiLogEntity != null)
- {
- string outParam = GetOutParam(context);
- httpApiLogEntity.OutParam = outParam;
- httpApiLogEntity.ResponseStaus = GetResponseStatus(pathSmall, outParam);
- httpApiLogEntity.EndTime = DateTime.Now;
- dbContext.HttpApiLogEntity.Update(httpApiLogEntity);
- }
- }
- }
复制代码 该类实现了IActionFilter接口,该接口定义了OnActionExecuting和OnActionExecuted方法,分别在动作方法执行前和执行后调用。同时实现了IOrderedFilter接口,用于指定过滤器的执行顺序。
2. 构造函数
- /// <summary>
- /// 构造
- /// </summary>
- /// <param name="logger"></param>
- public GlobalActionFilter(SugarDbContext dbContext, ILogger<GlobalActionFilter> logger)
- {
- this.dbContext = dbContext;
- this.logger = logger;
- }
- private readonly SugarDbContext dbContext;
- private readonly ILogger<GlobalActionFilter> logger;
复制代码 构造函数接收数据库上下文对象SugarDbContext和日志记录器ILogger,用于后续的数据库操作和日志记录。
3. 执行顺序
- /// <summary>
- /// 过滤器执行顺序
- /// </summary>
- public int Order => 2; // 设置执行顺序
复制代码 通过实现IOrderedFilter接口的Order属性,指定该过滤器的执行顺序为2。数值越小,过滤器越先执行。
4. 获取请求信息
- private (string Method, string Path, string ClientIp) GetRequestInfo(HttpContext context)
- {
- string method = context.Request.Method;
- string serverIp = context.Connection.LocalIpAddress.GetFormattedIpAddress();
- string serverBaseUrl = $"http://{serverIp}:{context.Connection.LocalPort}";
- string pathSmall = context.Request.Path.ToString();
- string path = serverBaseUrl + pathSmall;
- string clientIp = context.Connection.RemoteIpAddress.GetFormattedIpAddress();
- return (method, path, clientIp);
- }
复制代码 该方法从HttpContext中提取请求方法、完整请求路径和客户端IP地址,并以元组形式返回。
5. 获取输入参数
- private string GetInParam(ActionExecutingContext context)
- {
- return context.ActionArguments.Values.FirstOrDefault()?.ToJson() ?? "Null";
- }
复制代码 从ActionExecutingContext的ActionArguments中获取动作方法的输入参数,并序列化为JSON字符串。如果没有参数,则返回"Null"。
6. 获取输出参数
- private string GetOutParam(ActionExecutedContext context)
- {
- JsonResult? response = context.Result as JsonResult;
- return response?.Value != null ? JsonConvert.SerializeObject(response.Value) : "";
- }
复制代码 从ActionExecutedContext的Result中获取动作方法的返回结果,如果是JsonResult类型,则将其值序列化为JSON字符串返回。
7. 获取响应状态
- private EmResponseStaus GetResponseStatus(string pathSmall, string outParam)
- {
- if (string.IsNullOrWhiteSpace(outParam))
- {
- return EmResponseStaus.ReqNull;
- }
- try
- {
- //如果是TesTaskNotice接口,则使用TesResponse因为它的返回值和ApiResponse不一样
- if (pathSmall.Contains("TesTaskNotice"))
- {
- var apiResponse = outParam.ToObject<TesResponse>();
- return apiResponse?.ReturnCode == 0 ? EmResponseStaus.Success : EmResponseStaus.Fail;
- }
- else
- {
- var apiResponse = outParam.ToObject();
- return apiResponse?.Code == 0 ? EmResponseStaus.Success : EmResponseStaus.Fail;
- }
- }
- catch (JsonException)
- {
- logger.LogError($"在记录http日志时,序列化返参失败,path:{pathSmall} | outParam:{outParam}");
- return EmResponseStaus.Unknown;
- }
- catch (Exception ex)
- {
- logger.LogError($"在记录http日志时,序列化返参失败,走到了Catch中,msg:{ex.Message},path:{pathSmall} | outParam:{outParam}");
- return EmResponseStaus.Unknown;
- }
- }
复制代码 根据响应结果和请求路径判断响应状态。如果是特定接口TesTaskNotice,则根据TesResponse的ReturnCode判断;否则根据ApiResponse的Code判断。如果序列化失败,则记录错误日志并返回未知状态。
8. 动作执行前
- /// <summary>
- /// 执行Action之前
- /// </summary>
- /// <param name="context"></param>
- public void OnActionExecuting(ActionExecutingContext context)
- {
- SkipActionFilterAttribute? skipActionFilterAttribute = context.ActionDescriptor.EndpointMetadata.OfType<SkipActionFilterAttribute>().FirstOrDefault();
- // 判断是否存在 SkipActionFilterAttribute 特性
- bool hasSkipActionFilter = skipActionFilterAttribute != null;
- var (method, path, clientIp) = GetRequestInfo(context.HttpContext);
- if (method == "GET" || path.Contains("Test") || hasSkipActionFilter) return; // 忽略GET请求和测试控制器
- string inParam = GetInParam(context);
- //当前请求的唯一ID
- string requestId = context.HttpContext.TraceIdentifier;
- HttpApiLogEntity httpApiLogEntity = new HttpApiLogEntity()
- {
- Method = Tool.ToMethodEnum(method),
- Url = path,
- IPAddress = clientIp,
- InParam = inParam,
- OutParam = "",
- ResponseStaus = EmResponseStaus.Unknown,
- IsIncomingRequest = true,
- SystemType = EmSystemType.WCS,
- RequestId = requestId,
- CreateTime = DateTime.Now,
- };
- dbContext.HttpApiLogEntity.Insert(httpApiLogEntity);
- if (!context.ModelState.IsValid)//如果在进入Action之前 就已经判断到入参有误 则直接返回不进入Action
- {
- List<string>? errors = context.ModelState.SelectMany(x => x.Value.Errors)
- .Select(x => x.ErrorMessage)
- .ToList();
- ApiResponse? outParam = new ApiResponse
- {
- Code = EmApiResCode.ReqError, //入参有误 返回2
- Msg = string.Join(',', errors),
- Data = false
- };
- logger.LogInformation(
- $"Method: {method}, Path: {path}, IP: {clientIp}, InParam: {inParam}, OutParam: {outParam.ToJson()}");
- httpApiLogEntity.OutParam = outParam.ToJson();
- httpApiLogEntity.ResponseStaus = outParam.Code == 0 ? EmResponseStaus.Success : EmResponseStaus.Fail;
- httpApiLogEntity.EndTime = DateTime.Now;
- dbContext.HttpApiLogEntity.Update(httpApiLogEntity);
- context.Result = new JsonResult(outParam);
- }
- }
复制代码 在动作方法执行前,首先判断是否应跳过该过滤器(例如GET请求、测试控制器或标记了SkipActionFilterAttribute特性的方法)。然后获取请求信息和输入参数,创建HttpApiLogEntity对象并插入数据库。如果模型状态无效(入参有误),则构造错误响应,更新日志记录并返回错误响应。
9. 动作执行后
- /// <summary>
- /// 执行Action之后
- /// </summary>
- /// <param name="context"></param>
- public void OnActionExecuted(ActionExecutedContext context)
- {
- SkipActionFilterAttribute? skipActionFilterAttribute = context.ActionDescriptor.EndpointMetadata.OfType<SkipActionFilterAttribute>()
- .FirstOrDefault();
- // 判断是否存在 SkipActionFilterAttribute 特性
- bool hasSkipActionFilter = skipActionFilterAttribute != null;
- var (method, path, clientIp) = GetRequestInfo(context.HttpContext);
- string pathSmall = context.HttpContext.Request.Path.ToString();
- if (method == "GET" || pathSmall.Contains("Test") || hasSkipActionFilter) return; // 忽略GET请求和测试控制器
- string requestId = context.HttpContext.TraceIdentifier;
- HttpApiLogEntity? httpApiLogEntity = dbContext.HttpApiLogEntity.GetFirst(x => x.RequestId == requestId);
- if (httpApiLogEntity != null)
- {
- string outParam = GetOutParam(context);
- httpApiLogEntity.OutParam = outParam;
- httpApiLogEntity.ResponseStaus = GetResponseStatus(pathSmall, outParam);
- httpApiLogEntity.EndTime = DateTime.Now;
- dbContext.HttpApiLogEntity.Update(httpApiLogEntity);
- }
- }
复制代码 在动作方法执行后,同样判断是否应跳过该过滤器。然后根据请求ID从数据库中获取之前插入的日志记录,获取输出参数和响应状态,更新日志记录的输出参数、响应状态和结束时间。
三、使用过滤器
1. 注册过滤器
在Startup.cs文件的ConfigureServices方法中注册过滤器:- public void ConfigureServices(IServiceCollection services)
- {
- // 其他服务注册...
- services.AddControllers(options =>
- {
- options.Filters.Add<GlobalActionFilter>();
- });
- }
复制代码 这样,该过滤器将应用于所有控制器的动作方法。如果只希望应用于特定控制器或动作方法,可以在控制器类或动作方法上添加[TypeFilter(typeof(GlobalActionFilter))]特性。
2. 示例
假设我们有一个简单的控制器:- using Microsoft.AspNetCore.Mvc;
- namespace Project.Controllers
- {
- [Route("api/[controller]")]
- [ApiController]
- public class WeatherForecastController : ControllerBase
- {
- [HttpPost]
- public IActionResult Post([FromBody] WeatherForecast forecast)
- {
- // 处理逻辑...
- return Ok(new { Message = "Success" });
- }
- }
- }
复制代码 当发送POST请求到该控制器的Post方法时,GlobalActionFilter将记录请求和响应信息到数据库中。
四、总结
通过自定义动作过滤器,我们可以方便地在ASP.NET Core应用中记录Http API日志。这不仅有助于系统的调试和维护,还能提供有价值的运行时信息。在实际应用中,可以根据具体需求对过滤器进行扩展和优化,例如添加更多的日志字段、支持不同的日志存储方式等。
希望本文能帮助你理解和使用ASP.NET Core中的请求过滤器来记录API日志。如果有任何问题或建议,欢迎留言讨论。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |