Skip to content

Commit 5870012

Browse files
committed
Add a loglevel command option for additional logging
1 parent f2ee8d5 commit 5870012

File tree

2 files changed

+164
-55
lines changed

2 files changed

+164
-55
lines changed

src/Microsoft.OpenApi.Hidi/OpenApiService.cs

Lines changed: 152 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.IO;
78
using System.Linq;
89
using System.Net;
910
using System.Net.Http;
11+
using System.Security;
1012
using System.Text;
1113
using System.Text.Json;
14+
using Microsoft.Extensions.Logging;
1215
using Microsoft.OpenApi.Extensions;
1316
using Microsoft.OpenApi.Models;
1417
using Microsoft.OpenApi.Readers;
@@ -18,123 +21,200 @@
1821

1922
namespace Microsoft.OpenApi.Hidi
2023
{
21-
public static class OpenApiService
24+
public class OpenApiService
2225
{
2326
public static void ProcessOpenApiDocument(
2427
string openapi,
2528
FileInfo output,
2629
OpenApiSpecVersion? version,
2730
OpenApiFormat? format,
31+
LogLevel loglevel,
2832
string filterbyoperationids,
2933
string filterbytags,
3034
string filterbycollection,
3135
bool inline,
3236
bool resolveexternal)
3337
{
34-
if (string.IsNullOrEmpty(openapi))
38+
var logger = ConfigureLoggerInstance(loglevel);
39+
40+
try
3541
{
36-
throw new ArgumentNullException(nameof(openapi));
42+
if (string.IsNullOrEmpty(openapi))
43+
{
44+
throw new ArgumentNullException(nameof(openapi));
45+
}
46+
}
47+
catch (ArgumentNullException ex)
48+
{
49+
logger.LogError(ex.Message);
50+
return;
51+
}
52+
try
53+
{
54+
if(output == null)
55+
{
56+
throw new ArgumentException(nameof(output));
57+
}
3758
}
38-
if(output == null)
59+
catch (ArgumentException ex)
3960
{
40-
throw new ArgumentException(nameof(output));
61+
logger.LogError(ex.Message);
62+
return;
4163
}
42-
if (output.Exists)
64+
try
4365
{
44-
throw new IOException("The file you're writing to already exists. Please input a new output path.");
66+
if (output.Exists)
67+
{
68+
throw new IOException("The file you're writing to already exists. Please input a new file path.");
69+
}
4570
}
71+
catch (IOException ex)
72+
{
73+
logger.LogError(ex.Message);
74+
return;
75+
}
76+
77+
var stream = GetStream(openapi, logger);
4678

47-
var stream = GetStream(openapi);
79+
// Parsing OpenAPI file
80+
var stopwatch = new Stopwatch();
81+
stopwatch.Start();
82+
logger.LogTrace("Parsing OpenApi file");
4883
var result = new OpenApiStreamReader(new OpenApiReaderSettings
4984
{
5085
ReferenceResolution = resolveexternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences,
5186
RuleSet = ValidationRuleSet.GetDefaultRuleSet()
5287
}
5388
).ReadAsync(stream).GetAwaiter().GetResult();
54-
5589
var document = result.OpenApiDocument;
90+
stopwatch.Stop();
91+
92+
var context = result.OpenApiDiagnostic;
93+
if (context.Errors.Count > 0)
94+
{
95+
var errorReport = new StringBuilder();
96+
97+
foreach (var error in context.Errors)
98+
{
99+
errorReport.AppendLine(error.ToString());
100+
}
101+
logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())}");
102+
}
103+
else
104+
{
105+
logger.LogTrace("{timestamp}ms: Parsed OpenApi successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count);
106+
}
107+
56108
Func<string, OperationType?, OpenApiOperation, bool> predicate;
57109

58-
// Check if filter options are provided, then execute
110+
// Check if filter options are provided, then slice the OpenAPI document
59111
if (!string.IsNullOrEmpty(filterbyoperationids) && !string.IsNullOrEmpty(filterbytags))
60112
{
61113
throw new InvalidOperationException("Cannot filter by operationIds and tags at the same time.");
62114
}
63115
if (!string.IsNullOrEmpty(filterbyoperationids))
64116
{
117+
logger.LogTrace("Creating predicate based on the operationIds supplied.");
65118
predicate = OpenApiFilterService.CreatePredicate(operationIds: filterbyoperationids);
119+
120+
logger.LogTrace("Creating subset OpenApi document.");
66121
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
67122
}
68123
if (!string.IsNullOrEmpty(filterbytags))
69124
{
125+
logger.LogTrace("Creating predicate based on the tags supplied.");
70126
predicate = OpenApiFilterService.CreatePredicate(tags: filterbytags);
71-
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
72-
}
73127

74-
if (!string.IsNullOrEmpty(filterbycollection))
75-
{
76-
var fileStream = GetStream(filterbycollection);
77-
var requestUrls = ParseJsonCollectionFile(fileStream);
78-
predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source:document);
128+
logger.LogTrace("Creating subset OpenApi document.");
79129
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
80130
}
81-
82-
var context = result.OpenApiDiagnostic;
83-
84-
if (context.Errors.Count > 0)
131+
if (!string.IsNullOrEmpty(filterbycollection))
85132
{
86-
var errorReport = new StringBuilder();
133+
var fileStream = GetStream(filterbycollection, logger);
134+
var requestUrls = ParseJsonCollectionFile(fileStream, logger);
87135

88-
foreach (var error in context.Errors)
89-
{
90-
errorReport.AppendLine(error.ToString());
91-
}
136+
logger.LogTrace("Creating predicate based on the paths and Http methods defined in the Postman collection.");
137+
predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source:document);
92138

93-
throw new ArgumentException(string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray()));
139+
logger.LogTrace("Creating subset OpenApi document.");
140+
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
94141
}
95-
142+
143+
logger.LogTrace("Creating a new file");
96144
using var outputStream = output?.Create();
97-
98-
var textWriter = outputStream != null ? new StreamWriter(outputStream) : Console.Out;
145+
var textWriter = outputStream != null ? new StreamWriter(outputStream) : Console.Out;
99146

