首页 > 解决方案 > 控制请求对象解构的选项

问题描述

我遇到了一个问题,我正在努力寻找一个干净的解决方案,而谷歌搜索并没有让我变得更聪明。

情况

(1) 我们有自己的程序集,用于为我们的任何项目(一致的日志输出、主题等)设置和添加 Serilog 记录器,并且该程序集不引用任何消费项目(位于不同的存储库中)。让我们称之为CompanySerilog程序集。

(2) 消费项目之一是外部可访问的 API,其“合同”对象在 ExternalContracts 程序集中定义。即请求和响应对象,以及用作这些对象一部分的任何枚举。可以将此 ExternalContracts 程序集提供给针对 API 进行集成的开发人员。

(3) 我们要记录所有请求,并使用IActionFilterSerilog 结构化记录方法来注销每个请求对象。例如循环上下文中的每个参数并最终执行_logger.LogDebug("With {name} of {@requestObject}", name, value);

问题

一些请求对象包含我们想要屏蔽的敏感数据,但是:

正如我所说,我一直在努力想出解决这个问题的方法,并且找不到太多关于使用的信息,例如,IDestructuringPolicy 以及它是否合适,或者转换是否应该发挥作用。到目前为止,我只能想到以下选项,但我希望其他人已经遇到了这个问题,并且有一种非常聪明和干净的方式来支持这个用例。

解决方案?

标签: c#asp.net-coreserilog

解决方案


我们提出了两个潜在的解决方案,如果有人遇到类似问题,我将分享这些解决方案 - 两者都涉及使用IDestructuringPolicy.

解决方案 1

IDestructuringPolicy在程序集中有一个泛型CompanySerilog

public class SensitiveDataDestructuringPolicy : IDestructuringPolicy
    {
        public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
        {
            var props = value.GetType().GetTypeInfo().DeclaredProperties;
            var logEventProperties = new List<LogEventProperty>();

            foreach (var propertyInfo in props)
            {
                switch (propertyInfo.Name.ToLower())
                {
                    case "cardnumber":
                    case "password":
                        logEventProperties.Add(new LogEventProperty(propertyInfo.Name,propertyValueFactory.CreatePropertyValue("***")));
                        break;
                    default:
                        logEventProperties.Add(new LogEventProperty(propertyInfo.Name, propertyValueFactory.CreatePropertyValue(propertyInfo.GetValue(value))));
                        break;
                }

            }
            result = new StructureValue(logEventProperties);
            return true;
        }
    }

并且在设置记录器时,使用以下类型的配置:

var logger = new LoggerConfiguration()
// snipped out all the other things that need configuring
// ...
.Destructure.With<SensitiveDataDestructuringPolicy>
.CreateLogger();

这种方法的优点:

  • 一个地方(在日志程序集中)负责决定如何记录对象,而不知道这些对象将是什么类型

这种方法的缺点:

  • 这将反映每个对象的每个属性,如果只有一个或两个对象需要被屏蔽,这将是多余的

最后,由于第一个解决方案的缺点,我们采用了不同的方法。

解决方案 2

让创建记录器的方法在CompanySerilog使用它的任何程序集中查找 IDestructuringPolicies。

public static ILogger Create()
{
    var destructuringPolicies = GetAllDestructuringPolicies();

    var logger = new LoggerConfiguration()
    // snipped out all the other things that need configuring
    // ...
    .Destructure.With(destructuringPolicies)
    .CreateLogger();

    //Set the static instance of Serilog.Log with the same config
    Log.Logger = logger;

    logger.Debug($"Found {destructuringPolicies.Length} destructuring policies");
    return logger;
}

/// <summary>
/// Finds all classes that implement IDestructuringPolicy, in the assembly that is calling this 
/// </summary>
/// <returns></returns>
private static IDestructuringPolicy[] GetAllDestructuringPolicies()
{
    var policies = Assembly.GetEntryAssembly().GetTypes().Where(x => typeof(IDestructuringPolicy).IsAssignableFrom(x));
    var instances = policies.Select(x => (IDestructuringPolicy)Activator.CreateInstance(x));
    return instances.ToArray();
}

现在,该程序集的任何使用者CompanySerilog都负责定义它希望如何记录敏感数据,方法是IDestructuringPolicy为它关心的每个类定义一个。例如:

public class RegisterNewUserDestructuringPolicy : IDestructuringPolicy
{
    public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
    {
        var request = value as RegisterNewUserRequest;
        if (request == null)
        {
            result = null;
            return false;
        }

        var logEventProperties = new List<LogEventProperty>
            {
                new LogEventProperty(nameof(request.Claims), propertyValueFactory.CreatePropertyValue(request.Claims)),
                new LogEventProperty(nameof(request.Email), propertyValueFactory.CreatePropertyValue(request.Email)),
                new LogEventProperty(nameof(request.Password), propertyValueFactory.CreatePropertyValue("****")),
                new LogEventProperty(nameof(request.Roles), propertyValueFactory.CreatePropertyValue(request.Roles)),
                new LogEventProperty(nameof(request.UserName),
                    propertyValueFactory.CreatePropertyValue(request.UserName))
            };

        result = new StructureValue(logEventProperties);
        return true;
    }
}

这种方法相对于解决方案 1 的优势在于,我们现在正在处理具体类型,如果该类型没有策略,那么它将不会被反映。


推荐阅读