Skip to content

Commit

Permalink
Fixed support for nodatime time with destructuring (#1175)
Browse files Browse the repository at this point in the history
  • Loading branch information
david-driscoll authored Jun 30, 2022
1 parent 3347bd8 commit 681fd47
Show file tree
Hide file tree
Showing 18 changed files with 308 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static JsonSerializer ConfigureNodaTimeForLaunchPad(this JsonSerializer o
{
options.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy()));
options.ConfigureForNodaTime(dateTimeZoneProvider);
ReplaceConverters(options.Converters);
ReplaceConverters(options.Converters, dateTimeZoneProvider);
return options;
}

Expand All @@ -37,12 +37,12 @@ public static JsonSerializerSettings ConfigureNodaTimeForLaunchPad(this JsonSeri
{
options.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy()));
options.ConfigureForNodaTime(dateTimeZoneProvider);
ReplaceConverters(options.Converters);
ReplaceConverters(options.Converters, dateTimeZoneProvider);

return options;
}

private static void ReplaceConverters(IList<JsonConverter> converters)
private static void ReplaceConverters(IList<JsonConverter> converters, IDateTimeZoneProvider dateTimeZoneProvider)
{
ReplaceConverter(
converters,
Expand Down Expand Up @@ -127,7 +127,7 @@ private static void ReplaceConverters(IList<JsonConverter> converters)
ReplaceConverter(
converters,
new NewtonsoftJsonCompositeNodaPatternConverter<ZonedDateTime>(
ZonedDateTimePattern.CreateWithInvariantCulture("uuuu'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFFFFo<G> z", DateTimeZoneProviders.Tzdb)
ZonedDateTimePattern.CreateWithInvariantCulture("uuuu'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFFFFo<G> z", dateTimeZoneProvider)
)
);
}
Expand Down
13 changes: 10 additions & 3 deletions src/Foundation/Conventions/NodaTimeConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using Rocket.Surgery.Conventions;
using Rocket.Surgery.Conventions.DependencyInjection;
using Rocket.Surgery.LaunchPad.Foundation.Conventions;
using Rocket.Surgery.LaunchPad.Serilog;
using Serilog;

[assembly: Convention(typeof(NodaTimeConvention))]

Expand All @@ -16,7 +18,7 @@ namespace Rocket.Surgery.LaunchPad.Foundation.Conventions;
/// </summary>
/// <seealso cref="IServiceConvention" />
[PublicAPI]
public class NodaTimeConvention : IServiceConvention
public class NodaTimeConvention : IServiceConvention, ISerilogConvention
{
private readonly FoundationOptions _options;

Expand All @@ -29,6 +31,12 @@ public NodaTimeConvention(FoundationOptions? options = null)
_options = options ?? new FoundationOptions();
}

/// <inheritdoc />
public void Register(IConventionContext context, IServiceProvider services, IConfiguration configuration, LoggerConfiguration loggerConfiguration)
{
loggerConfiguration.Destructure.NodaTimeTypes(services.GetRequiredService<IDateTimeZoneProvider>());
}

/// <summary>
/// Registers the specified context.
/// </summary>
Expand All @@ -43,7 +51,6 @@ public void Register(IConventionContext context, IConfiguration configuration, I
}

services.TryAddSingleton<IClock>(SystemClock.Instance);
services.TryAddSingleton<IDateTimeZoneProvider, DateTimeZoneCache>();
services.TryAddSingleton(_options.DateTimeZoneSource);
services.TryAddSingleton<IDateTimeZoneProvider>(new DateTimeZoneCache(_options.DateTimeZoneSource));
}
}
107 changes: 107 additions & 0 deletions src/Foundation/NodaTimeDestructuringPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System.Text.Json;
using NodaTime;
using NodaTime.Text;
using Serilog.Core;
using Serilog.Events;

namespace Rocket.Surgery.LaunchPad.Foundation;

