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

如何无需修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 14:42:15