You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 14:46:33