背景
.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