我遇到了一个问题,我一直在努力寻找一个干净的解决方案,而谷歌搜索并没有使我变得更明智。
情况
(1)我们有自己的程序集,用于设置Serilog记录器并将其添加到我们的任何项目(一致的记录输出,主题等),并且该程序集没有引用任何消耗项目(在不同项目中)回购)。我们将其称为CompanySerilog
程序集。
(2)消耗项目之一是一个外部可访问的API,为此,在ExternalContracts程序集中定义了“合同”对象。即请求和响应对象,以及用作这些对象一部分的任何枚举。可以将此ExternalContracts程序集提供给与API集成的开发人员。
(3)我们要记录所有请求,并使用IActionFilter
使用Serilog结构化记录方法注销每个请求对象。例如遍历上下文中的每个参数并最终执行_logger.LogDebug("With {name} of {@requestObject}", name, value);
问题
某些请求对象具有我们要屏蔽的敏感数据,但是:
CompanySerilog
扩展名在.Destructure
中创建记录器时定义解构方法,但是不知道或不希望知道请求对象的详细信息,因为这些可能来自Api1
,Api2
等,这意味着向每个消耗项目添加引用。Destructurama.Attributed
中添加属性,但这意味着ExternalContracts
程序集现在需要对该NuGet包的引用,而这又需要对所有必需的Serilog包的引用。严格来讲,在ExternalContracts程序集中不需要记录日志:这是我们的问题,而不是我们的API使用者正如我所说,我一直在努力想办法解决这个问题,并且在使用IDestructuringPolicy等信息的方式上找不到很多,甚至是否合适,或者是否应该进行转换发挥作用。到目前为止,我只能想到以下选项,但我希望其他人遇到此问题,并拥有支持该用例的邪恶聪明和简洁的方法。
解决方案?
停止执行结构化日志记录,并为每个请求对象定义一个ToString()
,以掩盖我们不想记录的值。这很简单,不需要讨厌的项目交叉引用或将日志记录问题添加到外部合同中。但这确实意味着不可能进行结构化日志记录。
将所有必需的日志记录引用添加到外部协定中。这将使我们能够继续使用内置销毁功能,但意味着我们的API使用者将拥有一个包含日志记录程序集的ExternalContracts程序集
在.Destructure
中配置日志记录时,通过引用将消耗此程序集的每个项目来设置CompanySerilog
值。会发生!
还有别的吗?拜托!
答案 0 :(得分:2)
我们提出了两个潜在的解决方案,以防万一有人遇到类似问题,我将与大家分享-都涉及使用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的优势在于,我们现在正在处理具体类型,如果该类型没有策略,则不会被反映出来。
答案 1 :(得分:1)
听起来像是适配器模式的情况。您不希望外部API涉及日志记录问题,也不希望CompanySerilog
知道API中的特殊情况。最好的选择可能是创建一个包装对象,该包装对象(临时)保存对请求对象的引用。记录包装程序,该包装程序将仅具有您要在日志中显示的属性。
由于包装程序除了被包装的对象之外不会保留任何状态,因此甚至可以通过池重新使用它们,以消除GC开销。
大致:
public class Request {
public string Username { get; set; } // log this
public string Password { get; set; } // but not this
}
public class RequestLogWrapper {
public Request WrappedRequest { private get; set; }
public String Username { get { return WrappedRequest.Username; }
}
//To use:
var rlw = new RequestLogWrapper { Request = request };
logger.log("Got a request: {0}", rlw);