如何无需修改Serilog消息模板实现日志属性分隔显示?
解决Serilog连续属性输出自动添加分隔符的问题
可以通过自定义**文本格式化器(ITextFormatter)**实现全局修改连续属性的输出格式,无需逐个修改日志消息模板。以下是两种适配不同场景的实现方案:
方案1:仅处理纯属性的日志模板
如果你的日志模板全部由连续属性组成(如"{Prop1}{Prop2}{Prop3}"),可以使用这个轻量方案:
实现自定义格式化器
using Serilog; using Serilog.Events; using Serilog.Formatting; using Serilog.Formatting.Display; using System.IO; using System.Linq; public class PurePropertyDelimiterFormatter : ITextFormatter { private readonly MessageTemplateTextFormatter _innerFormatter; private const string Delimiter = ", "; public PurePropertyDelimiterFormatter(string outputTemplate) { _innerFormatter = new MessageTemplateTextFormatter(outputTemplate, null); } public void Format(LogEvent logEvent, TextWriter output) { // 判断当前日志模板是否仅包含属性Token var allArePropertyTokens = logEvent.MessageTemplate.Tokens.All(t => t is PropertyToken); if (!allArePropertyTokens) { // 非纯属性模板,使用原始格式化逻辑 _innerFormatter.Format(logEvent, output); return; } // 提取属性值并拼接分隔符 var propertyValues = logEvent.MessageTemplate.Tokens .Cast<PropertyToken>() .Select(token => logEvent.Properties.TryGetValue(token.PropertyName, out var value) ? value.ToString().Trim('"') // 移除Serilog默认给字符串加的引号 : null); output.Write(string.Join(Delimiter, propertyValues)); } }
配置Serilog使用该格式化器
Log.Logger = new LoggerConfiguration() .WriteTo.Console(new PurePropertyDelimiterFormatter("{Message:lj}")) // 按需添加其他输出目标(如文件),同样使用该格式化器 .CreateLogger();
方案2:处理混合文本与属性的模板
如果你的日志模板存在文本与属性混合的情况(如"User {Id} performed {Action}"),但希望仅对连续的属性Token自动添加分隔符,可以使用这个更通用的方案:
实现自定义格式化器
using Serilog; using Serilog.Events; using Serilog.Formatting; using System.Collections.Generic; using System.IO; public class ContinuousPropertyDelimiterFormatter : ITextFormatter { private readonly ITextFormatter _innerFormatter; private const string Delimiter = ", "; public ContinuousPropertyDelimiterFormatter(ITextFormatter innerFormatter) { _innerFormatter = innerFormatter ?? throw new System.ArgumentNullException(nameof(innerFormatter)); } public void Format(LogEvent logEvent, TextWriter output) { var originalTokens = logEvent.MessageTemplate.Tokens; var modifiedTokens = new List<MessageTemplateToken>(); PropertyToken? lastPropertyToken = null; foreach (var token in originalTokens) { // 当遇到连续的属性Token时,插入分隔符文本Token if (token is PropertyToken currentProp && lastPropertyToken != null) { modifiedTokens.Add(new TextToken(Delimiter)); } modifiedTokens.Add(token); lastPropertyToken = token is PropertyToken ? (PropertyToken)token : null; } // 创建修改后的消息模板与日志事件 var modifiedTemplate = new MessageTemplate(logEvent.MessageTemplate.Text, modifiedTokens); var modifiedLogEvent = new LogEvent( logEvent.Timestamp, logEvent.Level, logEvent.Exception, modifiedTemplate, logEvent.Properties.Select(kv => new LogEventProperty(kv.Key, kv.Value))); // 使用原始格式化器输出修改后的日志 _innerFormatter.Format(modifiedLogEvent, output); } }
配置Serilog使用该格式化器
Log.Logger = new LoggerConfiguration() .WriteTo.Console(new ContinuousPropertyDelimiterFormatter( new MessageTemplateTextFormatter("{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}"))) .CreateLogger();
方案说明
- 优点:全局生效,无需修改任何现有日志代码,自动为连续属性添加分隔符。
- 注意事项:
- 方案1仅适配纯属性模板,方案2适配所有模板场景,但逻辑稍复杂。
- 可以根据需求修改
Delimiter常量的值(如改成" | "等)。 - 测试时需覆盖多种模板场景,确保不会意外修改不需要的输出格式。
内容的提问来源于stack exchange,提问作者Dustin Siewert
相关产品推荐
相关产品推荐

