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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 11:17:55