• 如何基于Newtonsoft Json实现console log formatter
  • 发布于 2个月前
  • 263 热度
    0 评论
  • 远行客
  • 0 粉丝 38 篇博客
  •   
背景
.NET 从 5.0 开始支持了 console formatter,首次支持了 json console, 自定义 console 日志也变得可能,然而 .NET 里的 json console 里的 json 编码要求是比较严格的,有一些特殊符号和特殊编码会有点问题,我们需要使用 JavaScriptEncoder.UnsafeRelaxedJsonEscaping 才能正常显式,于是想着直接基于 newtonsoft json 来实现一个 console log formatter 。

实现原理
参考 JsonConsoleFormatter 可以实现基于 NewtonsoftJson 的 NewtonJsonFormatter ,实现如下:
public sealed class NewtonJsonFormatterOptions: ConsoleFormatterOptions
{
}

public sealed class NewtonJsonFormatter: ConsoleFormatter
{
    public const string FormatterName = "NewtonJson";
    
    private readonly NewtonJsonFormatterOptions _options;

    public NewtonJsonFormatter(IOptions<NewtonJsonFormatterOptions> options) : base(FormatterName)
    {
      // 堆代码 duidaima.com
        _options = options.Value;
    }

    public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter)
    {
        var message = logEntry.Formatter(logEntry.State, logEntry.Exception);
        if (logEntry.Exception == null && message == null)
        {
            return;
        }

        JsonWriter writer = new JsonTextWriter(textWriter);
        writer.WriteStartObject();
        if (_options.TimestampFormat != null)
        {
            writer.WritePropertyName("Timestamp");
            var timestamp = _options.UseUtcTimestamp ? DateTimeOffset.UtcNow : DateTimeOffset.Now;
            var timestampText = timestamp.ToString(_options.TimestampFormat);
            writer.WriteValue(timestampText);
        }
        writer.WritePropertyName("Level");
        writer.WriteValue(logEntry.LogLevel.ToString());
        writer.WritePropertyName("EventId");
        writer.WriteValue(logEntry.EventId.ToString());
        writer.WritePropertyName(nameof(logEntry.Category));
        writer.WriteValue(logEntry.Category);
        writer.WritePropertyName("Message");
        writer.WriteValue(message);
        if (logEntry.Exception != null)
        {
            writer.WritePropertyName("Exception");
            writer.WriteValue(logEntry.Exception);
        }

        if (logEntry.State != null)
        {
            writer.WritePropertyName(nameof(logEntry.State));
            writer.WriteStartObject();
            writer.WritePropertyName("Message");
            writer.WriteValue(logEntry.State.ToString());
            if (logEntry.State is System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string, object>> stateProperties)
            {
                foreach (var item in stateProperties)
                {
                    writer.WritePropertyName(item.Key);
                    writer.WriteValue(item.Value);
                }
            }
            writer.WriteEndObject();
        }
        
        writer.WriteEndObject();
        writer.Flush();
        textWriter.WriteLine();
    }
}
formatter 的设计需要有一个 options 所以我们需要新加一个 NewtonJsonFormatterOptions 。为了使用起来方便,我们定义一个扩展方法 AddNewtonJsonConsole:
public static partial class LoggingBuilderExtensions
{
    public static ILoggingBuilder AddNewtonJsonConsole(this ILoggingBuilder loggingBuilder, 
        Action<NewtonJsonFormatterOptions>? optionsConfigure = null)
    {
        loggingBuilder.AddConsoleFormatter<NewtonJsonFormatter, NewtonJsonFormatterOptions>();
        loggingBuilder.AddConsole(options => options.FormatterName = NewtonJsonFormatter.FormatterName);
        if (optionsConfigure != null)
        {
            loggingBuilder.Services.Configure(optionsConfigure);
        }
        return loggingBuilder;
    }
}
首先我们需要通过 AddConsoleFormatter 来注册我们自定义的 NewtonJsonFormatter,然后使用 AddConsole 注册 console 服务并指定 FormatterName ,另外可以通过一个可选的委托来自定义一些配置

例子
来看一个简单的使用示例:
使用默认的 JsonConsole 时
var builder = Host.CreateEmptyApplicationBuilder(null);
builder.Logging.AddJsonConsole();
using var host = builder.Build();
host.Run();
输出结果如下:
Default JsonConsole
使用我们自定义的 NewtonJsonConsole 时:
var builder = Host.CreateEmptyApplicationBuilder(null);
builder.Logging.AddNewtonJsonConsole();
using var host = builder.Build();
host.Run();
可以看到使用我们基于 NewtonsoftJson 自定义的 NewtonJsonConsole 时,Ctrl+C 不会再被 encode 了,可以看到原始的日志,就不用再看到一脸懵了

总结
前面的实现仅供参考,仔细看的话会发现我们前面定义的 NewtonJsonConsole 并没有输出 OriginalFormat,并且没有处理 scope 的信息,实际使用的话还需要根据需要进行修改,实现仅供参考。

源码链接:https://github.com/WeihanLi/WeihanLi.Common/blob/dev/samples/DotNetCoreSample/NewtonJsonFormatter.cs

参考
https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs
https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerExtensions.cs
https://github.com/WeihanLi/WeihanLi.Common/blob/dev/samples/DotNetCoreSample/NewtonJsonFormatter.cs
用户评论