100147
var settings = new OpenApiWriterSettings()
101148
{
102149
ReferenceInline = inline ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences
103150
};
104151

105-
var openApiFormat = format ?? GetOpenApiFormat(openapi);
152+
var openApiFormat = format ?? GetOpenApiFormat(openapi, logger);
106153
var openApiVersion = version ?? result.OpenApiDiagnostic.SpecificationVersion;
107154
IOpenApiWriter writer = openApiFormat switch
108155
{
109156
OpenApiFormat.Json => new OpenApiJsonWriter(textWriter, settings),
110157
OpenApiFormat.Yaml => new OpenApiYamlWriter(textWriter, settings),
111158
_ => throw new ArgumentException("Unknown format"),
112159
};
160+
161+
logger.LogTrace("Serializing to OpenApi document using the provided spec version and writer");
162+
163+
stopwatch.Start();
113164
document.Serialize(writer, openApiVersion);
165+
stopwatch.Stop();
166+
167+
logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms");
114168

115169
textWriter.Flush();
116170
}
117171

118-
private static Stream GetStream(string input)
172+
private static Stream GetStream(string input, ILogger logger)
119173
{
174+
var stopwatch = new Stopwatch();
175+
stopwatch.Start();
176+
120177
Stream stream;
121178
if (input.StartsWith("http"))
122179
{
123-
var httpClient = new HttpClient(new HttpClientHandler()
180+
try
124181
{
125-
SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
126-
})
182+
var httpClient = new HttpClient(new HttpClientHandler()
183+
{
184+
SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
185+
})
186+
{
187+
DefaultRequestVersion = HttpVersion.Version20
188+
};
189+
stream = httpClient.GetStreamAsync(input).Result;
190+
}
191+
catch (HttpRequestException ex)
127192
{
128-
DefaultRequestVersion = HttpVersion.Version20
129-
};
130-
stream = httpClient.GetStreamAsync(input).Result;
193+
logger.LogError($"Could not download the file at {input}, reason{ex}");
194+
return null;
195+
}
131196
}
132197
else
133198
{
134-
var fileInput = new FileInfo(input);
135-
stream = fileInput.OpenRead();
199+
try
200+
{
201+
var fileInput = new FileInfo(input);
202+
stream = fileInput.OpenRead();
203+
}
204+
catch (Exception ex) when (ex is FileNotFoundException ||
205+
ex is PathTooLongException ||
206+
ex is DirectoryNotFoundException ||
207+
ex is IOException ||
208+
ex is UnauthorizedAccessException ||
209+
ex is SecurityException ||
210+
ex is NotSupportedException)
211+
{
212+
logger.LogError($"Could not open the file at {input}, reason: {ex.Message}");
213+
return null;
214+
}
136215
}
137-
216+
stopwatch.Stop();
217+
logger.LogTrace("{timestamp}ms: Read file {input}", stopwatch.ElapsedMilliseconds, input);
138218
return stream;
139219
}
140220

@@ -143,11 +223,11 @@ private static Stream GetStream(string input)
143223
/// </summary>
144224
/// <param name="stream"> A file stream.</param>
145225
/// <returns> A dictionary of request urls and http methods from a collection.</returns>
146-
public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream stream)
226+
public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream stream, ILogger logger)
147227
{
148228
var requestUrls = new Dictionary<string, List<string>>();
149229

150-
// Convert file to JsonDocument
230+
logger.LogTrace("Parsing the json collection file into a JsonDocument");
151231
using var document = JsonDocument.Parse(stream);
152232
var root = document.RootElement;
153233
var itemElement = root.GetProperty("item");
@@ -166,21 +246,21 @@ public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream st
166246
requestUrls[path].Add(method);
167247
}
168248
}
169-
249+
logger.LogTrace("Finished fetching the list of paths and Http methods defined in the Postman collection.");
170250
return requestUrls;
171251
}
172252

