Skip to content

Commit a7b41dd

Browse files
committed
Add trace logging for component schema generation
1 parent 5d49ecb commit a7b41dd

File tree

53 files changed

+478
-93
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+478
-93
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace JsonApiDotNetCore.OpenApi.Swashbuckle;
2+
3+
internal interface ISchemaGenerationTraceScope : IDisposable
4+
{
5+
void TraceSucceeded(string schemaId);
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
using JsonApiDotNetCore.Resources.Annotations;
2+
using JsonApiDotNetCore.Serialization.Objects;
3+
using Microsoft.Extensions.Logging;
4+
5+
namespace JsonApiDotNetCore.OpenApi.Swashbuckle;
6+
7+
/// <summary>
8+
/// Enables to log recursive component schema generation at trace level.
9+
/// </summary>
10+
internal sealed partial class SchemaGenerationTracer
11+
{
12+
private readonly ILoggerFactory _loggerFactory;
13+
14+
public SchemaGenerationTracer(ILoggerFactory loggerFactory)
15+
{
16+
ArgumentNullException.ThrowIfNull(loggerFactory);
17+
18+
_loggerFactory = loggerFactory;
19+
}
20+
21+
public ISchemaGenerationTraceScope TraceStart(object generator)
22+
{
23+
ArgumentNullException.ThrowIfNull(generator);
24+
25+
return InnerTraceStart(generator, () => "(none)");
26+
}
27+
28+
public ISchemaGenerationTraceScope TraceStart(object generator, Type schemaType)
29+
{
30+
ArgumentNullException.ThrowIfNull(generator);
31+
ArgumentNullException.ThrowIfNull(schemaType);
32+
33+
return InnerTraceStart(generator, () => GetSchemaTypeName(schemaType));
34+
}
35+
36+
public ISchemaGenerationTraceScope TraceStart(object generator, AtomicOperationCode operationCode)
37+
{
38+
ArgumentNullException.ThrowIfNull(generator);
39+
40+
return InnerTraceStart(generator, () => $"{nameof(AtomicOperationCode)}.{operationCode}");
41+
}
42+
43+
public ISchemaGenerationTraceScope TraceStart(object generator, RelationshipAttribute relationship)
44+
{
45+
ArgumentNullException.ThrowIfNull(generator);
46+
ArgumentNullException.ThrowIfNull(relationship);
47+
48+
return InnerTraceStart(generator,
49+
() => $"{GetSchemaTypeName(relationship.GetType())}({GetSchemaTypeName(relationship.LeftType.ClrType)}.{relationship.Property.Name})");
50+
}
51+
52+
public ISchemaGenerationTraceScope TraceStart(object generator, Type schemaOpenType, RelationshipAttribute relationship)
53+
{
54+
ArgumentNullException.ThrowIfNull(generator);
55+
ArgumentNullException.ThrowIfNull(schemaOpenType);
56+
ArgumentNullException.ThrowIfNull(relationship);
57+
58+
return InnerTraceStart(generator,
59+
() =>
60+
$"{GetSchemaTypeName(schemaOpenType)} with {GetSchemaTypeName(relationship.GetType())}({GetSchemaTypeName(relationship.LeftType.ClrType)}.{relationship.Property.Name})");
61+
}
62+
63+
private ISchemaGenerationTraceScope InnerTraceStart(object generator, Func<string> getSchemaTypeName)
64+
{
65+
ILogger logger = _loggerFactory.CreateLogger(generator.GetType());
66+
67+
if (logger.IsEnabled(LogLevel.Trace))
68+
{
69+
string schemaTypeName = getSchemaTypeName();
70+
return new SchemaGenerationTraceScope(logger, schemaTypeName);
71+
}
72+
73+
return DisabledSchemaGenerationTraceScope.Instance;
74+
}
75+
76+
private static string GetSchemaTypeName(Type type)
77+
{
78+
if (type.IsConstructedGenericType)
79+
{
80+
string typeArguments = string.Join(',', type.GetGenericArguments().Select(GetSchemaTypeName));
81+
int arityIndex = type.Name.IndexOf('`');
82+
return $"{type.Name[..arityIndex]}<{typeArguments}>";
83+
}
84+
85+
return type.Name;
86+
}
87+
88+
private sealed partial class SchemaGenerationTraceScope : ISchemaGenerationTraceScope
89+
{
90+
private static readonly AsyncLocal<int> RecursionDepthAsyncLocal = new();
91+
92+
private readonly ILogger _logger;
93+
private readonly string _schemaTypeName;
94+
private string? _schemaId;
95+
96+
public SchemaGenerationTraceScope(ILogger logger, string schemaTypeName)
97+
{
98+
ArgumentNullException.ThrowIfNull(logger);
99+
ArgumentNullException.ThrowIfNull(schemaTypeName);
100+
101+
_logger = logger;
102+
_schemaTypeName = schemaTypeName;
103+
104+
RecursionDepthAsyncLocal.Value++;
105+
LogStarted(RecursionDepthAsyncLocal.Value, _schemaTypeName);
106+
}
107+
108+
public void TraceSucceeded(string schemaId)
109+
{
110+
_schemaId = schemaId;
111+
}
112+
113+
public void Dispose()
114+
{
115+
if (_schemaId != null)
116+
{
117+
LogSucceeded(RecursionDepthAsyncLocal.Value, _schemaTypeName, _schemaId);
118+
}
119+
else
120+
{
121+
LogFailed(RecursionDepthAsyncLocal.Value, _schemaTypeName);
122+
}
123+
124+
RecursionDepthAsyncLocal.Value--;
125+
}
126+
127+
[LoggerMessage(Level = LogLevel.Trace, SkipEnabledCheck = true, Message = "({Depth:D2}) Started for {SchemaTypeName}.")]
128+
private partial void LogStarted(int depth, string schemaTypeName);
129+
130+
[LoggerMessage(Level = LogLevel.Trace, SkipEnabledCheck = true, Message = "({Depth:D2}) Generated '{SchemaId}' from {SchemaTypeName}.")]
131+
private partial void LogSucceeded(int depth, string schemaTypeName, string schemaId);
132+
133+
[LoggerMessage(Level = LogLevel.Trace, SkipEnabledCheck = true, Message = "({Depth:D2}) Failed for {SchemaTypeName}.")]
134+
private partial void LogFailed(int depth, string schemaTypeName);
135+
}
136+
137+
private sealed class DisabledSchemaGenerationTraceScope : ISchemaGenerationTraceScope
138+
{
139+
public static DisabledSchemaGenerationTraceScope Instance { get; } = new();
140+
141+
private DisabledSchemaGenerationTraceScope()
142+
{
143+
}
144+
145+
public void TraceSucceeded(string schemaId)
146+
{
147+
}
148+
149+
public void Dispose()
150+
{
151+
}
152+
}
153+
}

Diff for: src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/AtomicOperationCodeSchemaGenerator.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components;
77

88
internal sealed class AtomicOperationCodeSchemaGenerator
99
{
10+
private readonly SchemaGenerationTracer _schemaGenerationTracer;
1011
private readonly JsonApiSchemaIdSelector _schemaIdSelector;
1112

12-
public AtomicOperationCodeSchemaGenerator(JsonApiSchemaIdSelector schemaIdSelector)
13+
public AtomicOperationCodeSchemaGenerator(SchemaGenerationTracer schemaGenerationTracer, JsonApiSchemaIdSelector schemaIdSelector)
1314
{
15+
ArgumentNullException.ThrowIfNull(schemaGenerationTracer);
1416
ArgumentNullException.ThrowIfNull(schemaIdSelector);
1517

18+
_schemaGenerationTracer = schemaGenerationTracer;
1619
_schemaIdSelector = schemaIdSelector;
1720
}
1821

@@ -34,6 +37,8 @@ public OpenApiSchema GenerateSchema(AtomicOperationCode operationCode, SchemaRep
3437
};
3538
}
3639

40+
using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, operationCode);
41+
3742
string enumValue = operationCode.ToString().ToLowerInvariant();
3843

