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

如何通过枚举类实现Serilog日志标签的强制规范

最佳实践:用枚举规范Serilog结构化日志的属性标签

你的需求非常合理——用枚举强制日志属性名的一致性,避免拼写错误、统一团队的日志规范,这在结构化日志中是很常见的最佳实践。下面是具体的实现方案,结合Serilog的特性来做最优雅的处理:

1. 定义日志标签枚举

首先定义枚举,枚举成员名直接对应你想要的日志属性名(比如原来的{Item}就对应LogTags.Item)。如果需要属性名和枚举名不同,可以用DescriptionAttribute做映射:

using System.ComponentModel;

// 基础版本:枚举名直接等于日志属性名
public enum LogTags
{
    Item,
    UserId,
    OrderNumber,
    ProxyOperation
    // 添加所有需要统一的日志标签
}

// 进阶版本:如果需要属性名和枚举名不同,用DescriptionAttribute
public enum LogTags
{
    [Description("Item")]
    ProcessedItem,
    [Description("UserId")]
    UserIdentifier
    // 其他标签...
}

2. 封装枚举转属性名的工具方法

如果用了DescriptionAttribute,需要写一个扩展方法来获取真实的属性名;如果是基础版本,直接用ToString()就可以,不过封装成方法更统一:

public static class LogTagExtensions
{
    public static string ToPropertyName(this LogTags tag)
    {
        var fieldInfo = tag.GetType().GetField(tag.ToString());
        if (fieldInfo == null) return tag.ToString();
        
        var descriptionAttr = fieldInfo.GetCustomAttribute<DescriptionAttribute>();
        return descriptionAttr?.Description ?? tag.ToString();
    }
}

3. 优雅地在Serilog中使用枚举标签

Serilog的结构化日志推荐直接使用ILogger.Error(string messageTemplate, params object[] propertyValues)方法(替代ErrorFormat,因为Serilog的模板语法本身就是结构化的)。结合枚举,有两种常用方式:

方式一:直接在模板中使用枚举(简洁直观)

直接通过枚举获取属性名,嵌入到日志模板中:

public class Foo
{
    private readonly ILogger _logger;

    public Foo(ILogger logger)
    {
        _logger = logger;
    }

    public void HandleItemError(Item item)
    {
        var itemPropertyName = LogTags.Item.ToPropertyName();
        // 模板中使用枚举对应的属性名
        _logger.Error("Proxy Logic for the Item {PropertyName} failed. Swallow exception", 
            new Dictionary<string, object> { { itemPropertyName, item } });
        
        // 或者更简洁的写法(利用Serilog的属性传递)
        _logger.Error($"Proxy Logic for the Item {{{LogTags.Item.ToPropertyName()}}} failed. Swallow exception", item);
    }
}

方式二:封装扩展方法(强制规范,减少重复代码)

如果团队需要严格强制使用枚举标签,最好封装一个ILogger的扩展方法,让日志方法只能接受枚举作为属性名,从根源上避免自定义字符串:

public static class LoggerTagExtensions
{
    public static void ErrorWithTag(this ILogger logger, LogTags tag, string messageTemplate, object propertyValue)
    {
        var propertyName = tag.ToPropertyName();
        // 替换模板中的占位符(比如用{Tag}作为临时占位符)
        var structuredTemplate = messageTemplate.Replace("{Tag}", $"{{{propertyName}}}");
        logger.Error(structuredTemplate, propertyValue);
    }

    // 可以重载其他日志级别(Info、Warning等)
    public static void InfoWithTag(this ILogger logger, LogTags tag, string messageTemplate, object propertyValue)
    {
        var propertyName = tag.ToPropertyName();
        var structuredTemplate = messageTemplate.Replace("{Tag}", $"{{{propertyName}}}");
        logger.Information(structuredTemplate, propertyValue);
    }
}

使用时就非常简洁,而且只能用枚举标签:

public void HandleItemError(Item item)
{
    _logger.ErrorWithTag(LogTags.Item, "Proxy Logic for the Item {Tag} failed. Swallow exception", item);
}

4. 额外建议:结合Serilog的Destructuring特性

如果你的Item是复杂对象,Serilog支持用@符号来解构对象(比如{@Item}),在扩展方法中也可以支持这个特性,比如:

public static void ErrorWithDestructuredTag(this ILogger logger, LogTags tag, string messageTemplate, object propertyValue)
{
    var propertyName = tag.ToPropertyName();
    // 添加@符号支持对象解构
    var structuredTemplate = messageTemplate.Replace("{Tag}", $"{{@{propertyName}}}");
    logger.Error(structuredTemplate, propertyValue);
}

使用时就能自动解构Item对象:

_logger.ErrorWithDestructuredTag(LogTags.Item, "Proxy Logic for the Item {Tag} failed. Swallow exception", item);

这样日志中会包含Item对象的所有属性,而不是只显示ToString()的结果,更符合结构化日志的需求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:53:19