173-
internal static void ValidateOpenApiDocument(string openapi)
253+
internal static void ValidateOpenApiDocument(string openapi, LogLevel loglevel)
174254
{
175-
if (openapi == null)
255+
if (string.IsNullOrEmpty(openapi))
176256
{
177-
throw new ArgumentNullException("openapi");
257+
throw new ArgumentNullException(nameof(openapi));
178258
}
179-
180-
var stream = GetStream(openapi);
259+
var logger = ConfigureLoggerInstance(loglevel);
260+
var stream = GetStream(openapi, logger);
181261

182262
OpenApiDocument document;
183-
263+
logger.LogTrace("Parsing the OpenApi file");
184264
document = new OpenApiStreamReader(new OpenApiReaderSettings
185265
{
186266
RuleSet = ValidationRuleSet.GetDefaultRuleSet()
@@ -199,12 +279,33 @@ internal static void ValidateOpenApiDocument(string openapi)
199279
var walker = new OpenApiWalker(statsVisitor);
200280
walker.Walk(document);
201281

282+
logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report..");
202283
Console.WriteLine(statsVisitor.GetStatisticsReport());
203284
}
204285

205-
private static OpenApiFormat GetOpenApiFormat(string openapi)
286+
private static OpenApiFormat GetOpenApiFormat(string openapi, ILogger logger)
206287
{
288+
logger.LogTrace("Getting the OpenApi format");
207289
return !openapi.StartsWith("http") && Path.GetExtension(openapi) == ".json" ? OpenApiFormat.Json : OpenApiFormat.Yaml;
208290
}
291+
292+
private static ILogger ConfigureLoggerInstance(LogLevel loglevel)
293+
{
294+
// Configure logger options
295+
#if DEBUG
296+
loglevel = loglevel > LogLevel.Debug ? LogLevel.Debug : loglevel;
297+
#endif
298+
299+
var logger = LoggerFactory.Create((builder) => {
300+
builder
301+
.AddConsole()
302+
#if DEBUG
303+
.AddDebug()
304+
#endif
305+
.SetMinimumLevel(loglevel);
306+
}).CreateLogger<OpenApiService>();
307+
308+
return logger;
309+
}
209310
}
210311
}

src/Microsoft.OpenApi.Hidi/Program.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.CommandLine.Invocation;
66
using System.IO;
77
using System.Threading.Tasks;
8+
using Microsoft.Extensions.Logging;
89

910
namespace Microsoft.OpenApi.Hidi
1011
{
@@ -27,7 +28,10 @@ static async Task<int> Main(string[] args)
2728

2829
var formatOption = new Option("--format", "File format", typeof(OpenApiFormat));
2930
formatOption.AddAlias("-f");
30-
31+
32+
var logLevelOption = new Option("--loglevel", "The log level to use when logging messages to the main output.", typeof(LogLevel), () => LogLevel.Warning);
33+
logLevelOption.AddAlias("-ll");
34+
3135
var inlineOption = new Option("--inline", "Inline $ref instances", typeof(bool));
3236
inlineOption.AddAlias("-i");
3337

@@ -45,23 +49,27 @@ static async Task<int> Main(string[] args)
4549

4650
var validateCommand = new Command("validate")
4751
{
48-
descriptionOption
52+
descriptionOption,
53+
logLevelOption
4954
};
50-
validateCommand.Handler = CommandHandler.Create<string>(OpenApiService.ValidateOpenApiDocument);
55+
56+
validateCommand.Handler = CommandHandler.Create<string, LogLevel>(OpenApiService.ValidateOpenApiDocument);
5157

5258
var transformCommand = new Command("transform")
5359
{
5460
descriptionOption,
5561
outputOption,
5662
versionOption,
5763
formatOption,
64+
logLevelOption,
5865
inlineOption,
5966
resolveExternalOption,
6067
filterByOperationIdsOption,
6168
filterByTagsOption,
6269
filterByCollectionOption
6370
};
64-
transformCommand.Handler = CommandHandler.Create<string, FileInfo, OpenApiSpecVersion?, OpenApiFormat?, string, string, string, bool, bool>(
71+
72+
transformCommand.Handler = CommandHandler.Create<string, FileInfo, OpenApiSpecVersion?, OpenApiFormat?, LogLevel, string, string, string, bool, bool>(
6573
OpenApiService.ProcessOpenApiDocument);
6674

6775
rootCommand.Add(transformCommand);

0 commit comments

Comments
 (0)