3944
var fullSchema = new OpenApiSchema
@@ -42,6 +47,9 @@ public OpenApiSchema GenerateSchema(AtomicOperationCode operationCode, SchemaRep
4247
Enum = [new OpenApiString(enumValue)]
4348
};
4449

45-
return schemaRepository.AddDefinition(schemaId, fullSchema);
50+
OpenApiSchema referenceSchema = schemaRepository.AddDefinition(schemaId, fullSchema);
51+
52+
traceScope.TraceSucceeded(schemaId);
53+
return referenceSchema;
4654
}
4755
}

Diff for: src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/DataContainerSchemaGenerator.cs

+14-2
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@ namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components;
1212
/// </summary>
1313
internal sealed class DataContainerSchemaGenerator
1414
{
15+
private readonly SchemaGenerationTracer _schemaGenerationTracer;
1516
private readonly DataSchemaGenerator _dataSchemaGenerator;
1617
private readonly IResourceGraph _resourceGraph;
1718

18-
public DataContainerSchemaGenerator(DataSchemaGenerator dataSchemaGenerator, IResourceGraph resourceGraph)
19+
public DataContainerSchemaGenerator(SchemaGenerationTracer schemaGenerationTracer, DataSchemaGenerator dataSchemaGenerator, IResourceGraph resourceGraph)
1920
{
21+
ArgumentNullException.ThrowIfNull(schemaGenerationTracer);
2022
ArgumentNullException.ThrowIfNull(dataSchemaGenerator);
2123
ArgumentNullException.ThrowIfNull(resourceGraph);
2224

25+
_schemaGenerationTracer = schemaGenerationTracer;
2326
_dataSchemaGenerator = dataSchemaGenerator;
2427
_resourceGraph = resourceGraph;
2528
}
@@ -45,6 +48,13 @@ public OpenApiSchema GenerateSchema(Type dataContainerSchemaType, ResourceType r
4548

4649
Type dataConstructedType = GetElementTypeOfDataProperty(dataContainerSchemaType, resourceType);
4750

51+
if (schemaRepository.TryLookupByType(dataConstructedType, out _))
52+
{
53+
return referenceSchemaForData;
54+
}
55+
56+
using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, dataConstructedType);
57+
4858
if (canIncludeRelated)
4959
{
5060
var resourceSchemaType = ResourceSchemaType.Create(dataConstructedType, _resourceGraph);
@@ -57,7 +67,9 @@ public OpenApiSchema GenerateSchema(Type dataContainerSchemaType, ResourceType r
5767
}
5868
}
5969

60-
return _dataSchemaGenerator.GenerateSchema(dataConstructedType, forRequestSchema, schemaRepository);
70+
referenceSchemaForData = _dataSchemaGenerator.GenerateSchema(dataConstructedType, forRequestSchema, schemaRepository);
71+
traceScope.TraceSucceeded(referenceSchemaForData.Reference.Id);
72+
return referenceSchemaForData;
6173
}
6274

