[Abp vNext 源码分析] - 8. 审计日志


一、简要说明

ABP vNext 当中的审计模块早在 之前文章 讲过的 IAmbientScopeProvider 对象。

例如在某个应用服务内部,我可以这样写代码:

using (var scope = _auditingManager.BeginScope())
{
    await myAuditedObject1.DoItAsync(new InputObject { Value1 = "我是内部嵌套测试方法1。", Value2 = 5000 });
    using (var scope2 = _auditingManager.BeginScope())
    {
        await myAuditedObject1.DoItAsync(new InputObject {Value1 = "我是内部嵌套测试方法2。", Value2 = 10000});
        await scope2.SaveAsync();
    }
    await scope.SaveAsync();
}

想一下之前的代码,在拦截器内部,我们是通过 IAuditingManager.Current 拿到当前可用的 IAuditLogScope ,而这个 Scope 就是在调用 IAuditingManager.BeginScope() 之后生成的

2.2.3 最终的持久化代码

通过上述的流程,我们得知最后的审计日志信息会通过 IAuditingStore 进行持久化。ABP vNext 为我们提供了一个默认的 SimpleLogAuditingStore 实现,其内部就是调用 ILogger 将信息输出。如果需要将审计日志持久化到数据库,你可以实现 IAUditingStore 接口,覆盖原有实现 ,或者使用 ABP vNext 提供的 Volo.Abp.AuditLogging 模块。

2.3 审计日志的序列化

审计日志的序列化处理是在 IAuditingHelper 的默认实现内部被使用,可以看到构建审计日志的方法内部,通过自定义的序列化器来将 Action 的参数进行序列化处理,方便存储。

public virtual AuditLogActionInfo CreateAuditLogAction(
    AuditLogInfo auditLog,
    Type type, 
    MethodInfo method, 
    IDictionary arguments)
{
    var actionInfo = new AuditLogActionInfo
    {
        ServiceName = type != null
            ? type.FullName
            : "",
        MethodName = method.Name,
        // 序列化参数信息。
        Parameters = SerializeConvertArguments(arguments),
        ExecutionTime = Clock.Now
    };

    //TODO Execute contributors

    return actionInfo;
}

protected virtual string SerializeConvertArguments(IDictionary arguments)
{
    try
    {
        if (arguments.IsNullOrEmpty())
        {
            return "{}";
        }

        var dictionary = new Dictionary();

        foreach (var argument in arguments)
        {
            // 忽略的代码,主要作用是构建参数字典。
        }

        // 调用序列化器,序列化 Action 的调用参数。
        return AuditSerializer.Serialize(dictionary);
    }
    catch (Exception ex)
    {
        Logger.LogException(ex, LogLevel.Warning);
        return "{}";
    }
}

下面就是具体序列化器的代码:

public class JsonNetAuditSerializer : IAuditSerializer, ITransientDependency
{
    protected AbpAuditingOptions Options;

    public JsonNetAuditSerializer(IOptions options)
    {
        Options = options.Value;
    }

    public string Serialize(object obj)
    {
        // 使用 JSON.NET 进行序列化操作。
        return JsonConvert.SerializeObject(obj, GetSharedJsonSerializerSettings());
    }

    // ... 省略的代码。
}

2.4 审计日志的配置参数

针对审计日志相关的配置参数的定义,都存放在 AbpAuditingOptions 当中。下面我会针对各个参数的用途,对其进行详细的说明。

public class AbpAuditingOptions
{
    //TODO: Consider to add an option to disable auditing for application service methods?

    // 该参数目前版本暂未使用,为保留参数。
    public bool HideErrors { get; set; }

    // 是否启用审计日志功能,默认值为 true。
    public bool IsEnabled { get; set; }

    // 审计日志的应用程序名称,默认值为 null,主要在构建 AuditingInfo 被使用。
    public string ApplicationName { get; set; }

    // 是否为匿名请求记录审计日志默认值 true。
    public bool IsEnabledForAnonymousUsers { get; set; }

    // 审计日志功能的协作者集合,默认添加了 AspNetCoreAuditLogContributor 实现。
    public List Contributors { get; }

    // 默认的忽略类型,主要在序列化时使用。
    public List IgnoredTypes { get; }

    // 实体类型选择器。
    public IEntityHistorySelectorList EntityHistorySelectors { get; }

    //TODO: Move this to asp.net core layer or convert it to a more dynamic strategy?
    // 是否为 Get 请求记录审计日志,默认值 false。
    public bool IsEnabledForGetRequests { get; set; }

    public AbpAuditingOptions()
    {
        IsEnabled = true;
        IsEnabledForAnonymousUsers = true;
        HideErrors = true;

        Contributors = new List();

        IgnoredTypes = new List
        {
            typeof(Stream),
            typeof(Expression)
        };

        EntityHistorySelectors = new EntityHistorySelectorList();
    }
}

2.4 实体相关的审计信息

在文章开始就谈到,除了对 HTTP 请求有审计日志记录以外,ABP vNext 还提供了实体审计信息的记录功能。所谓的实体的审计信息,指的就是实体继承了 ABP vNext 提供的接口之后,ABP vNext 会自动维护实现的接口字段,不需要开发人员自己再进行处理。

这些接口包括创建实体操作的相关信息 IHasCreationTimeIMayHaveCreatorICreationAuditedObject 以及删除实体时,需要记录的相关信息接口 IHasDeletionTimeIDeletionAuditedObject 等。除了审计日志模块定义的类型以外,在 Volo.Abp.Ddd.Domain 模块的 Auditing 里面也有很多审计实体的默认实现。

我在这里就不再一一列举,下面仅快速讲解一下 ABP vNext 是如何通过这些接口,实现对审计字段的自动维护的。

在审计日志模块的内部,我们看到一个接口名字叫做 IAuditPropertySetter,它提供了三个方法,分别是:

public interface IAuditPropertySetter
{
    void SetCreationProperties(object targetObject);

    void SetModificationProperties(object targetObject);

    void SetDeletionProperties(object targetObject);
}

所以,这几个方法就是用于设置创建信息、修改信息、删除信息的。现在跳转到默认实现 AuditPropertySetter,随便找一个 SetCreationTime() 方法。该方法内部首先是判断传入的 object 是否实现了 IHasCreationTime 接口,如果实现了对其进行强制类型转换,然后赋值即可。

private void SetCreationTime(object targetObject)
{
    if (!(targetObject is IHasCreationTime objectWithCreationTime))
    {
        return;
    }

    if (objectWithCreationTime.CreationTime == default)
    {
        objectWithCreationTime.CreationTime = Clock.Now;
    }
}

其他几个 Set 方法大同小异,那我们看一下有哪些地方使用到了上述三个方法。

可以看到使用者就包含有 EF Core 模块和 MongoDB 模块,这里我以 EF Core 模块为例,猜测应该是传入了实体对象过来。

果不其然...查看这个方法的调用链,发现是 DbContext 每次进行 SaveChanges/SaveChangesAsync 的时候,就会对实体进行审计字段自动赋值操作。

三、总结

审计日志是 ABP vNext 为我们提供的一个可选组件,当开启审计日志功能后,我们可以根据审计日志信息快速定位问题。但审计日志的开启,也会较大的影响性能,因为每次请求都会创建审计日志信息,之后再进行持久化。因此在使用审计日志功能时,可以结合 DisableAuditingAttribute 特性和 IAuditingManager.BeginScope(),按需开启审计日志功能。