• .Net Core 审计日志实现


    前言:

      近日在项目协同开发过程中出现了问题,数据出现了异常;其他人员怀疑项目数据丢失程序存在问题。于是通过排查程序提供的审计日志最终还原了当时操作及原因。

      可见审计日志在排查、定位问题是相当有用的,那么在.Net Core 如何来实现审计日志呢?

      接下来一步步来实现效果

    一、审计日志定义及作用

     审计日志:  

    维基百科: “审计跟踪(也叫审计日志)是与安全相关的按照时间顺序的记录,记录集或者记录源,它们提供了活动序列的文档证据,这些活动序列可以在任何时间影响一个特定的操作,步骤或其他”

     作用:

      1、快速定位问题耗时及性能情况

      2、记录调用时环境信息:如浏览器、参数等

    二、.Net Core 中实现审计日志

     那么怎么实现审计日志呢?其实核心思想很简单。包含以下步骤:

    • 获取调用接口方法时相关信息
    • 记录当前接口耗时情况
    • 保存审计日志信息到数据库中

      那么如何获取调用接口时相关信息呢?.Net Core中可以使用:过滤器、拦截器 实现。 

      本次示例中将采用过滤器实现审计日志实现功能;主要流程如下

     

    • 定义审计日志信息:  

      public class AuditInfo
      {
          /// <summary>
          /// 调用参数
          /// </summary>
          public string Parameters { get; set; }
          /// <summary>
          /// 浏览器信息
          /// </summary>
          public string BrowserInfo { get; set; }
          /// <summary>
          /// 客户端信息
          /// </summary>
          public string ClientName { get; set; }
          /// <summary>
          /// 客户端IP地址
          /// </summary>
          public string ClientIpAddress { get; set; }
          /// <summary>
          /// 执行耗时
          /// </summary>
          public int ExecutionDuration { get; set; }
          /// <summary>
          /// 执行时间
          /// </summary>
          public DateTime ExecutionTime { get; set; }
          /// <summary>
          /// 返回内容
          /// </summary>
          public string ReturnValue { get; set; }
          /// <summary>
          /// 异常对象
          /// </summary>
          public Exception Exception { get; set; }
          /// <summary>
          /// 方法名
          /// </summary>
          public string MethodName { get; set; }
          /// <summary>
          /// 服务名
          /// </summary>
          public string ServiceName { get; set; }
          /// <summary>
          /// 调用者信息
          /// </summary>
          public string UserInfo { get; set; }
          /// <summary>
          /// 自定义数据
          /// </summary>
          public string CustomData { get; set; }
      }
    • 实现审计日志过滤器
      using AuditLogDemo.Models;
      using AuditLogDemo.Services;
      using Microsoft.AspNetCore.Http;
      using Microsoft.AspNetCore.Mvc;
      using Microsoft.AspNetCore.Mvc.Controllers;
      using Microsoft.AspNetCore.Mvc.Filters;
      using Microsoft.Extensions.Logging;
      using Newtonsoft.Json;
      using System;
      using System.Collections.Generic;
      using System.Diagnostics;
      using System.Linq;
      using System.Reflection;
      using System.Threading.Tasks;
      
      namespace AuditLogDemo.Fliters
      {
          public class AuditLogActionFilter : IAsyncActionFilter
          {
              /// <summary>
              /// 审计日志服务对象
              /// </summary>
              private readonly IAuditLogService _auditLogService;
              /// <summary>
              /// 登录用户
              /// </summary>
              private readonly ISession _Session;
              /// <summary>
              /// 日志记录
              /// </summary>
              private readonly ILogger<AuditLogActionFilter> _logger;
      
              public AuditLogActionFilter(
                  IAuditLogService auditLogService,
                  ISession Session,
                  ILogger<AuditLogActionFilter> logger
                  )
              {
                  _Session = Session;
                  _logger = logger;
                  _auditLogService = auditLogService;
              }
      
              public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
              {
                  // 判断是否写日志
                  if (!ShouldSaveAudit(context))
                  {
                      await next();
                      return;
                  }
                  //接口Type
                  var type = (context.ActionDescriptor as ControllerActionDescriptor).ControllerTypeInfo.AsType();
                  //方法信息
                  var method = (context.ActionDescriptor as ControllerActionDescriptor).MethodInfo;
                  //方法参数
                  var arguments = context.ActionArguments;
                  //开始计时
                  var stopwatch = Stopwatch.StartNew();
                  var auditInfo = new AuditInfo
                  {
                      UserInfo = _Session?.Id,
                      ServiceName = type != null ? type.FullName.TruncateWithPostfix(EntityDefault.FieldsLength250) : "",
                      MethodName = method.Name.TruncateWithPostfix(EntityDefault.FieldsLength250),
                      ////请求参数转Json
                      Parameters = JsonConvert.SerializeObject(arguments),
                      ExecutionTime = DateTime.Now,
                      BrowserInfo = context.HttpContext.Request.Headers["User-Agent"].ToString().TruncateWithPostfix(EntityDefault.FieldsLength250),
                      ClientIpAddress = context.HttpContext.Connection.RemoteIpAddress.ToString().TruncateWithPostfix(EntityDefault.FieldsLength50),
                      //ClientName = _clientInfoProvider.ComputerName.TruncateWithPostfix(EntityDefault.FieldsLength100),
                      Id = Guid.NewGuid().ToString()
                  };
      
                  ActionExecutedContext result = null;
                  try
                  {
                      result = await next();
                      if (result.Exception != null && !result.ExceptionHandled)
                      {
                          auditInfo.Exception = result.Exception;
                      }
                  }
                  catch (Exception ex)
                  {
                      auditInfo.Exception = ex;
                      throw;
                  }
                  finally
                  {
                      stopwatch.Stop();
                      auditInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds);
      
                      if (result != null)
                      {
                          switch (result.Result)
                          {
                              case ObjectResult objectResult:
                                  auditInfo.ReturnValue = JsonConvert.SerializeObject(objectResult.Value);
                                  break;
      
                              case JsonResult jsonResult:
                                  auditInfo.ReturnValue = JsonConvert.SerializeObject(jsonResult.Value);
                                  break;
      
                              case ContentResult contentResult:
                                  auditInfo.ReturnValue = contentResult.Content;
                                  break;
                          }
                      }
                      Console.WriteLine(auditInfo.ToString());
                      //保存审计日志
                      await _auditLogService.SaveAsync(auditInfo);
                  }
              }
      
              /// <summary>
              /// 是否需要记录审计
              /// </summary>
              /// <param name="context"></param>
              /// <returns></returns>
              private bool ShouldSaveAudit(ActionExecutingContext context)
              {
                  if (!(context.ActionDescriptor is ControllerActionDescriptor))
                      return false;
                  var methodInfo = (context.ActionDescriptor as ControllerActionDescriptor).MethodInfo;
      
                  if (methodInfo == null)
                  {
                      return false;
                  }
      
                  if (!methodInfo.IsPublic)
                  {
                      return false;
                  }
      
                  if (methodInfo.GetCustomAttribute<AuditedAttribute>() != null)
                  {
                      return true;
                  }
      
                  if (methodInfo.GetCustomAttribute<DisableAuditingAttribute>() != null)
                  {
                      return false;
                  }
      
                  var classType = methodInfo.DeclaringType;
                  if (classType != null)
                  {
                      if (classType.GetTypeInfo().GetCustomAttribute<AuditedAttribute>() != null)
                      {
                          return true;
                      }
      
                      if (classType.GetTypeInfo().GetCustomAttribute<AuditedAttribute>() != null)
                      {
                          return false;
                      }
                  }
                  return false;
              }
          }
      }

       该内容为实现审计日志功能主要逻辑,通过过滤器获取当前执行控制器、方法判断是否需要记录审计日志;其他请求参数、客户端ip等相关基本信息组成审计日志对象,并记录调用时间。 

    • 注册过滤器
      public void ConfigureServices(IServiceCollection services)
      {
          services.AddControllers(options =>
          {
              options.Filters.Add(typeof(AuditLogActionFilter));
          });
      
          //审计日志存储
          services.AddDbContext<AuditLogDBContent>(options =>
          {
              string conn = Configuration.GetConnectionString("LogDB");
              options.UseSqlite(conn);
          });
      }

      到此审计日志主要逻辑已经实现完成。是不是很简单

    三、总结

     回过头来看,在.net core 中需要统一监控或过滤时,可以采用过滤器(Filter)或拦截器来实现相关效果 

     .Net Core中 Filter 常件的有:Authorization Filter(认证过滤器),Resource Filter(资源过滤器),Exception Filter(异常过滤器),Action Filter(方法过滤器),Result Filter(结果过滤器)

     

     

  • 相关阅读:
    学习算法必备数学
    Use NDepend to Measure How SOLID Your Code Is
    使用Docker 快速体验TDengine
    ASP.NET Core 修改开源协议为MIT,.NET全平台 MIT协议开源了
    DNS泛域名解析应用(nip.io/sslip.io)
    对象池在 .NET (Core)中的应用[3]: 扩展篇
    对象池在 .NET (Core)中的应用[2]: 设计篇
    对象池在 .NET (Core)中的应用[1]: 编程篇
    项目组织结构的3种类型:职能型、项目型和矩阵型
    [LeetCode] 1208. Get Equal Substrings Within Budget 尽可能使字符串相等
  • 原文地址:https://www.cnblogs.com/cwsheng/p/14199085.html
Copyright © 2020-2023  润新知