6375
private static Type GetElementTypeOfDataProperty(Type dataContainerConstructedType, ResourceType resourceType)

Diff for: src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/DataSchemaGenerator.cs

+23-6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ internal sealed class DataSchemaGenerator
2929
JsonApiPropertyName.Meta
3030
];
3131

32+
private readonly SchemaGenerationTracer _schemaGenerationTracer;
3233
private readonly SchemaGenerator _defaultSchemaGenerator;
3334
private readonly GenerationCacheSchemaGenerator _generationCacheSchemaGenerator;
3435
private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator;
@@ -42,12 +43,14 @@ internal sealed class DataSchemaGenerator
4243
private readonly RelationshipTypeFactory _relationshipTypeFactory;
4344
private readonly ResourceDocumentationReader _resourceDocumentationReader;
4445

45-
public DataSchemaGenerator(SchemaGenerator defaultSchemaGenerator, GenerationCacheSchemaGenerator generationCacheSchemaGenerator,
46-
ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, ResourceIdSchemaGenerator resourceIdSchemaGenerator,
47-
LinksVisibilitySchemaGenerator linksVisibilitySchemaGenerator, MetaSchemaGenerator metaSchemaGenerator, JsonApiSchemaIdSelector schemaIdSelector,
48-
IJsonApiOptions options, IResourceGraph resourceGraph, ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider,
49-
RelationshipTypeFactory relationshipTypeFactory, ResourceDocumentationReader resourceDocumentationReader)
46+
public DataSchemaGenerator(SchemaGenerationTracer schemaGenerationTracer, SchemaGenerator defaultSchemaGenerator,
47+
GenerationCacheSchemaGenerator generationCacheSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator,
48+
ResourceIdSchemaGenerator resourceIdSchemaGenerator, LinksVisibilitySchemaGenerator linksVisibilitySchemaGenerator,
49+
MetaSchemaGenerator metaSchemaGenerator, JsonApiSchemaIdSelector schemaIdSelector, IJsonApiOptions options, IResourceGraph resourceGraph,
50+
ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider, RelationshipTypeFactory relationshipTypeFactory,
51+
ResourceDocumentationReader resourceDocumentationReader)
5052
{
53+
ArgumentNullException.ThrowIfNull(schemaGenerationTracer);
5154
ArgumentNullException.ThrowIfNull(defaultSchemaGenerator);
5255
ArgumentNullException.ThrowIfNull(generationCacheSchemaGenerator);
5356
ArgumentNullException.ThrowIfNull(resourceTypeSchemaGenerator);
@@ -61,6 +64,7 @@ public DataSchemaGenerator(SchemaGenerator defaultSchemaGenerator, GenerationCac
6164
ArgumentNullException.ThrowIfNull(relationshipTypeFactory);
6265
ArgumentNullException.ThrowIfNull(resourceDocumentationReader);
6366

67+
_schemaGenerationTracer = schemaGenerationTracer;
6468
_defaultSchemaGenerator = defaultSchemaGenerator;
6569
_generationCacheSchemaGenerator = generationCacheSchemaGenerator;
6670
_resourceTypeSchemaGenerator = resourceTypeSchemaGenerator;
@@ -108,6 +112,8 @@ public OpenApiSchema GenerateSchema(Type dataSchemaType, bool forRequestSchema,
108112
return schemaRepository.LookupByType(dataSchemaType);
109113
}
110114

115+
using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, dataSchemaType);
116+
111117
referenceSchemaForData = _defaultSchemaGenerator.GenerateSchema(dataSchemaType, schemaRepository);
112118
OpenApiSchema fullSchemaForData = schemaRepository.Schemas[referenceSchemaForData.Reference.Id];
113119
fullSchemaForData.AdditionalPropertiesAllowed = false;
@@ -139,6 +145,7 @@ public OpenApiSchema GenerateSchema(Type dataSchemaType, bool forRequestSchema,
139145
fullSchemaForData.Extensions[SetSchemaTypeToObjectDocumentFilter.RequiresRootObjectTypeKey] = new OpenApiBoolean(true);
140146
}
141147

148+
traceScope.TraceSucceeded(referenceSchemaForData.Reference.Id);
142149
return referenceSchemaForData;
143150
}
144151

