C#中如何创建带结构化消息模板的异常并适配Serilog日志?
实现支持结构化消息模板的自定义异常并集成Serilog+Elasticsearch/Kibana
1. 创建自定义结构化异常类
默认Exception仅保存格式化后的消息,丢失了原始模板和结构化参数。我们需要自定义异常类来保留这些关键信息,方便后续日志筛选:
public class StructuredException : Exception { public string MessageTemplate { get; } public IReadOnlyDictionary<string, object> Properties { get; } public StructuredException(string messageTemplate, params object[] args) : base(string.Format(messageTemplate, args)) { MessageTemplate = messageTemplate; Properties = ExtractProperties(messageTemplate, args); } public StructuredException(string messageTemplate, Exception innerException, params object[] args) : base(string.Format(messageTemplate, args), innerException) { MessageTemplate = messageTemplate; Properties = ExtractProperties(messageTemplate, args); } // 从模板中提取参数名与对应值 private IReadOnlyDictionary<string, object> ExtractProperties(string messageTemplate, object[] args) { var properties = new Dictionary<string, object>(); var paramMatches = System.Text.RegularExpressions.Regex.Matches(messageTemplate, @"\{(\w+)\}"); for (int i = 0; i < paramMatches.Count && i < args.Length; i++) { var paramName = paramMatches[i].Groups[1].Value; properties[paramName] = args[i]; } return properties; } } // 业务场景子类示例 public class NotFoundException : StructuredException { public NotFoundException(string messageTemplate, params object[] args) : base(messageTemplate, args) { } public NotFoundException(string messageTemplate, Exception innerException, params object[] args) : base(messageTemplate, innerException, args) { } }
2. 配置Serilog识别结构化异常
通过自定义解构策略,让Serilog将异常的MessageTemplate和结构化参数作为独立字段写入Elasticsearch:
public class StructuredExceptionDestructuringPolicy : IDestructuringPolicy { public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result) { if (value is StructuredException structuredEx) { var properties = new Dictionary<string, LogEventPropertyValue> { { "MessageTemplate", propertyValueFactory.CreatePropertyValue(structuredEx.MessageTemplate) }, { "Properties", propertyValueFactory.CreatePropertyValue(structuredEx.Properties) } }; // 将每个参数转为独立字段,简化Kibana筛选 foreach (var prop in structuredEx.Properties) { properties[prop.Key] = propertyValueFactory.CreatePropertyValue(prop.Value); } result = new StructureValue(properties); return true; } result = null; return false; } }
在Serilog初始化时添加该策略:
Log.Logger = new LoggerConfiguration() .Destructure.With<StructuredExceptionDestructuringPolicy>() .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://your-es-host:9200")) { AutoRegisterTemplate = true, IndexFormat = "serilog-logs-{0:yyyy.MM.dd}" }) .CreateLogger();
3. 业务代码中使用示例
var book = new { Id = 150, Title = ".NET实战" }; throw new NotFoundException("The book {BookId} was not found", book.Id); // 捕获并记录异常 try { // 触发异常的业务逻辑 } catch (Exception ex) { Log.Error(ex, "业务操作失败"); }
4. Kibana筛选操作
日志写入Elasticsearch后,在Kibana Discover视图中使用以下KQL语句筛选目标日志:
messageTemplate:"The book {BookId} was not found" AND BookId:[100 TO 1000]
内容的提问来源于stack exchange,提问作者RubenHerman
相关产品推荐
相关产品推荐

