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

如何在Serilog中通过ILogger作用域序列化任意对象为JSON

问题描述

我使用标准ILogger配合Serilog记录日志,基础输出功能正常,但通过ILogger<T>.BeginScope添加的自定义对象(如TestLoggableType实例)未序列化为JSON结构,仅显示类型名称(如"TestApp.Program+TestLoggableType")。同时,自定义异常的额外字段也未被序列化到日志中。

当前配置与代码

Serilog模板配置

return loggerConfiguration.WriteTo.Console(new ExpressionTemplate(
  "{ { timestamp:@t, message:@m, messageTemplate:@mt, level:@l, exception:@x, context:@p } }\n"
));

自定义类型定义

// 记录类型
public record TestLoggableRecord(string Name, int Value);

// 自定义可日志类型
public class TestLoggableType
{
    public string Name { get; }
    public int Thing { get; }

    public TestLoggableType(string name, int thing)
    {
        Name = name;
        Thing = thing;
    }
}

// 自定义异常
public class CustomException : Exception
{
    public object? Context { get; }
    public bool IsRetryable { get; }

    public CustomException(string message, object? context = null, bool isRetryable = false)
        : base(message)
    {
        Context = context;
        IsRetryable = isRetryable;
    }
}

作用域使用方式

Dictionary<string, object?> scopes = new()
{
    { "context", new TestLoggableType("Blah", 1) }
};

using (logger.BeginScope(scopes))
{
    logger.LogError(testException, "Error");
}

完整示例代码

public record TestLoggableRecord(string Name, int Value);

public class TestLoggableType
{
    public string Name { get; }
    public int Thing { get; }

    public TestLoggableType(string name, int thing)
    {
        Name = name;
        Thing = thing;
    }
}

public class CustomException : Exception
{
    public object? Context { get; }
    public bool IsRetryable { get; }

    public CustomException(string message, object? context = null, bool isRetryable = false)
        : base(message)
    {
        Context = context;
        IsRetryable = isRetryable;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        Exception testException = new("Message", new TestLoggableRecord("Blah", 1));
    
        IHost host = Host.CreateDefaultBuilder()
            .UseSerilog((_, loggerConfiguration) =>
            {
                loggerConfiguration
                    .Enrich.FromLogContext()
                    .WriteTo
                    .Console(new ExpressionTemplate("{ { timestamp:@t, message:@m, messageTemplate:@mt, level:@l, exception:@x, context:@p } }\n"));
            })
            .Build();

        ILogger<IHost> logger = (ILogger<IHost>)host.Services.GetService(typeof(ILogger<IHost>))!;

        Dictionary<string, object?> scopes = new()
        {
            { "context", new TestLoggableType("Blah", 1) }
        };

        using (logger.BeginScope(scopes))
        {
            logger.LogError(testException, "Error");
        }
    }
}

使用的NuGet包版本

  • Serilog.Expressions/3.4.1
  • Serilog.Extensions.Hosting/7.0.0
  • Serilog.Sinks.Console/4.1.0

当前实际输出

{"timestamp":"2023-09-27T14:10:35.2964020-05:00","message":"Error","messageTemplate":"Error","level":"Error","exception":"JsonNodeSerialize.Program+CustomException: Message","context":{"SourceContext":"Microsoft.Extensions.Hosting.IHost","context":"JsonNodeSerialize.Program+TestLoggableType"}}

解决方案

1. 处理作用域对象的JSON序列化

默认情况下,Serilog的@p(日志属性)会对复杂对象调用ToString(),导致仅输出类型名。需使用Serilog.Expressions提供的Serialize()函数,将属性对象序列化为JSON结构:

修改Serilog的ExpressionTemplate配置,将context:@p替换为context:Serialize(@p):

.WriteTo.Console(new ExpressionTemplate(
  "{ { timestamp:@t, message:@m, messageTemplate:@mt, level:@l, exception:@x, context:Serialize(@p) } }\n"
));

若需针对特定类型强制解构(而非全局序列化),可在Serilog配置中添加Destructure规则:

loggerConfiguration
    .Enrich.FromLogContext()
    .Destructure.AsObjects<TestLoggableType>() // 对该类型自动解构为对象
    .Destructure.AsObjects<TestLoggableRecord>()
    .WriteTo.Console(new ExpressionTemplate(/* ... */));

2. 处理自定义异常的额外字段序列化

要捕获自定义异常的额外属性(如Context、IsRetryable),需安装Serilog.Exceptions包(版本匹配当前Serilog版本),然后在配置中添加异常详情增强:

loggerConfiguration
    .Enrich.FromLogContext()
    .Enrich.WithExceptionDetails() // 捕获自定义异常的属性
    .Destructure.AsObjects<TestLoggableType>()
    .WriteTo.Console(new ExpressionTemplate(/* ... */));

修改后的输出示例

{"timestamp":"2023-09-27T14:10:35.2964020-05:00","message":"Error","messageTemplate":"Error","level":"Error","exception":"JsonNodeSerialize.Program+CustomException: Message","context":{"SourceContext":"Microsoft.Extensions.Hosting.IHost","context":{"Name":"Blah","Thing":1}}}

内容的提问来源于stack exchange,提问作者Lee Crabtree

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 03:02:07