internal class NodaTimeDestructuringPolicy : IDestructuringPolicy
{
private readonly ZonedDateTimePattern _zonedDateTimePattern;

public NodaTimeDestructuringPolicy(IDateTimeZoneProvider provider)
{
_zonedDateTimePattern = ZonedDateTimePattern.CreateWithInvariantCulture("uuuu'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFFFFo<G> z", provider);
}

public bool TryDestructure(object value, ILogEventPropertyValueFactory _, out LogEventPropertyValue? result)
{
ScalarValue a;
if (value is Instant instant1)
{
result = new ScalarValue(InstantPattern.ExtendedIso.Format(instant1));
return true;
}

if (value is LocalDateTime localDateTime)
{
result = new ScalarValue(LocalDateTimePattern.ExtendedIso.Format(localDateTime));
return true;
}

if (value is LocalDate localDate)
{
result = new ScalarValue(LocalDatePattern.Iso.Format(localDate));
return true;
}

if (value is LocalTime localTime)
{
result = new ScalarValue(LocalTimePattern.ExtendedIso.Format(localTime));
return true;
}

if (value is OffsetDateTime offsetDateTime)
{
result = new ScalarValue(OffsetDateTimePattern.ExtendedIso.Format(offsetDateTime));
return true;
}

if (value is OffsetDate offsetDate)
{
result = new ScalarValue(OffsetDatePattern.GeneralIso.Format(offsetDate));
return true;
}

if (value is OffsetTime offsetTime)
{
result = new ScalarValue(OffsetTimePattern.ExtendedIso.Format(offsetTime));
return true;
}

if (value is ZonedDateTime zonedDateTime)
{
result = new ScalarValue(_zonedDateTimePattern.Format(zonedDateTime));
return true;
}

if (value is DateTimeZone dateTimeZone)
{
result = new ScalarValue(dateTimeZone.Id);
return true;
}

if (value is Duration duration)
{
result = new ScalarValue(DurationPattern.Roundtrip.Format(duration));
return true;
}

if (value is Period period)
{
result = new ScalarValue(PeriodPattern.NormalizingIso.Format(period));
return true;
}

if (value is Interval interval)
{
var values = new List<LogEventProperty>();
if (interval.HasStart)
{
values.Add(new LogEventProperty("Start", _.CreatePropertyValue(interval.Start)));
}

if (interval.HasEnd)
{
values.Add(new LogEventProperty("End", _.CreatePropertyValue(interval.End)));
}

result = new StructureValue(values);
return true;
}

result = null;
return false;
}
}
25 changes: 25 additions & 0 deletions src/Foundation/NodaTimeLoggerConfigurationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using NodaTime;
using Serilog;
using Serilog.Configuration;

namespace Rocket.Surgery.LaunchPad.Foundation;

