.Net Core 審計日誌實現

chaney1992發表於2020-12-27

前言:

  近日在專案協同開發過程中出現了問題,資料出現了異常;其他人員懷疑專案資料丟失程式存在問題。於是通過排查程式提供的審計日誌最終還原了當時操作及原因。

  可見審計日誌在排查、定位問題是相當有用的,那麼在.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(結果過濾器)

 

 

相關文章