如何通过枚举类实现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
相关产品推荐
相关产品推荐