/// <summary>
/// Adds the Destructure.NewtonsoftJsonTypes() extension to <see cref="LoggerConfiguration" />.
/// </summary>
public static class NodaTimeLoggerConfigurationExtensions
{
/// <summary>
/// Enable destructuring of JSON.NET dynamic objects.
/// </summary>
/// <param name="configuration">The logger configuration to apply configuration to.</param>
/// <param name="provider"></param>
/// <returns>An object allowing configuration to continue.</returns>
public static LoggerConfiguration NodaTimeTypes(this LoggerDestructuringConfiguration configuration, IDateTimeZoneProvider provider)
{
return configuration
.AsScalar<Offset>().Destructure
.AsScalar<CalendarSystem>().Destructure
.With(new NodaTimeDestructuringPolicy(provider));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public static JsonSerializerOptions ConfigureNodaTimeForLaunchPad(this JsonSeria
ReplaceConverter(
options.Converters,
new SystemTextJsonCompositeNodaPatternConverter<ZonedDateTime>(
ZonedDateTimePattern.CreateWithInvariantCulture("uuuu'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFFFFo<G> z", DateTimeZoneProviders.Tzdb)
ZonedDateTimePattern.CreateWithInvariantCulture("uuuu'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFFFFo<G> z", dateTimeZoneProvider)
)
);

Expand Down
128 changes: 125 additions & 3 deletions test/Extensions.Tests/SerilogDestructuringTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using NetTopologySuite.Features;
using NetTopologySuite.Geometries;
using Newtonsoft.Json.Linq;
using NodaTime;
using NodaTime.Testing;
using Rocket.Surgery.Extensions.Testing;
using Rocket.Surgery.LaunchPad.Foundation;
using Rocket.Surgery.LaunchPad.Spatial;
Expand Down Expand Up @@ -89,19 +91,139 @@ public async Task Should_Destructure_NetTopologySuite_AttributesTable()
await Verify(logs.Select(z => z.RenderMessage()));
}

[Fact]
public async Task Should_Destructure_NodaTime_Instant()
{
var faker = new Faker { Random = new Randomizer(17) };

using var _ = CaptureLogs(out var logs);
Logger.LogInformation("This is just a test {@Data}", _clock.GetCurrentInstant());
await Verify(logs.Select(z => z.RenderMessage()));
}

[Fact]
public async Task Should_Destructure_NodaTime_LocalDateTime()
{
var faker = new Faker { Random = new Randomizer(17) };

using var _ = CaptureLogs(out var logs);
Logger.LogInformation("This is just a test {@Data}", LocalDateTime.FromDateTime(_clock.GetCurrentInstant().ToDateTimeUtc()));
await Verify(logs.Select(z => z.RenderMessage()));
}

[Fact]
public async Task Should_Destructure_NodaTime_LocalDate()
{
var faker = new Faker { Random = new Randomizer(17) };

using var _ = CaptureLogs(out var logs);
Logger.LogInformation("This is just a test {@Data}", LocalDate.FromDateTime(_clock.GetCurrentInstant().ToDateTimeUtc()));
await Verify(logs.Select(z => z.RenderMessage()));
}

[Fact]
public async Task Should_Destructure_NodaTime_LocalTime()
{
var faker = new Faker { Random = new Randomizer(17) };

using var _ = CaptureLogs(out var logs);
Logger.LogInformation("This is just a test {@Data}", LocalTime.FromHoursSinceMidnight(4));
await Verify(logs.Select(z => z.RenderMessage()));
}

[Fact]
public async Task Should_Destructure_NodaTime_OffsetDateTime()
{
var faker = new Faker { Random = new Randomizer(17) };

using var _ = CaptureLogs(out var logs);
Logger.LogInformation("This is just a test {@Data}", OffsetDateTime.FromDateTimeOffset(_clock.GetCurrentInstant().ToDateTimeOffset()));
await Verify(logs.Select(z => z.RenderMessage()));
}

[Fact]
public async Task Should_Destructure_NodaTime_OffsetDate()
{
var faker = new Faker { Random = new Randomizer(17) };

using var _ = CaptureLogs(out var logs);
Logger.LogInformation("This is just a test {@Data}", OffsetDateTime.FromDateTimeOffset(_clock.GetCurrentInstant().ToDateTimeOffset()).ToOffsetDate());
await Verify(logs.Select(z => z.RenderMessage()));
}

[Fact]
public async Task Should_Destructure_NodaTime_OffsetTime()
{
var faker = new Faker { Random = new Randomizer(17) };

using var _ = CaptureLogs(out var logs);
Logger.LogInformation("This is just a test {@Data}", OffsetDateTime.FromDateTimeOffset(_clock.GetCurrentInstant().ToDateTimeOffset()).ToOffsetTime());
await Verify(logs.Select(z => z.RenderMessage()));
}

[Fact]
public async Task Should_Destructure_NodaTime_ZonedDateTime()
{
var faker = new Faker { Random = new Randomizer(17) };

using var _ = CaptureLogs(out var logs);
Logger.LogInformation("This is just a test {@Data}", ZonedDateTime.FromDateTimeOffset(_clock.GetCurrentInstant().ToDateTimeOffset()));
await Verify(logs.Select(z => z.RenderMessage()));
}

[Fact]
public async Task Should_Destructure_NodaTime_DateTimeZone()
{
var faker = new Faker { Random = new Randomizer(17) };

using var _ = CaptureLogs(out var logs);
Logger.LogInformation("This is just a test {@Data}", DateTimeZoneProviders.Tzdb["America/New_York"]);
await Verify(logs.Select(z => z.RenderMessage()));
}

[Fact]
public async Task Should_Destructure_NodaTime_Duration()
{
var faker = new Faker { Random = new Randomizer(17) };

using var _ = CaptureLogs(out var logs);
Logger.LogInformation("This is just a test {@Data}", Duration.FromDays(1));
await Verify(logs.Select(z => z.RenderMessage()));
}

[Fact]
public async Task Should_Destructure_NodaTime_Period()
{
var faker = new Faker { Random = new Randomizer(17) };

using var _ = CaptureLogs(out var logs);
Logger.LogInformation("This is just a test {@Data}", Period.FromDays(1));
await Verify(logs.Select(z => z.RenderMessage()));
}

[Fact]
public async Task Should_Destructure_NodaTime_Interval()
{
var faker = new Faker { Random = new Randomizer(17) };

using var _ = CaptureLogs(out var logs);
Logger.LogInformation("This is just a test {@Data}", new Interval(_clock.GetCurrentInstant(), _clock.GetCurrentInstant()));
await Verify(logs.Select(z => z.RenderMessage()));
}

public SerilogDestructuringTests(ITestOutputHelper outputHelper) : base(
outputHelper, LogLevel.Information, configureLogger: configuration => configuration
.Destructure.NewtonsoftJsonTypes()
.Destructure.SystemTextJsonTypes()
.Destructure.NetTopologySuiteTypes()
.Destructure.NodaTimeTypes(DateTimeZoneProviders.Tzdb)
)
{
_outputHelper = outputHelper;

LogContext.PushProperty("SourceContext", nameof(SerilogDestructuringTests));
_clock = new FakeClock(Instant.FromUtc(2022, 1, 1, 4, 4, 4));
}

private readonly ITestOutputHelper _outputHelper;
private readonly FakeClock _clock;

[Theory]
[InlineData(OgcGeometryType.Point, 5, false)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
This is just a test "America/New_York"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
This is just a test "1:00:00:00"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
This is just a test "2022-01-01T04:04:04Z"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
This is just a test { Start: "2022-01-01T04:04:04Z", End: "2022-01-01T04:04:04Z" }
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
This is just a test "2022-01-01"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
This is just a test "2022-01-01T04:04:04"
]
Loading

0 comments on commit 681fd47

Please sign in to comment.