From 0a34a1ce271d5cd3a7acf7e9cac675e6438f17ef Mon Sep 17 00:00:00 2001 From: David Vreony Date: Sat, 6 Aug 2022 11:20:05 +0100 Subject: [PATCH 1/6] prep for functional log message definitions --- .../Logging/LoggerMessageFactory.cs | 721 ++++++++++++++++++ 1 file changed, 721 insertions(+) diff --git a/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs b/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs index 738eb82cd..e37f7c309 100644 --- a/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs +++ b/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for full license information. using System; +using System.Collections; +using System.Collections.Generic; using Microsoft.Extensions.Logging; namespace Whipstaff.Core.Logging @@ -50,6 +52,17 @@ public static class LoggerMessageFactory LogLevel.Critical, eventId); + /// + /// Gets a basic debug logger message action for an event id. Useful for basic logging of events where there is only + /// ever a basic message. + /// + /// The event id to define a log message action for. + /// Log Message Action. + public static Action, Exception?> GetCriticalBasicLoggerMessageActionForEventIdWithFunc(EventId eventId) => + GetBasicLoggerMessageActionForLogLevelAndEventIdWithFunc( + LogLevel.Critical, + eventId); + /// /// Gets a basic debug logger message action for an event id. Useful for basic logging of events where there is only /// ever a basic message. @@ -61,6 +74,17 @@ public static class LoggerMessageFactory LogLevel.Debug, eventId); + /// + /// Gets a basic debug logger message action for an event id. Useful for basic logging of events where there is only + /// ever a basic message. + /// + /// The event id to define a log message action for. + /// Log Message Action. + public static Action, Exception?> GetDebugBasicLoggerMessageActionForEventIdWithFunc(EventId eventId) => + GetBasicLoggerMessageActionForLogLevelAndEventIdWithFunc( + LogLevel.Debug, + eventId); + /// /// Gets a basic error logger message action for an event id. Useful for basic logging of events where there is only /// ever a basic message. @@ -72,6 +96,17 @@ public static class LoggerMessageFactory LogLevel.Error, eventId); + /// + /// Gets a basic error logger message action for an event id. Useful for basic logging of events where there is only + /// ever a basic message. + /// + /// The event id to define a log message action for. + /// Log Message Action. + public static Action, Exception?> GetErrorBasicLoggerMessageActionForEventIdWithFunc(EventId eventId) => + GetBasicLoggerMessageActionForLogLevelAndEventIdWithFunc( + LogLevel.Error, + eventId); + /// /// Gets a basic information logger message action for an event id. Useful for basic logging of events where there is only /// ever a basic message. @@ -83,6 +118,17 @@ public static class LoggerMessageFactory LogLevel.Information, eventId); + /// + /// Gets a basic information logger message action for an event id. Useful for basic logging of events where there is only + /// ever a basic message. + /// + /// The event id to define a log message action for. + /// Log Message Action. + public static Action, Exception?> GetInformationBasicLoggerMessageActionForEventIdWithFunc(EventId eventId) => + GetBasicLoggerMessageActionForLogLevelAndEventIdWithFunc( + LogLevel.Information, + eventId); + /// /// Gets a basic information logger message action for an event id. Useful for basic logging of events where there is only /// ever a basic message. @@ -94,6 +140,17 @@ public static class LoggerMessageFactory LogLevel.Warning, eventId); + /// + /// Gets a basic information logger message action for an event id. Useful for basic logging of events where there is only + /// ever a basic message. + /// + /// The event id to define a log message action for. + /// Log Message Action. + public static Action, Exception?> GetWarningBasicLoggerMessageActionForEventIdWithFunc(EventId eventId) => + GetBasicLoggerMessageActionForLogLevelAndEventIdWithFunc( + LogLevel.Warning, + eventId); + /// /// Gets a basic log message action where there will only ever be a basic message. /// @@ -107,5 +164,669 @@ public static class LoggerMessageFactory logLevel, eventId, "{Message}"); + + /// + /// Gets a basic log message action where there will only ever be a basic message. + /// + /// The logging level. + /// The event id. + /// Log Message Action. + public static Action, Exception?> GetBasicLoggerMessageActionForLogLevelAndEventIdWithFunc( + LogLevel logLevel, + EventId eventId) => + DefineWithFunc( + logLevel, + eventId, + "{Message}"); + + /// + /// Creates a delegate which can be invoked for logging a message. + /// + /// The type of the first parameter passed to the named format string. + /// The . + /// The event id. + /// The named format string. + /// A delegate which when invoked creates a log message. + public static Action, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) + => DefineWithFunc(logLevel, eventId, formatString, options: null); + + /// + /// Creates a delegate which can be invoked for logging a message. + /// + /// The type of the first parameter passed to the named format string. + /// The . + /// The event id. + /// The named format string. + /// The . + /// A delegate which when invoked creates a log message. + public static Action, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) + { + LogValuesFormatter formatter = CreateLogValuesFormatter(formatString, expectedNamedParameterCount: 1); + + void Log(ILogger logger, Func arg1, Exception? exception) + { + logger.Log(logLevel, eventId, new LogValues(formatter, arg1()), exception, LogValues.Callback); + } + + if (options != null && options.SkipEnabledCheck) + { + return Log; + } + + return (logger, arg1, exception) => + { + if (logger.IsEnabled(logLevel)) + { + Log(logger, arg1, exception); + } + }; + } + + /// + /// Creates a delegate which can be invoked for logging a message. + /// + /// The type of the first parameter passed to the named format string. + /// The type of the second parameter passed to the named format string. + /// The . + /// The event id. + /// The named format string. + /// A delegate which when invoked creates a log message. + public static Action, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) + => DefineWithFunc(logLevel, eventId, formatString, options: null); + + /// + /// Creates a delegate which can be invoked for logging a message. + /// + /// The type of the first parameter passed to the named format string. + /// The type of the second parameter passed to the named format string. + /// The + /// The event id + /// The named format string + /// The + /// A delegate which when invoked creates a log message. + public static Action, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) + { + LogValuesFormatter formatter = CreateLogValuesFormatter(formatString, expectedNamedParameterCount: 2); + + void Log(ILogger logger, Func arg1, Func arg2, Exception? exception) + { + logger.Log(logLevel, eventId, new LogValues(formatter, arg1(), arg2()), exception, LogValues.Callback); + } + + if (options != null && options.SkipEnabledCheck) + { + return Log; + } + + return (logger, arg1, arg2, exception) => + { + if (logger.IsEnabled(logLevel)) + { + Log(logger, arg1, arg2, exception); + } + }; + } + + /// + /// Creates a delegate which can be invoked for logging a message. + /// + /// The type of the first parameter passed to the named format string. + /// The type of the second parameter passed to the named format string. + /// The type of the third parameter passed to the named format string. + /// The + /// The event id + /// The named format string + /// A delegate which when invoked creates a log message. + public static Action, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) + => DefineWithFunc(logLevel, eventId, formatString, options: null); + + /// + /// Creates a delegate which can be invoked for logging a message. + /// + /// The type of the first parameter passed to the named format string. + /// The type of the second parameter passed to the named format string. + /// The type of the third parameter passed to the named format string. + /// The . + /// The event id. + /// The named format string. + /// The . + /// A delegate which when invoked creates a log message. + public static Action, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) + { + LogValuesFormatter formatter = CreateLogValuesFormatter(formatString, expectedNamedParameterCount: 3); + + void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Exception? exception) + { + logger.Log(logLevel, eventId, new LogValues(formatter, arg1(), arg2(), arg3()), exception, LogValues.Callback); + } + + if (options != null && options.SkipEnabledCheck) + { + return Log; + } + + return (logger, arg1, arg2, arg3, exception) => + { + if (logger.IsEnabled(logLevel)) + { + Log(logger, arg1, arg2, arg3, exception); + } + }; + } + + /// + /// Creates a delegate which can be invoked for logging a message. + /// + /// The type of the first parameter passed to the named format string. + /// The type of the second parameter passed to the named format string. + /// The type of the third parameter passed to the named format string. + /// The type of the fourth parameter passed to the named format string. + /// The + /// The event id + /// The named format string + /// A delegate which when invoked creates a log message. + public static Action, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) + => DefineWithFunc(logLevel, eventId, formatString, options: null); + + /// + /// Creates a delegate which can be invoked for logging a message. + /// + /// The type of the first parameter passed to the named format string. + /// The type of the second parameter passed to the named format string. + /// The type of the third parameter passed to the named format string. + /// The type of the fourth parameter passed to the named format string. + /// The . + /// The event id. + /// The named format string. + /// The . + /// A delegate which when invoked creates a log message. + public static Action, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) + { + LogValuesFormatter formatter = CreateLogValuesFormatter(formatString, expectedNamedParameterCount: 4); + + void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Func arg4, Exception? exception) + { + logger.Log(logLevel, eventId, new LogValues(formatter, arg1(), arg2(), arg3(), arg4()), exception, LogValues.Callback); + } + + if (options != null && options.SkipEnabledCheck) + { + return Log; + } + + return (logger, arg1, arg2, arg3, arg4, exception) => + { + if (logger.IsEnabled(logLevel)) + { + Log(logger, arg1, arg2, arg3, arg4, exception); + } + }; + } + + /// + /// Creates a delegate which can be invoked for logging a message. + /// + /// The type of the first parameter passed to the named format string. + /// The type of the second parameter passed to the named format string. + /// The type of the third parameter passed to the named format string. + /// The type of the fourth parameter passed to the named format string. + /// The type of the fifth parameter passed to the named format string. + /// The . + /// The event id. + /// The named format string. + /// A delegate which when invoked creates a log message. + public static Action, Func, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) + => DefineWithFunc(logLevel, eventId, formatString, options: null); + + /// + /// Creates a delegate which can be invoked for logging a message. + /// + /// The type of the first parameter passed to the named format string. + /// The type of the second parameter passed to the named format string. + /// The type of the third parameter passed to the named format string. + /// The type of the fourth parameter passed to the named format string. + /// The type of the fifth parameter passed to the named format string. + /// The . + /// The event id. + /// The named format string. + /// The . + /// A delegate which when invoked creates a log message. + public static Action, Func, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) + { + LogValuesFormatter formatter = CreateLogValuesFormatter(formatString, expectedNamedParameterCount: 5); + + void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Func arg4, Func arg5, Exception? exception) + { + logger.Log(logLevel, eventId, new LogValues(formatter, arg1(), arg2(), arg3(), arg4(), arg5()), exception, LogValues.Callback); + } + + if (options != null && options.SkipEnabledCheck) + { + return Log; + } + + return (logger, arg1, arg2, arg3, arg4, arg5, exception) => + { + if (logger.IsEnabled(logLevel)) + { + Log(logger, arg1, arg2, arg3, arg4, arg5, exception); + } + }; + } + + /// + /// Creates a delegate which can be invoked for logging a message. + /// + /// The type of the first parameter passed to the named format string. + /// The type of the second parameter passed to the named format string. + /// The type of the third parameter passed to the named format string. + /// The type of the fourth parameter passed to the named format string. + /// The type of the fifth parameter passed to the named format string. + /// The type of the sixth parameter passed to the named format string. + /// The + /// The event id + /// The named format string + /// A delegate which when invoked creates a log message. + public static Action, Func, Func, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) + => DefineWithFunc(logLevel, eventId, formatString, options: null); + + /// + /// Creates a delegate which can be invoked for logging a message. + /// + /// The type of the first parameter passed to the named format string. + /// The type of the second parameter passed to the named format string. + /// The type of the third parameter passed to the named format string. + /// The type of the fourth parameter passed to the named format string. + /// The type of the fifth parameter passed to the named format string. + /// The type of the sixth parameter passed to the named format string. + /// The + /// The event id + /// The named format string + /// The + /// A delegate which when invoked creates a log message. + public static Action, Func, Func, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) + { + LogValuesFormatter formatter = CreateLogValuesFormatter(formatString, expectedNamedParameterCount: 6); + + void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Func arg4, Func arg5, Func arg6, Exception? exception) + { + logger.Log(logLevel, eventId, new LogValues(formatter, arg1(), arg2(), arg3(), arg4(), arg5(), arg6()), exception, LogValues.Callback); + } + + if (options != null && options.SkipEnabledCheck) + { + return Log; + } + + return (logger, arg1, arg2, arg3, arg4, arg5, arg6, exception) => + { + if (logger.IsEnabled(logLevel)) + { + Log(logger, arg1, arg2, arg3, arg4, arg5, arg6, exception); + } + }; + } + + private static LogValuesFormatter CreateLogValuesFormatter(string formatString, int expectedNamedParameterCount) + { + var logValuesFormatter = new LogValuesFormatter(formatString); + + int actualCount = logValuesFormatter.ValueNames.Count; + if (actualCount != expectedNamedParameterCount) + { + throw new ArgumentException( + SR.Format(SR.UnexpectedNumberOfNamedParameters, formatString, expectedNamedParameterCount, actualCount)); + } + + return logValuesFormatter; + } + + private readonly struct LogValues : IReadOnlyList> + { + public static readonly Func, Exception?, string> Callback = (state, exception) => state.ToString(); + + private readonly LogValuesFormatter _formatter; + private readonly T0 _value0; + + public LogValues(LogValuesFormatter formatter, T0 value0) + { + _formatter = formatter; + _value0 = value0; + } + + public KeyValuePair this[int index] + { + get + { + switch (index) + { + case 0: + return new KeyValuePair(_formatter.ValueNames[0], _value0); + case 1: + return new KeyValuePair("{OriginalFormat}", _formatter.OriginalFormat); + default: + throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public int Count => 2; + + public IEnumerator> GetEnumerator() + { + for (int i = 0; i < Count; ++i) + { + yield return this[i]; + } + } + + public override string ToString() => _formatter.Format(_value0); + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + private readonly struct LogValues : IReadOnlyList> + { + public static readonly Func, Exception?, string> Callback = (state, exception) => state.ToString(); + + private readonly LogValuesFormatter _formatter; + private readonly T0 _value0; + private readonly T1 _value1; + + public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1) + { + _formatter = formatter; + _value0 = value0; + _value1 = value1; + } + + public KeyValuePair this[int index] + { + get + { + switch (index) + { + case 0: + return new KeyValuePair(_formatter.ValueNames[0], _value0); + case 1: + return new KeyValuePair(_formatter.ValueNames[1], _value1); + case 2: + return new KeyValuePair("{OriginalFormat}", _formatter.OriginalFormat); + default: + throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public int Count => 3; + + public IEnumerator> GetEnumerator() + { + for (int i = 0; i < Count; ++i) + { + yield return this[i]; + } + } + + public override string ToString() => _formatter.Format(_value0, _value1); + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + private readonly struct LogValues : IReadOnlyList> + { + public static readonly Func, Exception?, string> Callback = (state, exception) => state.ToString(); + + private readonly LogValuesFormatter _formatter; + private readonly T0 _value0; + private readonly T1 _value1; + private readonly T2 _value2; + + public int Count => 4; + + public KeyValuePair this[int index] + { + get + { + switch (index) + { + case 0: + return new KeyValuePair(_formatter.ValueNames[0], _value0); + case 1: + return new KeyValuePair(_formatter.ValueNames[1], _value1); + case 2: + return new KeyValuePair(_formatter.ValueNames[2], _value2); + case 3: + return new KeyValuePair("{OriginalFormat}", _formatter.OriginalFormat); + default: + throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1, T2 value2) + { + _formatter = formatter; + _value0 = value0; + _value1 = value1; + _value2 = value2; + } + + public override string ToString() => _formatter.Format(_value0, _value1, _value2); + + public IEnumerator> GetEnumerator() + { + for (int i = 0; i < Count; ++i) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + private readonly struct LogValues : IReadOnlyList> + { + public static readonly Func, Exception?, string> Callback = (state, exception) => state.ToString(); + + private readonly LogValuesFormatter _formatter; + private readonly T0 _value0; + private readonly T1 _value1; + private readonly T2 _value2; + private readonly T3 _value3; + + public int Count => 5; + + public KeyValuePair this[int index] + { + get + { + switch (index) + { + case 0: + return new KeyValuePair(_formatter.ValueNames[0], _value0); + case 1: + return new KeyValuePair(_formatter.ValueNames[1], _value1); + case 2: + return new KeyValuePair(_formatter.ValueNames[2], _value2); + case 3: + return new KeyValuePair(_formatter.ValueNames[3], _value3); + case 4: + return new KeyValuePair("{OriginalFormat}", _formatter.OriginalFormat); + default: + throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1, T2 value2, T3 value3) + { + _formatter = formatter; + _value0 = value0; + _value1 = value1; + _value2 = value2; + _value3 = value3; + } + + private object?[] ToArray() => new object?[] { _value0, _value1, _value2, _value3 }; + + public override string ToString() => _formatter.FormatWithOverwrite(ToArray()); + + public IEnumerator> GetEnumerator() + { + for (int i = 0; i < Count; ++i) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + private readonly struct LogValues : IReadOnlyList> + { + public static readonly Func, Exception?, string> Callback = (state, exception) => state.ToString(); + + private readonly LogValuesFormatter _formatter; + private readonly T0 _value0; + private readonly T1 _value1; + private readonly T2 _value2; + private readonly T3 _value3; + private readonly T4 _value4; + + public int Count => 6; + + public KeyValuePair this[int index] + { + get + { + switch (index) + { + case 0: + return new KeyValuePair(_formatter.ValueNames[0], _value0); + case 1: + return new KeyValuePair(_formatter.ValueNames[1], _value1); + case 2: + return new KeyValuePair(_formatter.ValueNames[2], _value2); + case 3: + return new KeyValuePair(_formatter.ValueNames[3], _value3); + case 4: + return new KeyValuePair(_formatter.ValueNames[4], _value4); + case 5: + return new KeyValuePair("{OriginalFormat}", _formatter.OriginalFormat); + default: + throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1, T2 value2, T3 value3, T4 value4) + { + _formatter = formatter; + _value0 = value0; + _value1 = value1; + _value2 = value2; + _value3 = value3; + _value4 = value4; + } + + private object?[] ToArray() => new object?[] { _value0, _value1, _value2, _value3, _value4 }; + + public override string ToString() => _formatter.FormatWithOverwrite(ToArray()); + + public IEnumerator> GetEnumerator() + { + for (int i = 0; i < Count; ++i) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + private readonly struct LogValues : IReadOnlyList> + { + public static readonly Func, Exception?, string> Callback = (state, exception) => state.ToString(); + + private readonly LogValuesFormatter _formatter; + private readonly T0 _value0; + private readonly T1 _value1; + private readonly T2 _value2; + private readonly T3 _value3; + private readonly T4 _value4; + private readonly T5 _value5; + + public int Count => 7; + + public KeyValuePair this[int index] + { + get + { + switch (index) + { + case 0: + return new KeyValuePair(_formatter.ValueNames[0], _value0); + case 1: + return new KeyValuePair(_formatter.ValueNames[1], _value1); + case 2: + return new KeyValuePair(_formatter.ValueNames[2], _value2); + case 3: + return new KeyValuePair(_formatter.ValueNames[3], _value3); + case 4: + return new KeyValuePair(_formatter.ValueNames[4], _value4); + case 5: + return new KeyValuePair(_formatter.ValueNames[5], _value5); + case 6: + return new KeyValuePair("{OriginalFormat}", _formatter.OriginalFormat); + default: + throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) + { + _formatter = formatter; + _value0 = value0; + _value1 = value1; + _value2 = value2; + _value3 = value3; + _value4 = value4; + _value5 = value5; + } + + private object?[] ToArray() => new object?[] { _value0, _value1, _value2, _value3, _value4, _value5 }; + + public override string ToString() => _formatter.FormatWithOverwrite(ToArray()); + + public IEnumerator> GetEnumerator() + { + for (int i = 0; i < Count; ++i) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } } } From 451c2d0dc5613b5813b378d23d966f905590ea0d Mon Sep 17 00:00:00 2001 From: David Vreony Date: Sat, 6 Aug 2022 11:28:08 +0100 Subject: [PATCH 2/6] add comments and attribution --- .../Logging/LoggerMessageFactory.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs b/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs index e37f7c309..f7e37d67d 100644 --- a/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs +++ b/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs @@ -10,8 +10,21 @@ namespace Whipstaff.Core.Logging { /// - /// Factory Methods for Logger Messages. + /// Factory Methods for Logger Messages. These are aimed at simplifying some use cases for logging. + /// + /// There are methods that allow passing in functions to the logging so that you can delay the generation \ evaluation + /// of any output. This can help with avoiding expensive operations when a log level may not be enabled. + /// + /// There are also some common log message actions that are used in downstream code, including Roslyn Source generators + /// sat in Nucleotide. /// + /// + /// This code is based upon https://github.com/dotnet/runtime/blob/e8a85b78f804578729392acd9d6307918c3b23f5/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/LoggerMessage.cs + /// which carries the following license. + /// + /// Licensed to the .NET Foundation under one or more agreements. + /// The .NET Foundation licenses this file to you under the MIT license. + /// public static class LoggerMessageFactory { /// From 5ab6cafbb12c190bcd1dc9f94cced4f6974afd39 Mon Sep 17 00:00:00 2001 From: David Vreony Date: Sun, 7 Aug 2022 16:20:36 +0100 Subject: [PATCH 3/6] restore some old log enabled extensions --- src/Whipstaff.Core/Logging/LogExtensions.cs | 156 +++++++++++ .../Logging/LogValuesFormatter.cs | 252 ++++++++++++++++++ .../Logging/LoggerMessageFactory.cs | 66 +++-- 3 files changed, 456 insertions(+), 18 deletions(-) create mode 100644 src/Whipstaff.Core/Logging/LogValuesFormatter.cs diff --git a/src/Whipstaff.Core/Logging/LogExtensions.cs b/src/Whipstaff.Core/Logging/LogExtensions.cs index 9307c660a..a6986b1f7 100644 --- a/src/Whipstaff.Core/Logging/LogExtensions.cs +++ b/src/Whipstaff.Core/Logging/LogExtensions.cs @@ -54,5 +54,161 @@ public static class LogExtensions _traceMethodExitAction(logger, callerMemberName, exception); } + + /// + /// Write a trace event if the log level is enabled. + /// + /// Logging instance. + /// Message producing func to evaluate if log level enabled. + public static void TraceIfEnabled( + this ILogger logger, + Func messageFunc) + { + + logger.LogIfEnabled(LogLevel.Trace, messageFunc); + } + + /// + /// Write a trace event and exception if the log level is enabled. + /// + /// Logging instance. + /// Exception that occurred. + /// Message producing func to evaluate if log level enabled. + public static void TraceIfEnabled( + this ILogger logger, + Exception exception, + Func messageFunc) + { + logger.LogIfEnabled(LogLevel.Trace, exception, messageFunc); + } + + /// + /// Traces an exception in a method. + /// + /// Logging instance. + /// Exception that occurred. + /// Name of the method. + public static void TraceMethodException( + this ILogger logger, + Exception exception, + [CallerMemberName] string callerMemberName = null) + { + logger.TraceIfEnabled(exception, () => $"Method Entry: {callerMemberName}"); + } + + /// + /// Traces the method exit. + /// + /// Logging instance. + /// Name of the method. + public static void TraceMethodExit( + this ILogger logger, + [CallerMemberName] string callerMemberName = null) + { + logger.TraceIfEnabled(() => $"Method Exit: {callerMemberName}"); + } + + /// + /// Write a warning event if the log level is enabled. + /// + /// Logging instance. + /// Message producing func to evaluate if log level enabled. + public static void WarningIfEnabled( + this ILogger logger, + Func messageFunc) + { + logger.LogIfEnabled(LogLevel.Warning, messageFunc); + } + + /// + /// Write a warn event and exception if the log level is enabled. + /// + /// Logging instance. + /// Exception that occurred. + /// Message producing func to evaluate if log level enabled. + public static void WarningIfEnabled( + this ILogger logger, + Exception exception, + Func messageFunc) + { + logger.LogIfEnabled(LogLevel.Warning, exception, messageFunc); + } + + /// + /// Write a error event if the log level is enabled. + /// + /// Logging instance. + /// Message producing func to evaluate if log level enabled. + public static void ErrorIfEnabled( + this ILogger logger, + Func messageFunc) + { + logger.LogIfEnabled(LogLevel.Error, messageFunc); + } + + /// + /// Write a information event if the log level is enabled. + /// + /// Logging instance. + /// Message producing func to evaluate if log level enabled. + public static void InformationIfEnabled( + this ILogger logger, + Func messageFunc) + { + logger.LogIfEnabled(LogLevel.Information, messageFunc); + } + + /// + /// Write a debug event if the log level is enabled. + /// + /// Logging instance. + /// Message producing func to evaluate if log level enabled. + public static void DebugIfEnabled( + this ILogger logger, + Func messageFunc) + { + logger.LogIfEnabled(LogLevel.Debug, messageFunc); + } + + /// + /// Write a critical event if the log level is enabled. + /// + /// Logging instance. + /// Message producing func to evaluate if log level enabled. + public static void CriticalIfEnabled( + this ILogger logger, + Func messageFunc) + { + logger.LogIfEnabled(LogLevel.Critical, messageFunc); + } + + private static void LogIfEnabled( + this ILogger logger, + LogLevel logLevel, + Exception exception, + Func messageFunc) + { + if (!logger.IsEnabled(logLevel)) + { + return; + } + + var message = messageFunc(); + logger.Log(logLevel, exception, message); + } + + private static void LogIfEnabled( + this ILogger logger, + LogLevel logLevel, + Func messageFunc) + { + if (!logger.IsEnabled(logLevel)) + { + return; + } + + var message = messageFunc(); + logger.Log(logLevel, message); + } } } diff --git a/src/Whipstaff.Core/Logging/LogValuesFormatter.cs b/src/Whipstaff.Core/Logging/LogValuesFormatter.cs new file mode 100644 index 000000000..a575992c3 --- /dev/null +++ b/src/Whipstaff.Core/Logging/LogValuesFormatter.cs @@ -0,0 +1,252 @@ +#pragma warning disable SA1636 // File header copyright text should match + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#pragma warning restore SA1636 // File header copyright text should match + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace Whipstaff.Core.Logging +{ + /// + /// Formatter to convert the named format items like {NamedformatItem} to format. + /// + internal sealed class LogValuesFormatter + { + private const string NullValue = "(null)"; + private static readonly char[] FormatDelimiters = { ',', ':' }; + private readonly string _format; + private readonly List _valueNames = new List(); + + public LogValuesFormatter(string format) + { + ThrowHelper.ThrowIfNull(format); + + OriginalFormat = format; + + var vsb = new ValueStringBuilder(stackalloc char[256]); + int scanIndex = 0; + int endIndex = format.Length; + + while (scanIndex < endIndex) + { + int openBraceIndex = FindBraceIndex(format, '{', scanIndex, endIndex); + if (scanIndex == 0 && openBraceIndex == endIndex) + { + // No holes found. + _format = format; + return; + } + + int closeBraceIndex = FindBraceIndex(format, '}', openBraceIndex, endIndex); + + if (closeBraceIndex == endIndex) + { + vsb.Append(format.AsSpan(scanIndex, endIndex - scanIndex)); + scanIndex = endIndex; + } + else + { + // Format item syntax : { index[,alignment][ :formatString] }. + int formatDelimiterIndex = FindIndexOfAny(format, FormatDelimiters, openBraceIndex, closeBraceIndex); + + vsb.Append(format.AsSpan(scanIndex, openBraceIndex - scanIndex + 1)); + vsb.Append(_valueNames.Count.ToString()); + _valueNames.Add(format.Substring(openBraceIndex + 1, formatDelimiterIndex - openBraceIndex - 1)); + vsb.Append(format.AsSpan(formatDelimiterIndex, closeBraceIndex - formatDelimiterIndex + 1)); + + scanIndex = closeBraceIndex + 1; + } + } + + _format = vsb.ToString(); + } + + public string OriginalFormat { get; private set; } + + public List ValueNames => _valueNames; + + private static int FindBraceIndex(string format, char brace, int startIndex, int endIndex) + { + // Example: {{prefix{{{Argument}}}suffix}}. + int braceIndex = endIndex; + int scanIndex = startIndex; + int braceOccurrenceCount = 0; + + while (scanIndex < endIndex) + { + if (braceOccurrenceCount > 0 && format[scanIndex] != brace) + { + if (braceOccurrenceCount % 2 == 0) + { + // Even number of '{' or '}' found. Proceed search with next occurrence of '{' or '}'. + braceOccurrenceCount = 0; + braceIndex = endIndex; + } + else + { + // An unescaped '{' or '}' found. + break; + } + } + else if (format[scanIndex] == brace) + { + if (brace == '}') + { + if (braceOccurrenceCount == 0) + { + // For '}' pick the first occurrence. + braceIndex = scanIndex; + } + } + else + { + // For '{' pick the last occurrence. + braceIndex = scanIndex; + } + + braceOccurrenceCount++; + } + + scanIndex++; + } + + return braceIndex; + } + + private static int FindIndexOfAny(string format, char[] chars, int startIndex, int endIndex) + { + int findIndex = format.IndexOfAny(chars, startIndex, endIndex - startIndex); + return findIndex == -1 ? endIndex : findIndex; + } + + public string Format(object?[]? values) + { + object?[]? formattedValues = values; + + if (values != null) + { + for (int i = 0; i < values.Length; i++) + { + object formattedValue = FormatArgument(values[i]); + +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line + // If the formatted value is changed, we allocate and copy items to a new array to avoid mutating the array passed in to this method + if (!ReferenceEquals(formattedValue, values[i])) + { + formattedValues = new object[values.Length]; + Array.Copy(values, formattedValues, i); + formattedValues[i++] = formattedValue; + for (; i < values.Length; i++) + { + formattedValues[i] = FormatArgument(values[i]); + } + break; + } +#pragma warning restore SA1515 // Single-line comment should be preceded by blank line + } + } + + return string.Format(CultureInfo.InvariantCulture, _format, formattedValues ?? Array.Empty()); + } + + // NOTE: This method mutates the items in the array if needed to avoid extra allocations, and should only be used when caller expects this to happen + internal string FormatWithOverwrite(object?[]? values) + { + if (values != null) + { + for (int i = 0; i < values.Length; i++) + { + values[i] = FormatArgument(values[i]); + } + } + + return string.Format(CultureInfo.InvariantCulture, _format, values ?? Array.Empty()); + } + + internal string Format() + { + return _format; + } + + internal string Format(object? arg0) + { + return string.Format(CultureInfo.InvariantCulture, _format, FormatArgument(arg0)); + } + + internal string Format(object? arg0, object? arg1) + { + return string.Format(CultureInfo.InvariantCulture, _format, FormatArgument(arg0), FormatArgument(arg1)); + } + + internal string Format(object? arg0, object? arg1, object? arg2) + { + return string.Format(CultureInfo.InvariantCulture, _format, FormatArgument(arg0), FormatArgument(arg1), FormatArgument(arg2)); + } + + public KeyValuePair GetValue(object?[] values, int index) + { + if (index < 0 || index > _valueNames.Count) + { + throw new IndexOutOfRangeException(nameof(index)); + } + + if (_valueNames.Count > index) + { + return new KeyValuePair(_valueNames[index], values[index]); + } + + return new KeyValuePair("{OriginalFormat}", OriginalFormat); + } + + public IEnumerable> GetValues(object[] values) + { + var valueArray = new KeyValuePair[values.Length + 1]; + for (int index = 0; index != _valueNames.Count; ++index) + { + valueArray[index] = new KeyValuePair(_valueNames[index], values[index]); + } + + valueArray[valueArray.Length - 1] = new KeyValuePair("{OriginalFormat}", OriginalFormat); + return valueArray; + } + + private object FormatArgument(object? value) + { + if (value == null) + { + return NullValue; + } + + // since 'string' implements IEnumerable, special case it + if (value is string) + { + return value; + } + + // if the value implements IEnumerable, build a comma separated string. + if (value is IEnumerable enumerable) + { + var vsb = new ValueStringBuilder(stackalloc char[256]); + bool first = true; + foreach (object? e in enumerable) + { + if (!first) + { + vsb.Append(", "); + } + + vsb.Append(e != null ? e.ToString() : NullValue); + first = false; + } + return vsb.ToString(); + } + + return value; + } + } +} diff --git a/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs b/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs index f7e37d67d..a9b779088 100644 --- a/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs +++ b/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs @@ -21,7 +21,7 @@ namespace Whipstaff.Core.Logging /// /// This code is based upon https://github.com/dotnet/runtime/blob/e8a85b78f804578729392acd9d6307918c3b23f5/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/LoggerMessage.cs /// which carries the following license. - /// + /// /// Licensed to the .NET Foundation under one or more agreements. /// The .NET Foundation licenses this file to you under the MIT license. /// @@ -252,10 +252,10 @@ void Log(ILogger logger, Func arg1, Exception? exception) /// /// The type of the first parameter passed to the named format string. /// The type of the second parameter passed to the named format string. - /// The - /// The event id - /// The named format string - /// The + /// The . + /// The event id. + /// The named format string. + /// The . /// A delegate which when invoked creates a log message. public static Action, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) { @@ -286,9 +286,9 @@ void Log(ILogger logger, Func arg1, Func arg2, Exception? exception) /// The type of the first parameter passed to the named format string. /// The type of the second parameter passed to the named format string. /// The type of the third parameter passed to the named format string. - /// The - /// The event id - /// The named format string + /// The . + /// The event id. + /// The named format string. /// A delegate which when invoked creates a log message. public static Action, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) => DefineWithFunc(logLevel, eventId, formatString, options: null); @@ -334,9 +334,9 @@ void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Exception? /// The type of the second parameter passed to the named format string. /// The type of the third parameter passed to the named format string. /// The type of the fourth parameter passed to the named format string. - /// The - /// The event id - /// The named format string + /// The . + /// The event id. + /// The named format string. /// A delegate which when invoked creates a log message. public static Action, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) => DefineWithFunc(logLevel, eventId, formatString, options: null); @@ -436,9 +436,9 @@ void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Func a /// The type of the fourth parameter passed to the named format string. /// The type of the fifth parameter passed to the named format string. /// The type of the sixth parameter passed to the named format string. - /// The - /// The event id - /// The named format string + /// The . + /// The event id. + /// The named format string. /// A delegate which when invoked creates a log message. public static Action, Func, Func, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) => DefineWithFunc(logLevel, eventId, formatString, options: null); @@ -452,10 +452,10 @@ void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Func a /// The type of the fourth parameter passed to the named format string. /// The type of the fifth parameter passed to the named format string. /// The type of the sixth parameter passed to the named format string. - /// The - /// The event id - /// The named format string - /// The + /// The . + /// The event id. + /// The named format string. + /// The . /// A delegate which when invoked creates a log message. public static Action, Func, Func, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) { @@ -518,12 +518,16 @@ public LogValues(LogValuesFormatter formatter, T0 value0) case 1: return new KeyValuePair("{OriginalFormat}", _formatter.OriginalFormat); default: +#pragma warning disable CA2201 // Do not raise reserved exception types throw new IndexOutOfRangeException(nameof(index)); +#pragma warning restore CA2201 // Do not raise reserved exception types } } } +#pragma warning disable SA1201 // Elements should appear in the correct order public int Count => 2; +#pragma warning restore SA1201 // Elements should appear in the correct order public IEnumerator> GetEnumerator() { @@ -569,12 +573,16 @@ public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1) case 2: return new KeyValuePair("{OriginalFormat}", _formatter.OriginalFormat); default: +#pragma warning disable CA2201 // Do not raise reserved exception types throw new IndexOutOfRangeException(nameof(index)); +#pragma warning restore CA2201 // Do not raise reserved exception types } } } +#pragma warning disable SA1201 // Elements should appear in the correct order public int Count => 3; +#pragma warning restore SA1201 // Elements should appear in the correct order public IEnumerator> GetEnumerator() { @@ -618,12 +626,16 @@ IEnumerator IEnumerable.GetEnumerator() case 3: return new KeyValuePair("{OriginalFormat}", _formatter.OriginalFormat); default: +#pragma warning disable CA2201 // Do not raise reserved exception types throw new IndexOutOfRangeException(nameof(index)); +#pragma warning restore CA2201 // Do not raise reserved exception types } } } +#pragma warning disable SA1201 // Elements should appear in the correct order public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1, T2 value2) +#pragma warning restore SA1201 // Elements should appear in the correct order { _formatter = formatter; _value0 = value0; @@ -676,12 +688,16 @@ IEnumerator IEnumerable.GetEnumerator() case 4: return new KeyValuePair("{OriginalFormat}", _formatter.OriginalFormat); default: +#pragma warning disable CA2201 // Do not raise reserved exception types throw new IndexOutOfRangeException(nameof(index)); +#pragma warning restore CA2201 // Do not raise reserved exception types } } } +#pragma warning disable SA1201 // Elements should appear in the correct order public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1, T2 value2, T3 value3) +#pragma warning restore SA1201 // Elements should appear in the correct order { _formatter = formatter; _value0 = value0; @@ -692,7 +708,9 @@ public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1, T2 value2, private object?[] ToArray() => new object?[] { _value0, _value1, _value2, _value3 }; +#pragma warning disable SA1202 // Elements should be ordered by access public override string ToString() => _formatter.FormatWithOverwrite(ToArray()); +#pragma warning restore SA1202 // Elements should be ordered by access public IEnumerator> GetEnumerator() { @@ -740,12 +758,16 @@ IEnumerator IEnumerable.GetEnumerator() case 5: return new KeyValuePair("{OriginalFormat}", _formatter.OriginalFormat); default: +#pragma warning disable CA2201 // Do not raise reserved exception types throw new IndexOutOfRangeException(nameof(index)); +#pragma warning restore CA2201 // Do not raise reserved exception types } } } +#pragma warning disable SA1201 // Elements should appear in the correct order public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1, T2 value2, T3 value3, T4 value4) +#pragma warning restore SA1201 // Elements should appear in the correct order { _formatter = formatter; _value0 = value0; @@ -757,7 +779,9 @@ public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1, T2 value2, private object?[] ToArray() => new object?[] { _value0, _value1, _value2, _value3, _value4 }; +#pragma warning disable SA1202 // Elements should be ordered by access public override string ToString() => _formatter.FormatWithOverwrite(ToArray()); +#pragma warning restore SA1202 // Elements should be ordered by access public IEnumerator> GetEnumerator() { @@ -808,12 +832,16 @@ IEnumerator IEnumerable.GetEnumerator() case 6: return new KeyValuePair("{OriginalFormat}", _formatter.OriginalFormat); default: +#pragma warning disable CA2201 // Do not raise reserved exception types throw new IndexOutOfRangeException(nameof(index)); +#pragma warning restore CA2201 // Do not raise reserved exception types } } } +#pragma warning disable SA1201 // Elements should appear in the correct order public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) +#pragma warning restore SA1201 // Elements should appear in the correct order { _formatter = formatter; _value0 = value0; @@ -826,7 +854,9 @@ public LogValues(LogValuesFormatter formatter, T0 value0, T1 value1, T2 value2, private object?[] ToArray() => new object?[] { _value0, _value1, _value2, _value3, _value4, _value5 }; +#pragma warning disable SA1202 // Elements should be ordered by access public override string ToString() => _formatter.FormatWithOverwrite(ToArray()); +#pragma warning restore SA1202 // Elements should be ordered by access public IEnumerator> GetEnumerator() { From 5fde4ac1156273ae35ca725735c9d817858458e7 Mon Sep 17 00:00:00 2001 From: David Vreony Date: Sun, 7 Aug 2022 21:30:47 +0100 Subject: [PATCH 4/6] build is green for logging --- src/Whipstaff.Core/Logging/LogExtensions.cs | 111 ++++- .../Logging/LogValuesFormatter.cs | 16 +- .../Logging/LoggerMessageFactory.cs | 3 +- src/Whipstaff.Core/Logging/SR.cs | 24 ++ src/Whipstaff.Core/Logging/ThrowHelper.cs | 59 +++ .../Logging/ValueStringBuilder.cs | 350 ++++++++++++++++ .../Core/Logging/LogExtensionsTest.cs | 385 ++++++++++++++++++ 7 files changed, 932 insertions(+), 16 deletions(-) create mode 100644 src/Whipstaff.Core/Logging/SR.cs create mode 100644 src/Whipstaff.Core/Logging/ThrowHelper.cs create mode 100644 src/Whipstaff.Core/Logging/ValueStringBuilder.cs create mode 100644 src/Whipstaff.UnitTests/Core/Logging/LogExtensionsTest.cs diff --git a/src/Whipstaff.Core/Logging/LogExtensions.cs b/src/Whipstaff.Core/Logging/LogExtensions.cs index a6986b1f7..f29630c28 100644 --- a/src/Whipstaff.Core/Logging/LogExtensions.cs +++ b/src/Whipstaff.Core/Logging/LogExtensions.cs @@ -19,6 +19,9 @@ public static class LogExtensions private static readonly Action _traceMethodExitAction = LoggerMessage.Define(LogLevel.Trace, EventIdFactory.MethodEntryEventId(), "Method Exit: {MethodName}"); + private static readonly Action _traceMethodExceptionAction = + LoggerMessage.Define(LogLevel.Trace, EventIdFactory.MethodEntryEventId(), "Method Exception: {MethodName}"); + /// /// Traces the method entry. /// @@ -64,8 +67,9 @@ public static class LogExtensions this ILogger logger, Func messageFunc) { - +#pragma warning disable CA1062 // Validate arguments of public methods logger.LogIfEnabled(LogLevel.Trace, messageFunc); +#pragma warning restore CA1062 // Validate arguments of public methods } /// @@ -79,7 +83,9 @@ public static class LogExtensions Exception exception, Func messageFunc) { +#pragma warning disable CA1062 // Validate arguments of public methods logger.LogIfEnabled(LogLevel.Trace, exception, messageFunc); +#pragma warning restore CA1062 // Validate arguments of public methods } /// @@ -91,21 +97,14 @@ public static class LogExtensions public static void TraceMethodException( this ILogger logger, Exception exception, - [CallerMemberName] string callerMemberName = null) + [CallerMemberName] string? callerMemberName = null) { - logger.TraceIfEnabled(exception, () => $"Method Entry: {callerMemberName}"); - } + if (string.IsNullOrWhiteSpace(callerMemberName)) + { + return; + } - /// - /// Traces the method exit. - /// - /// Logging instance. - /// Name of the method. - public static void TraceMethodExit( - this ILogger logger, - [CallerMemberName] string callerMemberName = null) - { - logger.TraceIfEnabled(() => $"Method Exit: {callerMemberName}"); + _traceMethodExceptionAction(logger, callerMemberName, exception); } /// @@ -117,7 +116,9 @@ public static class LogExtensions this ILogger logger, Func messageFunc) { +#pragma warning disable CA1062 // Validate arguments of public methods logger.LogIfEnabled(LogLevel.Warning, messageFunc); +#pragma warning restore CA1062 // Validate arguments of public methods } /// @@ -131,7 +132,9 @@ public static class LogExtensions Exception exception, Func messageFunc) { +#pragma warning disable CA1062 // Validate arguments of public methods logger.LogIfEnabled(LogLevel.Warning, exception, messageFunc); +#pragma warning restore CA1062 // Validate arguments of public methods } /// @@ -143,7 +146,25 @@ public static class LogExtensions this ILogger logger, Func messageFunc) { +#pragma warning disable CA1062 // Validate arguments of public methods logger.LogIfEnabled(LogLevel.Error, messageFunc); +#pragma warning restore CA1062 // Validate arguments of public methods + } + + /// + /// Write a error event if the log level is enabled. + /// + /// Logging instance. + /// Exception that occurred. + /// Message producing func to evaluate if log level enabled. + public static void ErrorIfEnabled( + this ILogger logger, + Exception exception, + Func messageFunc) + { +#pragma warning disable CA1062 // Validate arguments of public methods + logger.LogIfEnabled(LogLevel.Error, exception, messageFunc); +#pragma warning restore CA1062 // Validate arguments of public methods } /// @@ -155,7 +176,25 @@ public static class LogExtensions this ILogger logger, Func messageFunc) { +#pragma warning disable CA1062 // Validate arguments of public methods logger.LogIfEnabled(LogLevel.Information, messageFunc); +#pragma warning restore CA1062 // Validate arguments of public methods + } + + /// + /// Write a information event if the log level is enabled. + /// + /// Logging instance. + /// Exception that occurred. + /// Message producing func to evaluate if log level enabled. + public static void InformationIfEnabled( + this ILogger logger, + Exception exception, + Func messageFunc) + { +#pragma warning disable CA1062 // Validate arguments of public methods + logger.LogIfEnabled(LogLevel.Information, exception, messageFunc); +#pragma warning restore CA1062 // Validate arguments of public methods } /// @@ -167,7 +206,25 @@ public static class LogExtensions this ILogger logger, Func messageFunc) { +#pragma warning disable CA1062 // Validate arguments of public methods logger.LogIfEnabled(LogLevel.Debug, messageFunc); +#pragma warning restore CA1062 // Validate arguments of public methods + } + + /// + /// Write a debug event if the log level is enabled. + /// + /// Logging instance. + /// Exception that occurred. + /// Message producing func to evaluate if log level enabled. + public static void DebugIfEnabled( + this ILogger logger, + Exception exception, + Func messageFunc) + { +#pragma warning disable CA1062 // Validate arguments of public methods + logger.LogIfEnabled(LogLevel.Debug, exception, messageFunc); +#pragma warning restore CA1062 // Validate arguments of public methods } /// @@ -179,7 +236,25 @@ public static class LogExtensions this ILogger logger, Func messageFunc) { +#pragma warning disable CA1062 // Validate arguments of public methods logger.LogIfEnabled(LogLevel.Critical, messageFunc); +#pragma warning restore CA1062 // Validate arguments of public methods + } + + /// + /// Write a critical event if the log level is enabled. + /// + /// Logging instance. + /// Exception that occurred. + /// Message producing func to evaluate if log level enabled. + public static void CriticalIfEnabled( + this ILogger logger, + Exception exception, + Func messageFunc) + { +#pragma warning disable CA1062 // Validate arguments of public methods + logger.LogIfEnabled(LogLevel.Critical, exception, messageFunc); +#pragma warning restore CA1062 // Validate arguments of public methods } private static void LogIfEnabled( @@ -194,7 +269,11 @@ public static class LogExtensions } var message = messageFunc(); +#pragma warning disable CA1848 // Use the LoggerMessage delegates +#pragma warning disable CA2254 // Template should be a static expression logger.Log(logLevel, exception, message); +#pragma warning restore CA2254 // Template should be a static expression +#pragma warning restore CA1848 // Use the LoggerMessage delegates } private static void LogIfEnabled( @@ -208,7 +287,11 @@ public static class LogExtensions } var message = messageFunc(); +#pragma warning disable CA1848 // Use the LoggerMessage delegates +#pragma warning disable CA2254 // Template should be a static expression logger.Log(logLevel, message); +#pragma warning restore CA2254 // Template should be a static expression +#pragma warning restore CA1848 // Use the LoggerMessage delegates } } } diff --git a/src/Whipstaff.Core/Logging/LogValuesFormatter.cs b/src/Whipstaff.Core/Logging/LogValuesFormatter.cs index a575992c3..29e94a612 100644 --- a/src/Whipstaff.Core/Logging/LogValuesFormatter.cs +++ b/src/Whipstaff.Core/Logging/LogValuesFormatter.cs @@ -7,6 +7,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Text; @@ -15,6 +16,7 @@ namespace Whipstaff.Core.Logging /// /// Formatter to convert the named format items like {NamedformatItem} to format. /// + [ExcludeFromCodeCoverage] internal sealed class LogValuesFormatter { private const string NullValue = "(null)"; @@ -28,7 +30,9 @@ public LogValuesFormatter(string format) OriginalFormat = format; +#pragma warning disable CA2000 // Dispose objects before losing scope var vsb = new ValueStringBuilder(stackalloc char[256]); +#pragma warning restore CA2000 // Dispose objects before losing scope int scanIndex = 0; int endIndex = format.Length; @@ -55,7 +59,7 @@ public LogValuesFormatter(string format) int formatDelimiterIndex = FindIndexOfAny(format, FormatDelimiters, openBraceIndex, closeBraceIndex); vsb.Append(format.AsSpan(scanIndex, openBraceIndex - scanIndex + 1)); - vsb.Append(_valueNames.Count.ToString()); + vsb.Append(_valueNames.Count.ToString(NumberFormatInfo.InvariantInfo)); _valueNames.Add(format.Substring(openBraceIndex + 1, formatDelimiterIndex - openBraceIndex - 1)); vsb.Append(format.AsSpan(formatDelimiterIndex, closeBraceIndex - formatDelimiterIndex + 1)); @@ -124,7 +128,9 @@ private static int FindIndexOfAny(string format, char[] chars, int startIndex, i return findIndex == -1 ? endIndex : findIndex; } +#pragma warning disable SA1202 // Elements should be ordered by access public string Format(object?[]? values) +#pragma warning restore SA1202 // Elements should be ordered by access { object?[]? formattedValues = values; @@ -145,6 +151,7 @@ public string Format(object?[]? values) { formattedValues[i] = FormatArgument(values[i]); } + break; } #pragma warning restore SA1515 // Single-line comment should be preceded by blank line @@ -188,11 +195,15 @@ internal string Format(object? arg0, object? arg1, object? arg2) return string.Format(CultureInfo.InvariantCulture, _format, FormatArgument(arg0), FormatArgument(arg1), FormatArgument(arg2)); } +#pragma warning disable SA1202 // Elements should be ordered by access public KeyValuePair GetValue(object?[] values, int index) +#pragma warning restore SA1202 // Elements should be ordered by access { if (index < 0 || index > _valueNames.Count) { +#pragma warning disable CA2201 // Do not raise reserved exception types throw new IndexOutOfRangeException(nameof(index)); +#pragma warning restore CA2201 // Do not raise reserved exception types } if (_valueNames.Count > index) @@ -231,7 +242,9 @@ private object FormatArgument(object? value) // if the value implements IEnumerable, build a comma separated string. if (value is IEnumerable enumerable) { +#pragma warning disable CA2000 // Dispose objects before losing scope var vsb = new ValueStringBuilder(stackalloc char[256]); +#pragma warning restore CA2000 // Dispose objects before losing scope bool first = true; foreach (object? e in enumerable) { @@ -243,6 +256,7 @@ private object FormatArgument(object? value) vsb.Append(e != null ? e.ToString() : NullValue); first = false; } + return vsb.ToString(); } diff --git a/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs b/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs index a9b779088..c565352ef 100644 --- a/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs +++ b/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs @@ -5,6 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Globalization; using Microsoft.Extensions.Logging; namespace Whipstaff.Core.Logging @@ -488,7 +489,7 @@ private static LogValuesFormatter CreateLogValuesFormatter(string formatString, if (actualCount != expectedNamedParameterCount) { throw new ArgumentException( - SR.Format(SR.UnexpectedNumberOfNamedParameters, formatString, expectedNamedParameterCount, actualCount)); + SR.Format(CultureInfo.InvariantCulture, "The format string '{0}' does not have the expected number of named parameters. Expected {1} parameter(s) but found {2} parameter(s).", formatString, expectedNamedParameterCount, actualCount)); } return logValuesFormatter; diff --git a/src/Whipstaff.Core/Logging/SR.cs b/src/Whipstaff.Core/Logging/SR.cs new file mode 100644 index 000000000..cd999b895 --- /dev/null +++ b/src/Whipstaff.Core/Logging/SR.cs @@ -0,0 +1,24 @@ +#pragma warning disable SA1636 // File header copyright text should match + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#pragma warning restore SA1636 // File header copyright text should match +using System.Diagnostics.CodeAnalysis; +using System.Resources; + +namespace System +{ + [ExcludeFromCodeCoverage] + internal static partial class SR + { + internal static string Format(IFormatProvider? provider, string resourceFormat, params object?[]? args) + { + if (args != null) + { + return string.Format(provider, resourceFormat, args); + } + + return resourceFormat; + } + } +} diff --git a/src/Whipstaff.Core/Logging/ThrowHelper.cs b/src/Whipstaff.Core/Logging/ThrowHelper.cs new file mode 100644 index 000000000..dfc2c4471 --- /dev/null +++ b/src/Whipstaff.Core/Logging/ThrowHelper.cs @@ -0,0 +1,59 @@ +#pragma warning disable SA1636 // File header copyright text should match + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#pragma warning restore SA1636 // File header copyright text should match +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +// This file is intended to be used by components that don't have access to ArgumentNullException.ThrowIfNull. +#pragma warning disable CS0436 // Type conflicts with imported type + +namespace System +{ + [ExcludeFromCodeCoverage] + internal static partial class ThrowHelper + { + /// Throws an if is null. + /// The reference type argument to validate as non-null. + /// The name of the parameter with which corresponds. + internal static void ThrowIfNull( +#if NETCOREAPP3_0_OR_GREATER + [NotNull] +#endif + object? argument, + [CallerArgumentExpression("argument")] string? paramName = null) + { + if (argument is null) + { + Throw(paramName); + } + } + +#if NETCOREAPP3_0_OR_GREATER + [DoesNotReturn] +#endif + private static void Throw(string? paramName) => throw new ArgumentNullException(paramName); + } +} + +#if !NETCOREAPP3_0_OR_GREATER +#pragma warning disable SA1403 // File may only contain a single namespace +namespace System.Runtime.CompilerServices +#pragma warning restore SA1403 // File may only contain a single namespace +{ + [ExcludeFromCodeCoverage] + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] +#pragma warning disable SA1402 // File may only contain a single type + internal sealed class CallerArgumentExpressionAttribute : Attribute +#pragma warning restore SA1402 // File may only contain a single type + { + public CallerArgumentExpressionAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; } + } +} +#endif diff --git a/src/Whipstaff.Core/Logging/ValueStringBuilder.cs b/src/Whipstaff.Core/Logging/ValueStringBuilder.cs new file mode 100644 index 000000000..bdad12e55 --- /dev/null +++ b/src/Whipstaff.Core/Logging/ValueStringBuilder.cs @@ -0,0 +1,350 @@ +#pragma warning disable SA1636 // File header copyright text should match + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#pragma warning restore SA1636 // File header copyright text should match + +using System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Text +{ + [ExcludeFromCodeCoverage] + internal ref partial struct ValueStringBuilder + { + private char[]? _arrayToReturnToPool; + private Span _chars; + private int _pos; + + public ValueStringBuilder(Span initialBuffer) + { + _arrayToReturnToPool = null; + _chars = initialBuffer; + _pos = 0; + } + + public ValueStringBuilder(int initialCapacity) + { + _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity); + _chars = _arrayToReturnToPool; + _pos = 0; + } + + public int Length + { + get => _pos; + set + { +#pragma warning disable SA1405 // Debug.Assert should provide message text + Debug.Assert(value >= 0); +#pragma warning restore SA1405 // Debug.Assert should provide message text +#pragma warning disable SA1405 // Debug.Assert should provide message text + Debug.Assert(value <= _chars.Length); +#pragma warning restore SA1405 // Debug.Assert should provide message text + _pos = value; + } + } + + public int Capacity => _chars.Length; + + public void EnsureCapacity(int capacity) + { + // This is not expected to be called this with negative capacity +#pragma warning disable SA1405 // Debug.Assert should provide message text + Debug.Assert(capacity >= 0); +#pragma warning restore SA1405 // Debug.Assert should provide message text + + // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception. + if ((uint)capacity > (uint)_chars.Length) +#pragma warning disable SA1503 // Braces should not be omitted + Grow(capacity - _pos); +#pragma warning restore SA1503 // Braces should not be omitted + } + + /// + /// Get a pinnable reference to the builder. + /// Does not ensure there is a null char after + /// This overload is pattern matched in the C# 7.3+ compiler so you can omit + /// the explicit method call, and write eg "fixed (char* c = builder)". + /// + public ref char GetPinnableReference() + { + return ref MemoryMarshal.GetReference(_chars); + } + + /// + /// Get a pinnable reference to the builder. + /// + /// Ensures that the builder has a null char after . + public ref char GetPinnableReference(bool terminate) + { + if (terminate) + { + EnsureCapacity(Length + 1); + _chars[Length] = '\0'; + } + + return ref MemoryMarshal.GetReference(_chars); + } + +#pragma warning disable SA1201 // Elements should appear in the correct order + public ref char this[int index] +#pragma warning restore SA1201 // Elements should appear in the correct order + { + get + { +#pragma warning disable SA1405 // Debug.Assert should provide message text + Debug.Assert(index < _pos); +#pragma warning restore SA1405 // Debug.Assert should provide message text + return ref _chars[index]; + } + } + + public override string ToString() + { + string s = _chars.Slice(0, _pos).ToString(); + Dispose(); + return s; + } + + /// Returns the underlying storage of the builder. +#pragma warning disable SA1201 // Elements should appear in the correct order +#pragma warning disable SA1623 // Property summary documentation should match accessors + public Span RawChars => _chars; +#pragma warning restore SA1623 // Property summary documentation should match accessors +#pragma warning restore SA1201 // Elements should appear in the correct order + + /// + /// Returns a span around the contents of the builder. + /// + /// Ensures that the builder has a null char after . + public ReadOnlySpan AsSpan(bool terminate) + { + if (terminate) + { + EnsureCapacity(Length + 1); + _chars[Length] = '\0'; + } + + return _chars.Slice(0, _pos); + } + + public ReadOnlySpan AsSpan() => _chars.Slice(0, _pos); + + public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start); + + public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length); + + public bool TryCopyTo(Span destination, out int charsWritten) + { +#pragma warning disable RCS1211 // Remove unnecessary 'else'. + if (_chars.Slice(0, _pos).TryCopyTo(destination)) + { + charsWritten = _pos; + Dispose(); + return true; + } + else + { + charsWritten = 0; + Dispose(); + return false; + } +#pragma warning restore RCS1211 // Remove unnecessary 'else'. + } + + public void Insert(int index, char value, int count) + { + if (_pos > _chars.Length - count) + { + Grow(count); + } + + int remaining = _pos - index; + _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); + _chars.Slice(index, count).Fill(value); + _pos += count; + } + + public void Insert(int index, string? s) + { + if (s == null) + { + return; + } + + int count = s.Length; + + if (_pos > (_chars.Length - count)) + { + Grow(count); + } + + int remaining = _pos - index; + _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); + s +#if !NETCOREAPP + .AsSpan() +#endif + .CopyTo(_chars.Slice(index)); + _pos += count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(char c) + { + int pos = _pos; + if ((uint)pos < (uint)_chars.Length) + { + _chars[pos] = c; + _pos = pos + 1; + } + else + { + GrowAndAppend(c); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(string? s) + { + if (s == null) + { + return; + } + + int pos = _pos; +#pragma warning disable SA1108 // Block statements should not contain embedded comments + if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. + { + _chars[pos] = s[0]; + _pos = pos + 1; + } + else + { + AppendSlow(s); + } +#pragma warning restore SA1108 // Block statements should not contain embedded comments + } + + private void AppendSlow(string s) + { + int pos = _pos; + if (pos > _chars.Length - s.Length) + { + Grow(s.Length); + } + + s +#if !NETCOREAPP + .AsSpan() +#endif + .CopyTo(_chars.Slice(pos)); + _pos += s.Length; + } + +#pragma warning disable SA1202 // Elements should be ordered by access + public void Append(char c, int count) +#pragma warning restore SA1202 // Elements should be ordered by access + { + if (_pos > _chars.Length - count) + { + Grow(count); + } + + Span dst = _chars.Slice(_pos, count); + for (int i = 0; i < dst.Length; i++) + { + dst[i] = c; + } + + _pos += count; + } + + public void Append(ReadOnlySpan value) + { + int pos = _pos; + if (pos > _chars.Length - value.Length) + { + Grow(value.Length); + } + + value.CopyTo(_chars.Slice(_pos)); + _pos += value.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AppendSpan(int length) + { + int origPos = _pos; + if (origPos > _chars.Length - length) + { + Grow(length); + } + + _pos = origPos + length; + return _chars.Slice(origPos, length); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void GrowAndAppend(char c) + { + Grow(1); + Append(c); + } + + /// + /// Resize the internal buffer either by doubling current buffer size or + /// by adding to + /// whichever is greater. + /// + /// + /// Number of chars requested beyond current position. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void Grow(int additionalCapacityBeyondPos) + { +#pragma warning disable SA1405 // Debug.Assert should provide message text + Debug.Assert(additionalCapacityBeyondPos > 0); +#pragma warning restore SA1405 // Debug.Assert should provide message text + Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed."); + + const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength + + // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try + // to double the size if possible, bounding the doubling to not go beyond the max array length. + int newCapacity = (int)Math.Max( + (uint)(_pos + additionalCapacityBeyondPos), + Math.Min((uint)_chars.Length * 2, ArrayMaxLength)); + + // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative. + // This could also go negative if the actual required length wraps around. + char[] poolArray = ArrayPool.Shared.Rent(newCapacity); + + _chars.Slice(0, _pos).CopyTo(poolArray); + + char[]? toReturn = _arrayToReturnToPool; + _chars = _arrayToReturnToPool = poolArray; + if (toReturn != null) + { + ArrayPool.Shared.Return(toReturn); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1202 // Elements should be ordered by access + public void Dispose() +#pragma warning restore SA1202 // Elements should be ordered by access + { + char[]? toReturn = _arrayToReturnToPool; + this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again + if (toReturn != null) + { + ArrayPool.Shared.Return(toReturn); + } + } + } +} diff --git a/src/Whipstaff.UnitTests/Core/Logging/LogExtensionsTest.cs b/src/Whipstaff.UnitTests/Core/Logging/LogExtensionsTest.cs new file mode 100644 index 000000000..317fc5b05 --- /dev/null +++ b/src/Whipstaff.UnitTests/Core/Logging/LogExtensionsTest.cs @@ -0,0 +1,385 @@ +// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. +// This file is licensed to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using Microsoft.Extensions.Logging; +using Whipstaff.Core.Logging; +using Xunit; +using Xunit.Abstractions; + +namespace Whipstaff.UnitTests.Core.Logging +{ + /// + /// Unit Tests for . + /// + public static class LogExtensionsTest + { + /// + /// Unit Tests for . + /// + public sealed class TraceMethodEntryMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public TraceMethodEntryMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + _logger.TraceMethodEntry(); + } + } + + /// + /// Unit Tests for . + /// + public sealed class TraceMethodExitMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public TraceMethodExitMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + _logger.TraceMethodExit(); + } + } + + /// + /// Unit Tests for . + /// + public sealed class TraceIfEnabledMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public TraceIfEnabledMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + _logger.TraceIfEnabled(() => "TEST"); + } + } + + /// + /// Unit Tests for . + /// + public sealed class TraceIfEnabledWithExceptionMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public TraceIfEnabledWithExceptionMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + var exception = new InvalidOperationException("Some test exception"); + _logger.TraceIfEnabled(exception, () => "TEST"); + } + } + + /// + /// Unit Tests for . + /// + public sealed class TraceMethodExceptionEnabledMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public TraceMethodExceptionEnabledMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + var exception = new InvalidOperationException("Some test exception"); + _logger.TraceMethodException(exception); + } + } + + /// + /// Unit Tests for . + /// + public sealed class WarningIfEnabledMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public WarningIfEnabledMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + _logger.WarningIfEnabled(() => "TEST"); + } + } + + /// + /// Unit Tests for . + /// + public sealed class WarningIfEnabledWithExceptionMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public WarningIfEnabledWithExceptionMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + var exception = new InvalidOperationException("Some test exception"); + _logger.WarningIfEnabled(exception, () => "TEST"); + } + } + + /// + /// Unit Tests for . + /// + public sealed class ErrorIfEnabledMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public ErrorIfEnabledMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + _logger.ErrorIfEnabled(() => "TEST"); + } + } + + /// + /// Unit Tests for . + /// + public sealed class ErrorIfEnabledWithExceptionMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public ErrorIfEnabledWithExceptionMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + var exception = new InvalidOperationException("Some test exception"); + _logger.ErrorIfEnabled(exception, () => "TEST"); + } + } + + /// + /// Unit Tests for . + /// + public sealed class InformationIfEnabledMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public InformationIfEnabledMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + _logger.InformationIfEnabled(() => "TEST"); + } + } + + /// + /// Unit Tests for . + /// + public sealed class InformationIfEnabledWithExceptionMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public InformationIfEnabledWithExceptionMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + var exception = new InvalidOperationException("Some test exception"); + _logger.InformationIfEnabled(exception, () => "TEST"); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DebugIfEnabledMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DebugIfEnabledMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + _logger.DebugIfEnabled(() => "TEST"); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DebugIfEnabledWithExceptionMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DebugIfEnabledWithExceptionMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + var exception = new InvalidOperationException("Some test exception"); + _logger.DebugIfEnabled(exception, () => "TEST"); + } + } + + /// + /// Unit Tests for . + /// + public sealed class CriticalIfEnabledMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public CriticalIfEnabledMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + _logger.CriticalIfEnabled(() => "TEST"); + } + } + + /// + /// Unit Tests for . + /// + public sealed class CriticalIfEnabledWithExceptionMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public CriticalIfEnabledWithExceptionMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure the log event occurs. + /// + [Fact] + public void LogsMessage() + { + var exception = new InvalidOperationException("Some test exception"); + _logger.CriticalIfEnabled(exception, () => "TEST"); + } + } + } +} From b08e703d61354447ed9eb6aba9d478918ba849f5 Mon Sep 17 00:00:00 2001 From: David Vreony Date: Sun, 7 Aug 2022 22:46:02 +0100 Subject: [PATCH 5/6] factory test prep --- .../Logging/LoggerMessageFactory.cs | 58 +-- .../Core/Logging/LoggerMessageFactoryTests.cs | 450 ++++++++++++++++++ 2 files changed, 479 insertions(+), 29 deletions(-) create mode 100644 src/Whipstaff.UnitTests/Core/Logging/LoggerMessageFactoryTests.cs diff --git a/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs b/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs index c565352ef..78571d5dd 100644 --- a/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs +++ b/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs @@ -73,7 +73,7 @@ public static class LoggerMessageFactory /// The event id to define a log message action for. /// Log Message Action. public static Action, Exception?> GetCriticalBasicLoggerMessageActionForEventIdWithFunc(EventId eventId) => - GetBasicLoggerMessageActionForLogLevelAndEventIdWithFunc( + GetBasicLoggerMessageActionForLogLevelAndEventIdAndFunc( LogLevel.Critical, eventId); @@ -94,8 +94,8 @@ public static class LoggerMessageFactory /// /// The event id to define a log message action for. /// Log Message Action. - public static Action, Exception?> GetDebugBasicLoggerMessageActionForEventIdWithFunc(EventId eventId) => - GetBasicLoggerMessageActionForLogLevelAndEventIdWithFunc( + public static Action, Exception?> GetDebugBasicLoggerMessageActionForEventIdAndFunc(EventId eventId) => + GetBasicLoggerMessageActionForLogLevelAndEventIdAndFunc( LogLevel.Debug, eventId); @@ -116,8 +116,8 @@ public static class LoggerMessageFactory /// /// The event id to define a log message action for. /// Log Message Action. - public static Action, Exception?> GetErrorBasicLoggerMessageActionForEventIdWithFunc(EventId eventId) => - GetBasicLoggerMessageActionForLogLevelAndEventIdWithFunc( + public static Action, Exception?> GetErrorBasicLoggerMessageActionForEventIdAndFunc(EventId eventId) => + GetBasicLoggerMessageActionForLogLevelAndEventIdAndFunc( LogLevel.Error, eventId); @@ -138,8 +138,8 @@ public static class LoggerMessageFactory /// /// The event id to define a log message action for. /// Log Message Action. - public static Action, Exception?> GetInformationBasicLoggerMessageActionForEventIdWithFunc(EventId eventId) => - GetBasicLoggerMessageActionForLogLevelAndEventIdWithFunc( + public static Action, Exception?> GetInformationBasicLoggerMessageActionForEventIdAndFunc(EventId eventId) => + GetBasicLoggerMessageActionForLogLevelAndEventIdAndFunc( LogLevel.Information, eventId); @@ -160,8 +160,8 @@ public static class LoggerMessageFactory /// /// The event id to define a log message action for. /// Log Message Action. - public static Action, Exception?> GetWarningBasicLoggerMessageActionForEventIdWithFunc(EventId eventId) => - GetBasicLoggerMessageActionForLogLevelAndEventIdWithFunc( + public static Action, Exception?> GetWarningBasicLoggerMessageActionForEventIdAndFunc(EventId eventId) => + GetBasicLoggerMessageActionForLogLevelAndEventIdAndFunc( LogLevel.Warning, eventId); @@ -185,10 +185,10 @@ public static class LoggerMessageFactory /// The logging level. /// The event id. /// Log Message Action. - public static Action, Exception?> GetBasicLoggerMessageActionForLogLevelAndEventIdWithFunc( + public static Action, Exception?> GetBasicLoggerMessageActionForLogLevelAndEventIdAndFunc( LogLevel logLevel, EventId eventId) => - DefineWithFunc( + DefineForFunc( logLevel, eventId, "{Message}"); @@ -201,8 +201,8 @@ public static class LoggerMessageFactory /// The event id. /// The named format string. /// A delegate which when invoked creates a log message. - public static Action, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) - => DefineWithFunc(logLevel, eventId, formatString, options: null); + public static Action, Exception?> DefineForFunc(LogLevel logLevel, EventId eventId, string formatString) + => DefineForFunc(logLevel, eventId, formatString, options: null); /// /// Creates a delegate which can be invoked for logging a message. @@ -213,7 +213,7 @@ public static class LoggerMessageFactory /// The named format string. /// The . /// A delegate which when invoked creates a log message. - public static Action, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) + public static Action, Exception?> DefineForFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) { LogValuesFormatter formatter = CreateLogValuesFormatter(formatString, expectedNamedParameterCount: 1); @@ -245,8 +245,8 @@ void Log(ILogger logger, Func arg1, Exception? exception) /// The event id. /// The named format string. /// A delegate which when invoked creates a log message. - public static Action, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) - => DefineWithFunc(logLevel, eventId, formatString, options: null); + public static Action, Func, Exception?> DefineForFunc(LogLevel logLevel, EventId eventId, string formatString) + => DefineForFunc(logLevel, eventId, formatString, options: null); /// /// Creates a delegate which can be invoked for logging a message. @@ -258,7 +258,7 @@ void Log(ILogger logger, Func arg1, Exception? exception) /// The named format string. /// The . /// A delegate which when invoked creates a log message. - public static Action, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) + public static Action, Func, Exception?> DefineForFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) { LogValuesFormatter formatter = CreateLogValuesFormatter(formatString, expectedNamedParameterCount: 2); @@ -291,8 +291,8 @@ void Log(ILogger logger, Func arg1, Func arg2, Exception? exception) /// The event id. /// The named format string. /// A delegate which when invoked creates a log message. - public static Action, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) - => DefineWithFunc(logLevel, eventId, formatString, options: null); + public static Action, Func, Func, Exception?> DefineForFunc(LogLevel logLevel, EventId eventId, string formatString) + => DefineForFunc(logLevel, eventId, formatString, options: null); /// /// Creates a delegate which can be invoked for logging a message. @@ -305,7 +305,7 @@ void Log(ILogger logger, Func arg1, Func arg2, Exception? exception) /// The named format string. /// The . /// A delegate which when invoked creates a log message. - public static Action, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) + public static Action, Func, Func, Exception?> DefineForFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) { LogValuesFormatter formatter = CreateLogValuesFormatter(formatString, expectedNamedParameterCount: 3); @@ -339,8 +339,8 @@ void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Exception? /// The event id. /// The named format string. /// A delegate which when invoked creates a log message. - public static Action, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) - => DefineWithFunc(logLevel, eventId, formatString, options: null); + public static Action, Func, Func, Func, Exception?> DefineForFunc(LogLevel logLevel, EventId eventId, string formatString) + => DefineForFunc(logLevel, eventId, formatString, options: null); /// /// Creates a delegate which can be invoked for logging a message. @@ -354,7 +354,7 @@ void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Exception? /// The named format string. /// The . /// A delegate which when invoked creates a log message. - public static Action, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) + public static Action, Func, Func, Func, Exception?> DefineForFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) { LogValuesFormatter formatter = CreateLogValuesFormatter(formatString, expectedNamedParameterCount: 4); @@ -389,8 +389,8 @@ void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Func a /// The event id. /// The named format string. /// A delegate which when invoked creates a log message. - public static Action, Func, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) - => DefineWithFunc(logLevel, eventId, formatString, options: null); + public static Action, Func, Func, Func, Func, Exception?> DefineForFunc(LogLevel logLevel, EventId eventId, string formatString) + => DefineForFunc(logLevel, eventId, formatString, options: null); /// /// Creates a delegate which can be invoked for logging a message. @@ -405,7 +405,7 @@ void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Func a /// The named format string. /// The . /// A delegate which when invoked creates a log message. - public static Action, Func, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) + public static Action, Func, Func, Func, Func, Exception?> DefineForFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) { LogValuesFormatter formatter = CreateLogValuesFormatter(formatString, expectedNamedParameterCount: 5); @@ -441,8 +441,8 @@ void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Func a /// The event id. /// The named format string. /// A delegate which when invoked creates a log message. - public static Action, Func, Func, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString) - => DefineWithFunc(logLevel, eventId, formatString, options: null); + public static Action, Func, Func, Func, Func, Func, Exception?> DefineForFunc(LogLevel logLevel, EventId eventId, string formatString) + => DefineForFunc(logLevel, eventId, formatString, options: null); /// /// Creates a delegate which can be invoked for logging a message. @@ -458,7 +458,7 @@ void Log(ILogger logger, Func arg1, Func arg2, Func arg3, Func a /// The named format string. /// The . /// A delegate which when invoked creates a log message. - public static Action, Func, Func, Func, Func, Func, Exception?> DefineWithFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) + public static Action, Func, Func, Func, Func, Func, Exception?> DefineForFunc(LogLevel logLevel, EventId eventId, string formatString, LogDefineOptions? options) { LogValuesFormatter formatter = CreateLogValuesFormatter(formatString, expectedNamedParameterCount: 6); diff --git a/src/Whipstaff.UnitTests/Core/Logging/LoggerMessageFactoryTests.cs b/src/Whipstaff.UnitTests/Core/Logging/LoggerMessageFactoryTests.cs new file mode 100644 index 000000000..ad17b04e0 --- /dev/null +++ b/src/Whipstaff.UnitTests/Core/Logging/LoggerMessageFactoryTests.cs @@ -0,0 +1,450 @@ +// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. +// This file is licensed to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Logging; +using Whipstaff.Core.Logging; +using Xunit; +using Xunit.Abstractions; + +namespace Whipstaff.UnitTests.Core.Logging +{ + /// + /// Unit Tests for . + /// + public static class LoggerMessageFactoryTests + { + /// + /// Unit Tests for . + /// + public sealed class GetDbContextSaveResultLoggerMessageActionMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetDbContextSaveResultLoggerMessageActionMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetNoMediatRHandlersRegisteredForTypeLoggerMessageActionMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetNoMediatRHandlersRegisteredForTypeLoggerMessageActionMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetCountOfMediatRHandlersRegisteredLoggerMessageActionMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetCountOfMediatRHandlersRegisteredLoggerMessageActionMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetCriticalBasicLoggerMessageActionForEventIdMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetCriticalBasicLoggerMessageActionForEventIdMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetCriticalBasicLoggerMessageActionForEventIdWithFuncMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetCriticalBasicLoggerMessageActionForEventIdWithFuncMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetDebugBasicLoggerMessageActionForEventIdMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetDebugBasicLoggerMessageActionForEventIdMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetDebugBasicLoggerMessageActionForEventIdAndFuncMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetDebugBasicLoggerMessageActionForEventIdAndFuncMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetErrorBasicLoggerMessageActionForEventIdMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetErrorBasicLoggerMessageActionForEventIdMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetErrorBasicLoggerMessageActionForEventIdAndFuncMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetErrorBasicLoggerMessageActionForEventIdAndFuncMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetInformationBasicLoggerMessageActionForEventIdMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetInformationBasicLoggerMessageActionForEventIdMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetInformationBasicLoggerMessageActionForEventIdAndFuncMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetInformationBasicLoggerMessageActionForEventIdAndFuncMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetWarningBasicLoggerMessageActionForEventIdMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetWarningBasicLoggerMessageActionForEventIdMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetWarningBasicLoggerMessageActionForEventIdAndFuncMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetWarningBasicLoggerMessageActionForEventIdAndFuncMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetBasicLoggerMessageActionForLogLevelAndEventIdMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetBasicLoggerMessageActionForLogLevelAndEventIdMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetBasicLoggerMessageActionForLogLevelAndEventId(LogLevel.Information, new EventId(1, "Event")); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class GetBasicLoggerMessageActionForLogLevelAndEventIdAndFuncMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public GetBasicLoggerMessageActionForLogLevelAndEventIdAndFuncMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.GetBasicLoggerMessageActionForLogLevelAndEventIdAndFunc(LogLevel.Information, new EventId(1, "Event")); + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT1Method : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DefineForFuncT1Method(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.DefineForFunc( + LogLevel.Information, + new EventId(1, "Event"), + "Some Log Message. {Arg}"); + + Assert.NotNull(instance); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT1WithOptionsMethod : Foundatio.Xunit.TestWithLoggingBase + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DefineForFuncT1WithOptionsMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void ReturnsLogMessageAction() + { + var instance = LoggerMessageFactory.DefineForFunc( + LogLevel.Information, + new EventId(1, "Event"), + "Some Log Message. {Arg}", + new LogDefineOptions()); + Assert.NotNull(instance); + } + } + } +} From 05cf708e6b37095151e83e14021cc86d3deef2c7 Mon Sep 17 00:00:00 2001 From: David Vreony Date: Tue, 9 Aug 2022 21:49:48 +0100 Subject: [PATCH 6/6] more logger message factory tests --- .../Logging/LoggerMessageFactory.cs | 2 +- .../Core/Logging/LoggerMessageFactoryTests.cs | 532 +++++++++++++++++- 2 files changed, 502 insertions(+), 32 deletions(-) diff --git a/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs b/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs index 78571d5dd..ed51dd5be 100644 --- a/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs +++ b/src/Whipstaff.Core/Logging/LoggerMessageFactory.cs @@ -72,7 +72,7 @@ public static class LoggerMessageFactory /// /// The event id to define a log message action for. /// Log Message Action. - public static Action, Exception?> GetCriticalBasicLoggerMessageActionForEventIdWithFunc(EventId eventId) => + public static Action, Exception?> GetCriticalBasicLoggerMessageActionForEventIdAndFunc(EventId eventId) => GetBasicLoggerMessageActionForLogLevelAndEventIdAndFunc( LogLevel.Critical, eventId); diff --git a/src/Whipstaff.UnitTests/Core/Logging/LoggerMessageFactoryTests.cs b/src/Whipstaff.UnitTests/Core/Logging/LoggerMessageFactoryTests.cs index ad17b04e0..b1c389b2e 100644 --- a/src/Whipstaff.UnitTests/Core/Logging/LoggerMessageFactoryTests.cs +++ b/src/Whipstaff.UnitTests/Core/Logging/LoggerMessageFactoryTests.cs @@ -2,6 +2,7 @@ // This file is licensed to you under the MIT license. // See the LICENSE file in the project root for full license information. +using System; using Microsoft.Extensions.Logging; using Whipstaff.Core.Logging; using Xunit; @@ -14,6 +15,13 @@ namespace Whipstaff.UnitTests.Core.Logging /// public static class LoggerMessageFactoryTests { + private const string FormatString1 = "Some Log Message. {Arg}"; + private const string FormatString2 = "Some Log Message. {Arg1} {Arg2}"; + private const string FormatString3 = "Some Log Message. {Arg1} {Arg2} {Arg3}"; + private const string FormatString4 = "Some Log Message. {Arg1} {Arg2} {Arg3} {Arg4}"; + private const string FormatString5 = "Some Log Message. {Arg1} {Arg2} {Arg3} {Arg4} {Arg5}"; + private const string FormatString6 = "Some Log Message. {Arg1} {Arg2} {Arg3} {Arg4} {Arg5} {Arg6}"; + /// /// Unit Tests for . /// @@ -109,21 +117,21 @@ public GetCriticalBasicLoggerMessageActionForEventIdMethod(ITestOutputHelper out [Fact] public void ReturnsLogMessageAction() { - var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + var instance = LoggerMessageFactory.GetCriticalBasicLoggerMessageActionForEventId(new EventId(1, "Event")); Assert.NotNull(instance); } } /// - /// Unit Tests for . + /// Unit Tests for . /// - public sealed class GetCriticalBasicLoggerMessageActionForEventIdWithFuncMethod : Foundatio.Xunit.TestWithLoggingBase + public sealed class GetCriticalBasicLoggerMessageActionForEventIdAndFuncMethod : Foundatio.Xunit.TestWithLoggingBase { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// XUnit test output helper instance. - public GetCriticalBasicLoggerMessageActionForEventIdWithFuncMethod(ITestOutputHelper output) + /// XUnit test output helper instance. + public GetCriticalBasicLoggerMessageActionForEventIdAndFuncMethod(ITestOutputHelper output) : base(output) { } @@ -134,7 +142,7 @@ public GetCriticalBasicLoggerMessageActionForEventIdWithFuncMethod(ITestOutputHe [Fact] public void ReturnsLogMessageAction() { - var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + var instance = LoggerMessageFactory.GetCriticalBasicLoggerMessageActionForEventIdAndFunc(new EventId(1, "Event")); Assert.NotNull(instance); } } @@ -159,7 +167,7 @@ public GetDebugBasicLoggerMessageActionForEventIdMethod(ITestOutputHelper output [Fact] public void ReturnsLogMessageAction() { - var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + var instance = LoggerMessageFactory.GetDebugBasicLoggerMessageActionForEventId(new EventId(1, "Event")); Assert.NotNull(instance); } } @@ -184,7 +192,7 @@ public GetDebugBasicLoggerMessageActionForEventIdAndFuncMethod(ITestOutputHelper [Fact] public void ReturnsLogMessageAction() { - var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + var instance = LoggerMessageFactory.GetCriticalBasicLoggerMessageActionForEventIdAndFunc(new EventId(1, "Event")); Assert.NotNull(instance); } } @@ -209,7 +217,7 @@ public GetErrorBasicLoggerMessageActionForEventIdMethod(ITestOutputHelper output [Fact] public void ReturnsLogMessageAction() { - var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + var instance = LoggerMessageFactory.GetErrorBasicLoggerMessageActionForEventId(new EventId(1, "Event")); Assert.NotNull(instance); } } @@ -234,7 +242,7 @@ public GetErrorBasicLoggerMessageActionForEventIdAndFuncMethod(ITestOutputHelper [Fact] public void ReturnsLogMessageAction() { - var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + var instance = LoggerMessageFactory.GetErrorBasicLoggerMessageActionForEventIdAndFunc(new EventId(1, "Event")); Assert.NotNull(instance); } } @@ -259,7 +267,7 @@ public GetInformationBasicLoggerMessageActionForEventIdMethod(ITestOutputHelper [Fact] public void ReturnsLogMessageAction() { - var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + var instance = LoggerMessageFactory.GetInformationBasicLoggerMessageActionForEventId(new EventId(1, "Event")); Assert.NotNull(instance); } } @@ -284,7 +292,7 @@ public GetInformationBasicLoggerMessageActionForEventIdAndFuncMethod(ITestOutput [Fact] public void ReturnsLogMessageAction() { - var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + var instance = LoggerMessageFactory.GetInformationBasicLoggerMessageActionForEventIdAndFunc(new EventId(1, "Event")); Assert.NotNull(instance); } } @@ -309,7 +317,7 @@ public GetWarningBasicLoggerMessageActionForEventIdMethod(ITestOutputHelper outp [Fact] public void ReturnsLogMessageAction() { - var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + var instance = LoggerMessageFactory.GetWarningBasicLoggerMessageActionForEventId(new EventId(1, "Event")); Assert.NotNull(instance); } } @@ -334,7 +342,7 @@ public GetWarningBasicLoggerMessageActionForEventIdAndFuncMethod(ITestOutputHelp [Fact] public void ReturnsLogMessageAction() { - var instance = LoggerMessageFactory.GetDbContextSaveResultLoggerMessageAction(); + var instance = LoggerMessageFactory.GetWarningBasicLoggerMessageActionForEventIdAndFunc(new EventId(1, "Event")); Assert.NotNull(instance); } } @@ -347,7 +355,7 @@ public sealed class GetBasicLoggerMessageActionForLogLevelAndEventIdMethod : Fou /// /// Initializes a new instance of the class. /// - /// XUnit test output helper instance. + /// XUnit test output helper instance. public GetBasicLoggerMessageActionForLogLevelAndEventIdMethod(ITestOutputHelper output) : base(output) { @@ -390,15 +398,16 @@ public void ReturnsLogMessageAction() } /// - /// Unit Tests for . + /// Abstraction of unit tests for logger message define calls. /// - public sealed class DefineForFuncT1Method : Foundatio.Xunit.TestWithLoggingBase + /// The action signature for the log message action. + public abstract class AbstractDefineForFuncMethod : Foundatio.Xunit.TestWithLoggingBase { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// XUnit test output helper instance. - public DefineForFuncT1Method(ITestOutputHelper output) + /// XUnit test output helper instance. + protected AbstractDefineForFuncMethod(ITestOutputHelper output) : base(output) { } @@ -409,19 +418,110 @@ public DefineForFuncT1Method(ITestOutputHelper output) [Fact] public void ReturnsLogMessageAction() { - var instance = LoggerMessageFactory.DefineForFunc( + var instance = GetLoggerMessageAction(); + + Assert.NotNull(instance); + } + + /// + /// Test to ensure a message is logged when the log level allows. + /// + [Fact] + public void LogsMessage() + { + var instance = GetLoggerMessageAction(); + + var count = Log.LogEntries.Count; + var callCount = 0; + + InvokeLogMessageAction( + instance, + () => + { + callCount++; + return 1; + }); + + Assert.True(Log.LogEntries.Count > count); + Assert.Equal(1, callCount); + } + + /// + /// Test to ensure a log message action instance is created. + /// + [Fact] + public void SkipsMessage() + { + var instance = GetLoggerMessageAction(); + + Log.MinimumLevel = LogLevel.Error; + + var count = Log.LogEntries.Count; + var callCount = 0; + + InvokeLogMessageAction( + instance, + () => + { + callCount++; + return 1; + }); + + Assert.Equal(Log.LogEntries.Count, count); + Assert.Equal(0, callCount); + } + + /// + /// Logger Message Action to test. + /// + /// Logger Message Action instance. + protected abstract TLogMessageAction GetLoggerMessageAction(); + + /// + /// Called to allow the implementing test class to fire off the logger with the correct number of args. + /// + /// Logger Message Action instance. + /// The func to pass into arg 1 to track execution counts. + protected abstract void InvokeLogMessageAction(TLogMessageAction instance, Func trackingFunc); + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT1Method : AbstractDefineForFuncMethod, Exception?>> + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DefineForFuncT1Method(ITestOutputHelper output) + : base(output) + { + } + + /// + protected override Action, Exception?> GetLoggerMessageAction() + { + return LoggerMessageFactory.DefineForFunc( LogLevel.Information, new EventId(1, "Event"), - "Some Log Message. {Arg}"); + FormatString1); + } - Assert.NotNull(instance); + /// + protected override void InvokeLogMessageAction(Action, Exception?> instance, Func trackingFunc) + { + instance( + _logger, + trackingFunc, + null); } } /// /// Unit Tests for . /// - public sealed class DefineForFuncT1WithOptionsMethod : Foundatio.Xunit.TestWithLoggingBase + public sealed class DefineForFuncT1WithOptionsMethod : AbstractDefineForFuncMethod, Exception?>> { /// /// Initializes a new instance of the class. @@ -432,18 +532,388 @@ public DefineForFuncT1WithOptionsMethod(ITestOutputHelper output) { } + /// + protected override Action, Exception?> GetLoggerMessageAction() + { + return LoggerMessageFactory.DefineForFunc( + LogLevel.Information, + new EventId(1, "Event"), + FormatString1, + new LogDefineOptions()); + } + + /// + protected override void InvokeLogMessageAction(Action, Exception?> instance, Func trackingFunc) + { + instance( + _logger, + trackingFunc, + null); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT2Method : AbstractDefineForFuncMethod, Func, Exception?>> + { /// - /// Test to ensure a log message action instance is created. + /// Initializes a new instance of the class. /// - [Fact] - public void ReturnsLogMessageAction() + /// XUnit test output helper instance. + public DefineForFuncT2Method(ITestOutputHelper output) + : base(output) + { + } + + /// + protected override Action, Func, Exception?> GetLoggerMessageAction() { - var instance = LoggerMessageFactory.DefineForFunc( + return LoggerMessageFactory.DefineForFunc( LogLevel.Information, new EventId(1, "Event"), - "Some Log Message. {Arg}", + FormatString2); + } + + /// + protected override void InvokeLogMessageAction(Action, Func, Exception?> instance, Func trackingFunc) + { + instance( + _logger, + trackingFunc, + () => 2, + null); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT2WithOptionsMethod : AbstractDefineForFuncMethod, Func, Exception?>> + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DefineForFuncT2WithOptionsMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + protected override Action, Func, Exception?> GetLoggerMessageAction() + { + return LoggerMessageFactory.DefineForFunc( + LogLevel.Information, + new EventId(1, "Event"), + FormatString2, new LogDefineOptions()); - Assert.NotNull(instance); + } + + /// + protected override void InvokeLogMessageAction(Action, Func, Exception?> instance, Func trackingFunc) + { + instance( + _logger, + trackingFunc, + () => 2, + null); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT3Method : AbstractDefineForFuncMethod, Func, Func, Exception?>> + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DefineForFuncT3Method(ITestOutputHelper output) + : base(output) + { + } + + /// + protected override Action, Func, Func, Exception?> GetLoggerMessageAction() + { + return LoggerMessageFactory.DefineForFunc( + LogLevel.Information, + new EventId(1, "Event"), + FormatString3); + } + + /// + protected override void InvokeLogMessageAction(Action, Func, Func, Exception?> instance, Func trackingFunc) + { + instance( + _logger, + trackingFunc, + () => 2, + () => 3, + null); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT3WithOptionsMethod : AbstractDefineForFuncMethod, Func, Func, Exception?>> + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DefineForFuncT3WithOptionsMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + protected override Action, Func, Func, Exception?> GetLoggerMessageAction() + { + return LoggerMessageFactory.DefineForFunc( + LogLevel.Information, + new EventId(1, "Event"), + FormatString3, + new LogDefineOptions()); + } + + /// + protected override void InvokeLogMessageAction(Action, Func, Func, Exception?> instance, Func trackingFunc) + { + instance( + _logger, + trackingFunc, + () => 2, + () => 3, + null); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT4Method : AbstractDefineForFuncMethod, Func, Func, Func, Exception?>> + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DefineForFuncT4Method(ITestOutputHelper output) + : base(output) + { + } + + /// + protected override Action, Func, Func, Func, Exception?> GetLoggerMessageAction() + { + return LoggerMessageFactory.DefineForFunc( + LogLevel.Information, + new EventId(1, "Event"), + FormatString4); + } + + /// + protected override void InvokeLogMessageAction(Action, Func, Func, Func, Exception?> instance, Func trackingFunc) + { + instance( + _logger, + trackingFunc, + () => 2, + () => 3, + () => 4, + null); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT4WithOptionsMethod : AbstractDefineForFuncMethod, Func, Func, Func, Exception?>> + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DefineForFuncT4WithOptionsMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + protected override Action, Func, Func, Func, Exception?> GetLoggerMessageAction() + { + return LoggerMessageFactory.DefineForFunc( + LogLevel.Information, + new EventId(1, "Event"), + FormatString4, + new LogDefineOptions()); + } + + /// + protected override void InvokeLogMessageAction(Action, Func, Func, Func, Exception?> instance, Func trackingFunc) + { + instance( + _logger, + trackingFunc, + () => 2, + () => 3, + () => 4, + null); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT5Method : AbstractDefineForFuncMethod, Func, Func, Func, Func, Exception?>> + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DefineForFuncT5Method(ITestOutputHelper output) + : base(output) + { + } + + /// + protected override Action, Func, Func, Func, Func, Exception?> GetLoggerMessageAction() + { + return LoggerMessageFactory.DefineForFunc( + LogLevel.Information, + new EventId(1, "Event"), + FormatString5); + } + + /// + protected override void InvokeLogMessageAction(Action, Func, Func, Func, Func, Exception?> instance, Func trackingFunc) + { + instance( + _logger, + trackingFunc, + () => 2, + () => 3, + () => 4, + () => 5, + null); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT5WithOptionsMethod : AbstractDefineForFuncMethod, Func, Func, Func, Func, Exception?>> + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DefineForFuncT5WithOptionsMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + protected override Action, Func, Func, Func, Func, Exception?> GetLoggerMessageAction() + { + return LoggerMessageFactory.DefineForFunc( + LogLevel.Information, + new EventId(1, "Event"), + FormatString5, + new LogDefineOptions()); + } + + /// + protected override void InvokeLogMessageAction(Action, Func, Func, Func, Func, Exception?> instance, Func trackingFunc) + { + instance( + _logger, + trackingFunc, + () => 2, + () => 3, + () => 4, + () => 5, + null); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT6Method : AbstractDefineForFuncMethod, Func, Func, Func, Func, Func, Exception?>> + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DefineForFuncT6Method(ITestOutputHelper output) + : base(output) + { + } + + /// + protected override Action, Func, Func, Func, Func, Func, Exception?> GetLoggerMessageAction() + { + return LoggerMessageFactory.DefineForFunc( + LogLevel.Information, + new EventId(1, "Event"), + FormatString6); + } + + /// + protected override void InvokeLogMessageAction(Action, Func, Func, Func, Func, Func, Exception?> instance, Func trackingFunc) + { + instance( + _logger, + trackingFunc, + () => 2, + () => 3, + () => 4, + () => 5, + () => 6, + null); + } + } + + /// + /// Unit Tests for . + /// + public sealed class DefineForFuncT6WithOptionsMethod : AbstractDefineForFuncMethod, Func, Func, Func, Func, Func, Exception?>> + { + /// + /// Initializes a new instance of the class. + /// + /// XUnit test output helper instance. + public DefineForFuncT6WithOptionsMethod(ITestOutputHelper output) + : base(output) + { + } + + /// + protected override Action, Func, Func, Func, Func, Func, Exception?> GetLoggerMessageAction() + { + return LoggerMessageFactory.DefineForFunc( + LogLevel.Information, + new EventId(1, "Event"), + FormatString6, + new LogDefineOptions()); + } + + /// + protected override void InvokeLogMessageAction(Action, Func, Func, Func, Func, Func, Exception?> instance, Func trackingFunc) + { + instance( + _logger, + trackingFunc, + () => 2, + () => 3, + () => 4, + () => 5, + () => 6, + null); } } }