如何让Serilog解构运算符忽略null值属性?
如何让Serilog使用@解构运算符时不序列化null值属性?
我之前找过两篇相关帖子:
- 2019年的《Ignore null values when destructuring in Serilog》,里面唯一的答复说无法实现;
- 2019年的《Ignore null properties from being written to output?》提到可以通过自定义
ILogEventEnricher实现,但没给出具体方法。
我们当前的Serilog配置是:Console接收器搭配Serilog.Templates.ExpressionTemplate、Serilog.Expressions格式化器,同时使用默认格式化器的File接收器。目前为了忽略输出中的null值,我们用了一个基于JSON.NET的自定义扩展方法ToJson来把对象序列化为字符串:
public static string? ToJson ( this object data ) { JsonSerializer json = new() { // 不要修改下面这行,否则可能无法序列化层级数据 PreserveReferencesHandling = PreserveReferencesHandling.Objects, NullValueHandling = NullValueHandling.Ignore, DateFormatString = "yyyy-MM-ddTHH:mm:ss.fffZ", Formatting = Formatting.None, ReferenceLoopHandling = ReferenceLoopHandling.Ignore, TypeNameHandling = TypeNameHandling.None, }; StringWriter textWriter = new(); json.Serialize(textWriter, data); return textWriter.ToString(); }
举个例子,假设对象是:
{"propA":"valueA","propB":null,"propC":"valueC"}
使用logger.LogDebug("Data: {data}", data.ToJson());输出的日志是:
Data: {"propA":"valueA","propC":"valueC"}
但用logger.LogDebug("Data: {@data}", data);时,日志会保留null值属性:
Data: {"propA":"valueA","propB":null,"propC":"valueC"}
可行的实现方案
方案1:自定义IDestructuringPolicy(推荐)
Serilog的解构逻辑由IDestructuringPolicy控制,实现自定义策略可在解构阶段直接过滤null值属性:
using Serilog.Core; using Serilog.Events; using System.Reflection; public class IgnoreNullPropertiesDestructuringPolicy : IDestructuringPolicy { public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result) { // 跳过值类型、字符串和null本身,沿用默认解构逻辑 if (value == null || value.GetType().IsValueType || value is string) { result = null; return false; } // 仅保留非null值的属性 var properties = value.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.GetValue(value) != null) .Select(p => new LogEventProperty( p.Name, propertyValueFactory.CreatePropertyValue(p.GetValue(value), destructureObjects: true))); result = new StructureValue(properties); return true; } }
注册该策略到Serilog配置中:
Log.Logger = new LoggerConfiguration() .Destructure.With<IgnoreNullPropertiesDestructuringPolicy>() .WriteTo.Console(new ExpressionTemplate("{@t:yyyy-MM-dd HH:mm:ss} [{@l}] {@m}\n")) .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day) .CreateLogger();
此方案会在解构阶段移除null属性,所有接收器(Console/File等)都会生效,且支持嵌套对象的递归过滤。
方案2:在ExpressionTemplate中过滤null属性
若仅需在使用ExpressionTemplate的接收器中过滤null,可直接修改模板表达式:
将原模板:
"{@t:yyyy-MM-dd HH:mm:ss} [{@l}] Data: {@data}\n"
修改为:
"{@t:yyyy-MM-dd HH:mm:ss} [{@l}] Data: {@data.Select(kv => kv.Value != null ? {kv.Key: kv.Value} : null).Where(v => v != null)}\n"
此方式仅对ExpressionTemplate生效,File接收器若用默认格式化器仍会显示null属性,适合仅控制台过滤的场景。
内容的提问来源于stack exchange,提问作者Alek Davis
相关产品推荐
相关产品推荐

