控制请求对象解构的选项
Options for controlling Destructuring of request object
我 运行 遇到了一个问题,我正在努力寻找一个干净的解决方案,谷歌搜索并没有让我变得更聪明。
情况
(1) 我们有自己的程序集,用于设置 Serilog 记录器并将其添加到我们的任何项目(一致的日志输出、主题等),并且该程序集没有引用任何使用项目(它们是在不同的回购协议中)。我们称其为 CompanySerilog
程序集。
(2) 其中一个消费项目是外部可访问的 API,其 'contract' 对象在 ExternalContracts 程序集中定义。即请求和响应对象,以及用作这些对象一部分的任何枚举。这个 ExternalContracts 程序集可以提供给针对 API.
进行集成的开发人员
(3) 我们要记录所有请求,并使用 IActionFilter
使用 Serilog 结构化日志记录方法注销每个请求对象。例如遍历上下文中的每个参数并最终执行 _logger.LogDebug("With {name} of {@requestObject}", name, value);
问题
一些请求对象包含我们想要屏蔽的敏感数据,但是:
- 我们可以使用标准
.Destructure
扩展在 CompanySerilog
中创建记录器时定义解构方法,但不知道,或者不想知道请求对象的细节,因为这些可能来自 Api1
、Api2
等,这意味着添加对每个消费项目的引用。
- 我们可以向我们的请求对象添加属性 (
Destructurama.Attributed
),但这意味着我们的 ExternalContracts
程序集现在需要对该 NuGet 包的引用,而这又需要对所有必要的引用Serilog 包。严格来说,ExternalContracts 程序集中不需要日志记录问题:那是我们的问题,而不是 API 的消费者
正如我所说,我一直在努力想出解决这个问题的方法,但找不到太多关于使用的信息,例如 IDestructuringPolicy 以及它是否合适,或者转换是否合适应该发挥作用。到目前为止,我只能想到以下选项,但我希望其他人 运行 解决了这个问题,并且有一种非常聪明和干净的方式来支持这个用例。
解决方案?
停止进行结构化日志记录,只需为每个请求对象定义一个 ToString()
来屏蔽我们不想记录的值。这很简单,不需要讨厌的项目交叉引用或将日志记录问题添加到外部合同中。但这确实意味着无法进行结构化日志记录。
将所有需要的日志记录引用添加到外部合同中。这将允许我们继续使用内置销毁,但意味着我们 API 的消费者将拥有一个包含日志记录程序集的 ExternalContracts 程序集
在配置登录 CompanySerilog
时设置 .Destructure
值,方法是引用将使用此程序集的每个项目。不会发生!
还有别的吗?请!
听起来像是适配器模式的案例。您不希望外部 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);
我们想出了两个可能的解决方案,我会分享它们以防有人遇到类似问题 - 两者都涉及使用 IDestructuringPolicy
。
解决方案 1
在 CompanySerilog
程序集中有一个通用 IDestructuringPolicy
。
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
程序集的任何使用者都负责通过为它关心的每个 class 定义一个 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) 我们有自己的程序集,用于设置 Serilog 记录器并将其添加到我们的任何项目(一致的日志输出、主题等),并且该程序集没有引用任何使用项目(它们是在不同的回购协议中)。我们称其为 CompanySerilog
程序集。
(2) 其中一个消费项目是外部可访问的 API,其 'contract' 对象在 ExternalContracts 程序集中定义。即请求和响应对象,以及用作这些对象一部分的任何枚举。这个 ExternalContracts 程序集可以提供给针对 API.
进行集成的开发人员(3) 我们要记录所有请求,并使用 IActionFilter
使用 Serilog 结构化日志记录方法注销每个请求对象。例如遍历上下文中的每个参数并最终执行 _logger.LogDebug("With {name} of {@requestObject}", name, value);
问题
一些请求对象包含我们想要屏蔽的敏感数据,但是:
- 我们可以使用标准
.Destructure
扩展在CompanySerilog
中创建记录器时定义解构方法,但不知道,或者不想知道请求对象的细节,因为这些可能来自Api1
、Api2
等,这意味着添加对每个消费项目的引用。 - 我们可以向我们的请求对象添加属性 (
Destructurama.Attributed
),但这意味着我们的ExternalContracts
程序集现在需要对该 NuGet 包的引用,而这又需要对所有必要的引用Serilog 包。严格来说,ExternalContracts 程序集中不需要日志记录问题:那是我们的问题,而不是 API 的消费者
正如我所说,我一直在努力想出解决这个问题的方法,但找不到太多关于使用的信息,例如 IDestructuringPolicy 以及它是否合适,或者转换是否合适应该发挥作用。到目前为止,我只能想到以下选项,但我希望其他人 运行 解决了这个问题,并且有一种非常聪明和干净的方式来支持这个用例。
解决方案?
停止进行结构化日志记录,只需为每个请求对象定义一个
ToString()
来屏蔽我们不想记录的值。这很简单,不需要讨厌的项目交叉引用或将日志记录问题添加到外部合同中。但这确实意味着无法进行结构化日志记录。将所有需要的日志记录引用添加到外部合同中。这将允许我们继续使用内置销毁,但意味着我们 API 的消费者将拥有一个包含日志记录程序集的 ExternalContracts 程序集
在配置登录
CompanySerilog
时设置.Destructure
值,方法是引用将使用此程序集的每个项目。不会发生!还有别的吗?请!
听起来像是适配器模式的案例。您不希望外部 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);
我们想出了两个可能的解决方案,我会分享它们以防有人遇到类似问题 - 两者都涉及使用 IDestructuringPolicy
。
解决方案 1
在 CompanySerilog
程序集中有一个通用 IDestructuringPolicy
。
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
程序集的任何使用者都负责通过为它关心的每个 class 定义一个 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 相比,此方法的优势在于我们现在处理的是具体类型,如果该类型没有策略,则不会反映出来。