Skip to content

Commit

Permalink
Fix crash on operations requests when trace logging is turned on
Browse files Browse the repository at this point in the history
  • Loading branch information
bkoelman committed Nov 16, 2023
1 parent 24b9546 commit ba430da
Show file tree
Hide file tree
Showing 10 changed files with 485 additions and 62 deletions.
68 changes: 66 additions & 2 deletions src/JsonApiDotNetCore/Middleware/TraceLogWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCore.Middleware;
Expand All @@ -14,8 +17,69 @@ internal abstract class TraceLogWriter
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
ReferenceHandler = ReferenceHandler.Preserve
ReferenceHandler = ReferenceHandler.IgnoreCycles,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters =
{
new JsonStringEnumConverter(),
new ResourceTypeInTraceJsonConverter(),
new ResourceFieldInTraceJsonConverterFactory(),
new IdentifiableInTraceJsonConverter()
}
};

private sealed class ResourceTypeInTraceJsonConverter : JsonConverter<ResourceType>
{
public override ResourceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotSupportedException();
}

public override void Write(Utf8JsonWriter writer, ResourceType value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.PublicName);
}
}

private sealed class ResourceFieldInTraceJsonConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert.IsAssignableTo(typeof(ResourceFieldAttribute));
}

public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
return new ResourceFieldInTraceJsonConverter();
}

private sealed class ResourceFieldInTraceJsonConverter : JsonConverter<ResourceFieldAttribute>
{
public override ResourceFieldAttribute Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotSupportedException();
}

public override void Write(Utf8JsonWriter writer, ResourceFieldAttribute value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.PublicName);
}
}
}

private sealed class IdentifiableInTraceJsonConverter : JsonConverter<IIdentifiable>
{
public override IIdentifiable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotSupportedException();
}

public override void Write(Utf8JsonWriter writer, IIdentifiable value, JsonSerializerOptions options)
{
Type runtimeType = value.GetType();
JsonSerializer.Serialize(writer, value, runtimeType, options);
}
}
}

internal sealed class TraceLogWriter<T> : TraceLogWriter
Expand Down Expand Up @@ -139,7 +203,7 @@ private static string SerializeObject(object value)
{
return JsonSerializer.Serialize(value, SerializerOptions);
}
catch (JsonException)
catch (Exception exception) when (exception is JsonException or NotSupportedException)
{
// Never crash as a result of logging, this is best-effort only.
return "object";
Expand Down
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/Queries/QueryLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ internal void WriteLayer(IndentingStringWriter writer, string? prefix)

using (writer.Indent())
{
if (Include != null)
if (Include != null && Include.Elements.Any())
{
writer.WriteLine($"{nameof(Include)}: {Include}");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ public async Task Logs_at_error_level_on_unhandled_exception()
error.Source.ShouldNotBeNull();
error.Source.Pointer.Should().Be("/atomic:operations[0]");

loggerFactory.Logger.Messages.ShouldNotBeEmpty();
IReadOnlyList<FakeLogMessage> logMessages = loggerFactory.Logger.GetMessages();
logMessages.ShouldNotBeEmpty();

loggerFactory.Logger.Messages.Should().ContainSingle(message => message.LogLevel == LogLevel.Error &&
logMessages.Should().ContainSingle(message => message.LogLevel == LogLevel.Error &&
message.Text.Contains("Simulated failure.", StringComparison.Ordinal));
}

Expand Down Expand Up @@ -117,9 +118,10 @@ public async Task Logs_at_info_level_on_invalid_request_body()

responseDocument.Errors.ShouldHaveCount(1);

loggerFactory.Logger.Messages.ShouldNotBeEmpty();
IReadOnlyList<FakeLogMessage> logMessages = loggerFactory.Logger.GetMessages();
logMessages.ShouldNotBeEmpty();

loggerFactory.Logger.Messages.Should().ContainSingle(message => message.LogLevel == LogLevel.Information &&
logMessages.Should().ContainSingle(message => message.LogLevel == LogLevel.Information &&
message.Text.Contains("Failed to deserialize request body", StringComparison.Ordinal));
}

Expand Down
Loading

0 comments on commit ba430da

Please sign in to comment.