From 83b4ac107e78524f722a721da0841d544cc3eaad Mon Sep 17 00:00:00 2001 From: Jon Sagara Date: Thu, 14 Dec 2023 13:28:41 -0800 Subject: [PATCH] #14: Replace TextWriter with ILoggerFactory for logging to external logs. --- src/IdParser.Core/Barcode.cs | 19 ++++--- src/IdParser.Core/Fixes.cs | 32 ++++++----- src/IdParser.Core/IdParser.Core.csproj | 1 + src/IdParser.Core/LogProxy.cs | 79 -------------------------- 4 files changed, 29 insertions(+), 102 deletions(-) delete mode 100644 src/IdParser.Core/LogProxy.cs diff --git a/src/IdParser.Core/Barcode.cs b/src/IdParser.Core/Barcode.cs index 3bd4025..63d21c0 100644 --- a/src/IdParser.Core/Barcode.cs +++ b/src/IdParser.Core/Barcode.cs @@ -2,6 +2,7 @@ using System.Text.RegularExpressions; using IdParser.Core.Constants; using IdParser.Core.Parsers; +using Microsoft.Extensions.Logging; namespace IdParser.Core; @@ -53,8 +54,8 @@ public static class Barcode /// No validation will be performed if none is specified and exceptions will not be thrown /// for elements that do not match or do not adversely affect parsing. /// - /// The to log to. - public static BarcodeParseResult Parse(string rawPdf417Input, Validation validationLevel = Validation.Strict, TextWriter? log = null) + /// to use to create an for logging. + public static BarcodeParseResult Parse(string rawPdf417Input, Validation validationLevel = Validation.Strict, ILoggerFactory? loggerFactory = null) { ArgumentNullException.ThrowIfNull(rawPdf417Input); @@ -63,7 +64,7 @@ public static BarcodeParseResult Parse(string rawPdf417Input, Validation validat throw new ArgumentException($"The input is missing required header elements and is not a valid AAMVA format. Expected at least 31 characters. Received {rawPdf417Input.Length}.", nameof(rawPdf417Input)); } - using var logProxy = LogProxy.TryCreate(log); + ILogger? logger = loggerFactory?.CreateLogger(typeof(Barcode)); if (validationLevel == Validation.Strict) { @@ -71,7 +72,7 @@ public static BarcodeParseResult Parse(string rawPdf417Input, Validation validat } else { - rawPdf417Input = Fixes.TryToCorrectHeader(rawPdf417Input, logProxy); + rawPdf417Input = Fixes.TryToCorrectHeader(rawPdf417Input, loggerFactory); } var aamvaVersion = ParseAAMVAVersion(rawPdf417Input); @@ -82,10 +83,10 @@ public static BarcodeParseResult Parse(string rawPdf417Input, Validation validat var country = ParseCountry(idCard.IssuerIdentificationNumber, aamvaVersion, subfileRecords); idCard.Address.Country = country; - var unhandledElementIds = PopulateIdCard(idCard, aamvaVersion, country, subfileRecords, logProxy); + var unhandledElementIds = PopulateIdCard(idCard, aamvaVersion, country, subfileRecords, logger); if (unhandledElementIds.Count > 0) { - logProxy?.WriteLine($"[{nameof(Barcode)}] One or more ElementIds were not handled by the ID or Driver's License parsers: {string.Join(", ", unhandledElementIds)}"); + logger?.LogError($"One or more ElementIds were not handled by the ID or Driver's License parsers: {{UnhandledElementIds}}", string.Join(", ", unhandledElementIds)); } return new BarcodeParseResult(idCard, unhandledElementIds); @@ -217,7 +218,7 @@ private static IdentificationCard GetIdCardInstance(string rawPdf417Input, AAMVA return idCard; } - + private static readonly Regex _rxSubfile = new Regex("(DL|ID)([\\d\\w]{3,8})(DL|ID|Z\\w)([DZ][A-Z]{2})", RegexOptions.Compiled); /// @@ -340,7 +341,7 @@ private static Country ParseCountry(IssuerIdentificationNumber iin, AAMVAVersion return IssuerMetadataHelper.GetCountry(iin); } - private static IReadOnlyCollection PopulateIdCard(IdentificationCard idCard, AAMVAVersion version, Country country, Dictionary subfileRecords, LogProxy? logProxy) + private static IReadOnlyCollection PopulateIdCard(IdentificationCard idCard, AAMVAVersion version, Country country, Dictionary subfileRecords, ILogger? logger) { List unhandledElementIds = new(); @@ -374,7 +375,7 @@ private static IReadOnlyCollection PopulateIdCard(IdentificationCard idC } catch (Exception ex) { - logProxy?.WriteLine($"[{nameof(Barcode)}] Unhandled exception in {nameof(PopulateIdCard)} while trying to parse element Id {elementId}: {ex}"); + logger?.LogError(ex, $"Unhandled exception in {nameof(PopulateIdCard)} while trying to parse element Id {{ElementId}}", elementId); throw; } } diff --git a/src/IdParser.Core/Fixes.cs b/src/IdParser.Core/Fixes.cs index c66dd06..c1cb7c6 100644 --- a/src/IdParser.Core/Fixes.cs +++ b/src/IdParser.Core/Fixes.cs @@ -1,17 +1,21 @@ -namespace IdParser.Core; +using Microsoft.Extensions.Logging; + +namespace IdParser.Core; internal static class Fixes { /// /// If the header is invalid, try to correct it. Otherwise, return the string as-is. /// - internal static string TryToCorrectHeader(string input, LogProxy? logProxy) + internal static string TryToCorrectHeader(string input, ILoggerFactory? loggerFactory) { + var logger = loggerFactory?.CreateLogger(typeof(Fixes)); + return input .RemoveUndefinedCharacters() - .RemoveInvalidCharactersFromHeader(logProxy) - .FixIncorrectHeader(logProxy) - .RemoveIncorrectCarriageReturns(logProxy); + .RemoveInvalidCharactersFromHeader(logger) + .FixIncorrectHeader(logger) + .RemoveIncorrectCarriageReturns(logger); } @@ -54,14 +58,14 @@ private static string RemoveUndefinedCharacters(this string input) /// Sometimes bad characters (e.g. @a ANSI) get into the header (usually through HID keyboard emulation). /// Replace the header with what we are expecting. /// - private static string RemoveInvalidCharactersFromHeader(this string input, LogProxy? logProxy) + private static string RemoveInvalidCharactersFromHeader(this string input, ILogger? logger) { input = input.TrimStart(); if (input[0] != '@') { // Text doesn't start with an input. Don't try to parse it further. Return it as-is. - logProxy?.WriteLine($"[{nameof(Fixes)}] Input doesn't start with the expected compliance indicator '{Barcode.ExpectedComplianceIndicator}'. Exiting {nameof(RemoveInvalidCharactersFromHeader)}."); + logger?.LogError($"Input doesn't start with the expected compliance indicator '{Barcode.ExpectedComplianceIndicator}'. Exiting {nameof(RemoveInvalidCharactersFromHeader)}."); return input; } @@ -79,7 +83,7 @@ private static string RemoveInvalidCharactersFromHeader(this string input, LogPr // The string "ANSI " exists in the text. Starting with the expected header value, append everything from // the input string after the "ANSI " text. // This ensures that the input text has a valid header. - logProxy?.WriteLine($"[{nameof(Fixes)}] Header contains '{Barcode.ExpectedFileType}'. Forcefully ensuring that the header is valid."); + logger?.LogInformation($"Header contains '{Barcode.ExpectedFileType}'. Forcefully ensuring that the header is valid."); return string.Concat(Barcode.ExpectedHeader, input.AsSpan(start: ixANSI + Barcode.ExpectedFileType.Length)); } @@ -92,7 +96,7 @@ private static string RemoveInvalidCharactersFromHeader(this string input, LogPr // Earlier versions of the spec must have had "AMMVA" instead of "ANSI " in the header. Starting with // the current expected header value, append everything from the input string after the "AMMVA" text. // This ensures that the input text has a valid header. - logProxy?.WriteLine($"[{nameof(Fixes)}] Header contains '{AAMVA}'. This is from an earlier spec. Replacing the old header with the current valid header text."); + logger?.LogInformation($"Header contains '{AAMVA}'. This is from an earlier spec. Replacing the old header with the current valid header text."); return string.Concat(Barcode.ExpectedHeader, input.AsSpan(start: aamvaPosition + AAMVA.Length)); } @@ -104,7 +108,7 @@ private static string RemoveInvalidCharactersFromHeader(this string input, LogPr /// HID keyboard emulation, especially entered via a web browser, tends to mutilate the header. /// As long as part of the header is correct, this will fix the rest of it to make it parse-able. /// - private static string FixIncorrectHeader(this string input, LogProxy? logProxy) + private static string FixIncorrectHeader(this string input, ILogger? logger) { if (input[0] == Barcode.ExpectedComplianceIndicator && input[1] == Barcode.ExpectedSegmentTerminator && @@ -114,7 +118,7 @@ private static string FixIncorrectHeader(this string input, LogProxy? logProxy) { // Header is expected to be "@\n\u0030\rANSI ", but is currently "@\r\n\u0030A". Insert "\r\n" in // front of the A. We'll correct this later by removing incorrect "\r" characters. - logProxy?.WriteLine($"[{nameof(Fixes)}] Header is malformed, and starts with '@\\r\\n\\u0030A'. Changing it to '@\\r\\n\\u0030\\r\\nA'. A later method will remove incorrect \\r characters."); + logger?.LogWarning($"Header is malformed, and starts with '@\\r\\n\\u0030A'. Changing it to '@\\r\\n\\u0030\\r\\nA'. A later method will remove incorrect \\r characters."); return input.Insert(startIndex: 4, value: $"{Barcode.ExpectedSegmentTerminator}{Barcode.ExpectedDataElementSeparator}"); } @@ -125,16 +129,16 @@ private static string FixIncorrectHeader(this string input, LogProxy? logProxy) /// HID keyboard emulation (and some other methods) tend to replace the \r with \r\n, which is invalid and doesn't /// conform to the AAMVA standard. This fixes it before attempting to parse the fields. /// - private static string RemoveIncorrectCarriageReturns(this string input, LogProxy? logProxy) + private static string RemoveIncorrectCarriageReturns(this string input, ILogger? logger) { if (input.Contains("\r\n", StringComparison.Ordinal)) { // Input contains CRLFs (\r\n). Remove all CRs (\r). - logProxy?.WriteLine($"[{nameof(Fixes)}] Scanned text contains \\r characters. Remove them."); + logger?.LogInformation($"Scanned text contains \\r characters. Removing them."); var inputWithoutCRs = input.Replace("\r", string.Empty, StringComparison.Ordinal); // Add back the one CR (\r) that is required in the header. - logProxy?.WriteLine($"[{nameof(Fixes)}] Add the single allowed \\r character back to the header."); + logger?.LogInformation($"Adding the single allowed \\r character back to the header."); return $"{inputWithoutCRs.AsSpan(start: 0, length: 3)}\r{inputWithoutCRs.AsSpan(start: 4)}"; } diff --git a/src/IdParser.Core/IdParser.Core.csproj b/src/IdParser.Core/IdParser.Core.csproj index 04d1fe1..54ad1c2 100644 --- a/src/IdParser.Core/IdParser.Core.csproj +++ b/src/IdParser.Core/IdParser.Core.csproj @@ -40,6 +40,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IdParser.Core/LogProxy.cs b/src/IdParser.Core/LogProxy.cs deleted file mode 100644 index 3ef87c6..0000000 --- a/src/IdParser.Core/LogProxy.cs +++ /dev/null @@ -1,79 +0,0 @@ -namespace IdParser.Core; - -// Mad props to StackExchange.Redis for this. -// https://github.com/StackExchange/StackExchange.Redis - -internal sealed class LogProxy : IDisposable -{ - public static LogProxy? TryCreate(TextWriter? writer) - => writer is null - ? null - : new LogProxy(writer); - - public override string ToString() - { - string? s = null; - if (_log is not null) - { - lock (SyncLock) - { - s = _log?.ToString(); - } - } - return s ?? base.ToString() ?? string.Empty; - } - -#pragma warning disable CA2213 // Disposable fields should be disposed: We don't want to dispose the caller's text writer. - private TextWriter? _log; -#pragma warning restore CA2213 // Disposable fields should be disposed - - internal static Action NullWriter = _ => { }; - - public object SyncLock => this; - - private LogProxy(TextWriter log) - => _log = log; - - public void WriteLine() - { - if (_log is not null) // note: double-checked - { - lock (SyncLock) - { - _log?.WriteLine(); - } - } - } - - public void WriteLine(string? message = null) - { - if (_log is not null) // note: double-checked - { - lock (SyncLock) - { - _log?.WriteLine($"{DateTime.UtcNow:HH:mm:ss.ffff}: {message}"); - } - } - } - public void WriteLine(string prefix, string message) - { - if (_log is not null) // note: double-checked - { - lock (SyncLock) - { - _log?.WriteLine($"{DateTime.UtcNow:HH:mm:ss.ffff}: {prefix}{message}"); - } - } - } - public void Dispose() - { - if (_log is not null) // note: double-checked - { - lock (SyncLock) - { - // We're releasing the reference, but not disposing of the caller's TextWriter. - _log = null; - } - } - } -}