@@ -205,6 +212,8 @@ public OpenApiSchema GenerateSchemaForCommonData(Type commonDataSchemaType, Sche
205212
return referenceSchema;
206213
}
207214

215+
using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, commonDataSchemaType);
216+
208217
OpenApiSchema referenceSchemaForResourceType = _resourceTypeSchemaGenerator.GenerateSchema(schemaRepository);
209218
OpenApiSchema referenceSchemaForMeta = _metaSchemaGenerator.GenerateSchema(schemaRepository);
210219

@@ -234,6 +243,7 @@ public OpenApiSchema GenerateSchemaForCommonData(Type commonDataSchemaType, Sche
234243
referenceSchema = schemaRepository.AddDefinition(schemaId, fullSchema);
235244
schemaRepository.RegisterType(commonDataSchemaType, schemaId);
236245

246+
traceScope.TraceSucceeded(schemaId);
237247
return referenceSchema;
238248
}
239249

@@ -344,7 +354,7 @@ private void SetResourceFields(OpenApiSchema fullSchemaForData, ResourceSchemaTy
344354

345355
if (schemaHasFields)
346356
{
347-
var fieldSchemaBuilder = new ResourceFieldSchemaBuilder(_defaultSchemaGenerator, this, _linksVisibilitySchemaGenerator,
357+
var fieldSchemaBuilder = new ResourceFieldSchemaBuilder(_schemaGenerationTracer, _defaultSchemaGenerator, this, _linksVisibilitySchemaGenerator,
348358
_resourceFieldValidationMetadataProvider, _relationshipTypeFactory, resourceSchemaType);
349359

350360
SetFieldSchemaMembers(fullSchemaForData, resourceSchemaType, forRequestSchema, true, fieldSchemaBuilder, schemaRepository);
@@ -434,6 +444,8 @@ private OpenApiSchema GenerateSchemaForCommonFields(Type commonFieldsSchemaType,
434444
return referenceSchema;
435445
}
436446

447+
using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, commonFieldsSchemaType);
448+
437449
OpenApiSchema referenceSchemaForResourceType = _resourceTypeSchemaGenerator.GenerateSchema(schemaRepository);
438450

439451
var fullSchema = new OpenApiSchema
@@ -461,6 +473,7 @@ private OpenApiSchema GenerateSchemaForCommonFields(Type commonFieldsSchemaType,
461473
referenceSchema = schemaRepository.AddDefinition(schemaId, fullSchema);
462474
schemaRepository.RegisterType(commonFieldsSchemaType, schemaId);
463475

476+
traceScope.TraceSucceeded(schemaId);
464477
return referenceSchema;
465478
}
466479

@@ -557,6 +570,8 @@ private void GenerateDataSchemasForDirectlyDerivedTypes(ResourceSchemaType resou
557570
ResourceSchemaType resourceSchemaTypeForDerived = resourceSchemaType.ChangeResourceType(derivedType);
558571
Type derivedSchemaType = resourceSchemaTypeForDerived.SchemaConstructedType;
559572

573+
using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, resourceSchemaTypeForDerived.SchemaConstructedType);
574+
560575
OpenApiSchema referenceSchemaForDerived = _defaultSchemaGenerator.GenerateSchema(derivedSchemaType, schemaRepository);
561576
OpenApiSchema fullSchemaForDerived = schemaRepository.Schemas[referenceSchemaForDerived.Reference.Id];
562577
fullSchemaForDerived.AdditionalPropertiesAllowed = false;
@@ -594,6 +609,8 @@ private void GenerateDataSchemasForDirectlyDerivedTypes(ResourceSchemaType resou
594609
}
595610

596611
GenerateDataSchemasForDirectlyDerivedTypes(resourceSchemaTypeForDerived, forRequestSchema, schemaRepository);
612+
613+
traceScope.TraceSucceeded(referenceSchemaForDerived.Reference.Id);
597614
}
598615
}
599616

