如何在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.1Serilog.Extensions.Hosting/7.0.0Serilog.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
相关产品推荐
相关产品推荐

