From ee9314c6e6252b6384e04697d30b541c4157b6f4 Mon Sep 17 00:00:00 2001 From: Alex Martossy Date: Mon, 26 Aug 2024 15:27:10 +0200 Subject: [PATCH] Add UTF-8 SpanFormattable support (#83) * Add UTF-8 SpanFormattable support * Fix default timespan format * Bump net48 to net481 for tests and benchmarks * Update benchmark --- .changeset/new-seas-enjoy.md | 5 +++ Directory.Build.props | 2 +- README.md | 2 +- .../Utf8JsonFormatter.cs | 40 +++++++++++++++++-- .../README.md | 6 ++- ...log.Extensions.Formatting.Benchmark.csproj | 2 +- .../packages.lock.json | 4 +- .../Serilog.Extensions.Formatting.Test.csproj | 2 +- .../SerilogJsonFormatterTests.cs | 2 +- .../packages.lock.json | 2 +- 10 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 .changeset/new-seas-enjoy.md diff --git a/.changeset/new-seas-enjoy.md b/.changeset/new-seas-enjoy.md new file mode 100644 index 0000000..a6197bc --- /dev/null +++ b/.changeset/new-seas-enjoy.md @@ -0,0 +1,5 @@ +--- +"alexaka1.serilog.extensions.formatting": minor +--- + +Add IUtf8SpanFormattable support as fallback option diff --git a/Directory.Build.props b/Directory.Build.props index 6d2c547..aedb937 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -31,6 +31,6 @@ $(DefineConstants);FEATURE_SPAN;FEATURE_DATE_AND_TIME_ONLY;FEATURE_ISPANFORMATTABLE; - $(DefineConstants);FEATURE_SPAN;FEATURE_DATE_AND_TIME_ONLY;FEATURE_ISPANFORMATTABLE;FEATURE_JSON_NAMING_POLICY; + $(DefineConstants);FEATURE_SPAN;FEATURE_DATE_AND_TIME_ONLY;FEATURE_ISPANFORMATTABLE;FEATURE_JSON_NAMING_POLICY;FEATURE_IUTF8SPANFORMATTABLE; diff --git a/README.md b/README.md index daaf49f..010d427 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Utf8JsonFormatter -A simple JSON formatter for Serilog that uses the `System.Text.Json.Utf8JsonWriter` to write the log events to the output stream. +A simple JSON formatter for Serilog that uses the `System.Text.Json.Utf8JsonWriter` to write the log events to the output stream. As the name suggests, it is entirely built around UTF-8, with all the [.NET optimizations for UTF-8](https://github.com/dotnet/runtime/issues/81500), so using other encodings will most likely result in invalid characters. The default for the File sink is UTF-8. ### Usage diff --git a/src/Serilog.Extensions.Formatting/Utf8JsonFormatter.cs b/src/Serilog.Extensions.Formatting/Utf8JsonFormatter.cs index 2247ea2..ac362a3 100644 --- a/src/Serilog.Extensions.Formatting/Utf8JsonFormatter.cs +++ b/src/Serilog.Extensions.Formatting/Utf8JsonFormatter.cs @@ -30,6 +30,7 @@ public class Utf8JsonFormatter : ITextFormatter, IDisposable, IAsyncDisposable private readonly StringWriter _sw; private readonly Utf8JsonWriter _writer; private const string TimeFormat = "O"; + private const string TimeSpanFormat = "c"; #if FEATURE_DATE_AND_TIME_ONLY private const string DateOnlyFormat = "O"; #endif @@ -339,15 +340,23 @@ private void VisitScalarValue(ScalarValue value) break; case TimeSpan timeSpan: { -#if FEATURE_ISPANFORMATTABLE +#if FEATURE_IUTF8SPANFORMATTABLE + Span buffer = stackalloc byte[_spanBufferSize]; + if (timeSpan.TryFormat(buffer, out int written, formatProvider: _formatProvider, + format: TimeSpanFormat)) + { + // fallback to string + _writer.WriteStringValue(Encoding.UTF8.GetString(buffer.Slice(0, written))); + } +#elif FEATURE_ISPANFORMATTABLE Span buffer = stackalloc char[_spanBufferSize]; if (timeSpan.TryFormat(buffer, out int written, formatProvider: _formatProvider, - format: "c")) + format: TimeSpanFormat)) { _writer.WriteStringValue(buffer.Slice(0, written)); } #else - _writer.WriteStringValue(timeSpan.ToString("c", _formatProvider)); + _writer.WriteStringValue(timeSpan.ToString(TimeSpanFormat, _formatProvider)); #endif break; @@ -387,6 +396,18 @@ private void VisitScalarValue(ScalarValue value) { _writer.WriteStringValue(vt.ToString()); } +#if FEATURE_IUTF8SPANFORMATTABLE + else if (vt is IUtf8SpanFormattable utf8Span) + { + Span buffer = stackalloc byte[_spanBufferSize * 2]; + if (utf8Span.TryFormat(buffer, out int written, provider: _formatProvider, + format: default)) + { + // fallback to string + _writer.WriteStringValue(Encoding.UTF8.GetString(buffer.Slice(0, written))); + } + } +#endif #if FEATURE_ISPANFORMATTABLE else if (vt is ISpanFormattable span) { @@ -404,6 +425,19 @@ private void VisitScalarValue(ScalarValue value) } break; +#if FEATURE_IUTF8SPANFORMATTABLE + case IUtf8SpanFormattable span: + { + Span buffer = stackalloc byte[_spanBufferSize * 4]; + if (span.TryFormat(buffer, out int written, provider: _formatProvider, format: default)) + { + // fallback to string + _writer.WriteStringValue(buffer.Slice(0, written)); + } + + break; + } +#endif #if FEATURE_ISPANFORMATTABLE case ISpanFormattable span: { diff --git a/test/Serilog.Extensions.Formatting.Benchmark/README.md b/test/Serilog.Extensions.Formatting.Benchmark/README.md index 7c42966..2843a6e 100644 --- a/test/Serilog.Extensions.Formatting.Benchmark/README.md +++ b/test/Serilog.Extensions.Formatting.Benchmark/README.md @@ -1,3 +1,7 @@ ```shell -dotnet run -c Release -f net8.0 -- -f * --r net8.0 net6.0 net48 +dotnet run -c Release -f net8.0 -- -f * -r net481 net6.0 --join +``` + +```shell +dotnet run -c Release -f net481 -- -f * -r net481 --join ``` diff --git a/test/Serilog.Extensions.Formatting.Benchmark/Serilog.Extensions.Formatting.Benchmark.csproj b/test/Serilog.Extensions.Formatting.Benchmark/Serilog.Extensions.Formatting.Benchmark.csproj index 168cb41..90cea88 100644 --- a/test/Serilog.Extensions.Formatting.Benchmark/Serilog.Extensions.Formatting.Benchmark.csproj +++ b/test/Serilog.Extensions.Formatting.Benchmark/Serilog.Extensions.Formatting.Benchmark.csproj @@ -1,7 +1,7 @@  - net8.0;net6.0;net48;net472 + net8.0;net6.0;net481;net472 Exe false false diff --git a/test/Serilog.Extensions.Formatting.Benchmark/packages.lock.json b/test/Serilog.Extensions.Formatting.Benchmark/packages.lock.json index 7143bf9..b90a67e 100644 --- a/test/Serilog.Extensions.Formatting.Benchmark/packages.lock.json +++ b/test/Serilog.Extensions.Formatting.Benchmark/packages.lock.json @@ -469,7 +469,7 @@ } } }, - ".NETFramework,Version=v4.8": { + ".NETFramework,Version=v4.8.1": { "BenchmarkDotNet": { "type": "Direct", "requested": "[0.14.0, )", @@ -890,7 +890,7 @@ } } }, - ".NETFramework,Version=v4.8/win7-x86": { + ".NETFramework,Version=v4.8.1/win7-x86": { "Gee.External.Capstone": { "type": "Transitive", "resolved": "2.3.0", diff --git a/test/Serilog.Extensions.Formatting.Test/Serilog.Extensions.Formatting.Test.csproj b/test/Serilog.Extensions.Formatting.Test/Serilog.Extensions.Formatting.Test.csproj index d48a15a..6885018 100644 --- a/test/Serilog.Extensions.Formatting.Test/Serilog.Extensions.Formatting.Test.csproj +++ b/test/Serilog.Extensions.Formatting.Test/Serilog.Extensions.Formatting.Test.csproj @@ -1,7 +1,7 @@ - net8.0;net6.0;net48;net472 + net8.0;net6.0;net481;net472 false true false diff --git a/test/Serilog.Extensions.Formatting.Test/SerilogJsonFormatterTests.cs b/test/Serilog.Extensions.Formatting.Test/SerilogJsonFormatterTests.cs index ec14bd3..f17c733 100644 --- a/test/Serilog.Extensions.Formatting.Test/SerilogJsonFormatterTests.cs +++ b/test/Serilog.Extensions.Formatting.Test/SerilogJsonFormatterTests.cs @@ -634,7 +634,7 @@ public void AnISpanFormattablePropertySerializesAsStringValue() public void AnISpanFormattablePropertySerializesAsStringValueNet6() { string name = Some.String(); - var value = Version.Parse("1.2.3.4"); + var value = new Uri("https://www.google.com"); var @event = Some.InformationEvent(); @event.AddOrUpdateProperty(new LogEventProperty(name, new ScalarValue(value))); diff --git a/test/Serilog.Extensions.Formatting.Test/packages.lock.json b/test/Serilog.Extensions.Formatting.Test/packages.lock.json index 58e382c..a777076 100644 --- a/test/Serilog.Extensions.Formatting.Test/packages.lock.json +++ b/test/Serilog.Extensions.Formatting.Test/packages.lock.json @@ -257,7 +257,7 @@ } } }, - ".NETFramework,Version=v4.8": { + ".NETFramework,Version=v4.8.1": { "coverlet.collector": { "type": "Direct", "requested": "[6.0.2, )",