Diff for: src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/MetaSchemaGenerator.cs

+10-3
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,30 @@ namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components;
66

77
internal sealed class MetaSchemaGenerator
88
{
9+
private static readonly Type SchemaType = typeof(Meta);
10+
private readonly SchemaGenerationTracer _schemaGenerationTracer;
911
private readonly JsonApiSchemaIdSelector _schemaIdSelector;
1012

11-
public MetaSchemaGenerator(JsonApiSchemaIdSelector schemaIdSelector)
13+
public MetaSchemaGenerator(SchemaGenerationTracer schemaGenerationTracer, JsonApiSchemaIdSelector schemaIdSelector)
1214
{
15+
ArgumentNullException.ThrowIfNull(schemaGenerationTracer);
1316
ArgumentNullException.ThrowIfNull(schemaIdSelector);
1417

18+
_schemaGenerationTracer = schemaGenerationTracer;
1519
_schemaIdSelector = schemaIdSelector;
1620
}
1721

1822
public OpenApiSchema GenerateSchema(SchemaRepository schemaRepository)
1923
{
2024
ArgumentNullException.ThrowIfNull(schemaRepository);
2125

22-
if (schemaRepository.TryLookupByType(typeof(Meta), out OpenApiSchema? referenceSchema))
26+
if (schemaRepository.TryLookupByType(SchemaType, out OpenApiSchema? referenceSchema))
2327
{
2428
return referenceSchema;
2529
}
2630

31+
using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, SchemaType);
32+
2733
var fullSchema = new OpenApiSchema
2834
{
2935
Type = "object",
@@ -36,8 +42,9 @@ public OpenApiSchema GenerateSchema(SchemaRepository schemaRepository)
3642
string schemaId = _schemaIdSelector.GetMetaSchemaId();
3743

3844
referenceSchema = schemaRepository.AddDefinition(schemaId, fullSchema);
39-
schemaRepository.RegisterType(typeof(Meta), schemaId);
45+
schemaRepository.RegisterType(SchemaType, schemaId);
4046

47+
traceScope.TraceSucceeded(schemaId);
4148
return referenceSchema;
4249
}
4350
}

0 commit comments

Comments
 (0)