From d3fd75d4931f50fc8fa70b3399c6e745935c1fb1 Mon Sep 17 00:00:00 2001 From: Nikita Petko Date: Thu, 3 Oct 2024 18:13:39 +0100 Subject: [PATCH 1/3] Sync all versions All packages are now on inline versioning. --- src/Directory.Build.props | 2 ++ src/clients/client-settings-client/ClientSettings.Client.csproj | 2 -- src/clients/thumbnails-client/Thumbnails.Client.csproj | 2 -- src/clients/users-client/Users.Client.csproj | 2 -- src/configuration/configuration/Configuration.csproj | 2 -- src/configuration/core/Configuration.Core.csproj | 2 -- src/file-system/file-system/FileSystem.csproj | 2 -- src/floodcheckers/core/FloodCheckers.Core.csproj | 2 -- src/floodcheckers/redis/FloodCheckers.Redis.csproj | 2 -- src/grid/client/Grid.Client.csproj | 2 -- src/grid/commands/Grid.Commands.csproj | 2 -- src/grid/port-management/Grid.PortManagement.csproj | 2 -- .../process-management-core/Grid.ProcessManagement.Core.csproj | 2 -- .../Grid.ProcessManagement.Docker.csproj | 2 -- src/grid/process-management/Grid.ProcessManagement.csproj | 2 -- src/hashing/hashing/Hashing.csproj | 2 -- src/logging/logging/Logging.csproj | 2 -- src/networking/networking/Networking.csproj | 2 -- src/random/random/Random.csproj | 2 -- src/redis/redis/Redis.csproj | 2 -- src/service-discovery/service-discovery/ServiceDiscovery.csproj | 2 -- src/text/text/Text.csproj | 2 -- src/threading/threading/Threading.csproj | 2 -- src/vault/vault/Vault.csproj | 2 -- 24 files changed, 2 insertions(+), 46 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5c1398d..2382477 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -40,6 +40,8 @@ Copyright © $(Company) $([System.DateTime]::Now.ToString(`yyyy`)). All rights reserved. $(Company);Nikita Petko + 1.0.9 + https://github.com/mfdlabs/grid-bot-libraries git diff --git a/src/clients/client-settings-client/ClientSettings.Client.csproj b/src/clients/client-settings-client/ClientSettings.Client.csproj index 96a117d..3b2ffc1 100644 --- a/src/clients/client-settings-client/ClientSettings.Client.csproj +++ b/src/clients/client-settings-client/ClientSettings.Client.csproj @@ -1,8 +1,6 @@ HTTP client used to interact with Roblox's Client Settings API Site. - - 1.0.4 diff --git a/src/clients/thumbnails-client/Thumbnails.Client.csproj b/src/clients/thumbnails-client/Thumbnails.Client.csproj index 1ddb23c..c7891e2 100644 --- a/src/clients/thumbnails-client/Thumbnails.Client.csproj +++ b/src/clients/thumbnails-client/Thumbnails.Client.csproj @@ -1,8 +1,6 @@  HTTP client used to interact with Roblox's Thumbnails API Site. - - 1.0.4 diff --git a/src/clients/users-client/Users.Client.csproj b/src/clients/users-client/Users.Client.csproj index 19ebb59..c628829 100644 --- a/src/clients/users-client/Users.Client.csproj +++ b/src/clients/users-client/Users.Client.csproj @@ -1,8 +1,6 @@  HTTP client used to interact with Roblox's Users API Site. - - 1.0.4 diff --git a/src/configuration/configuration/Configuration.csproj b/src/configuration/configuration/Configuration.csproj index a0455a8..3d38dd9 100644 --- a/src/configuration/configuration/Configuration.csproj +++ b/src/configuration/configuration/Configuration.csproj @@ -1,8 +1,6 @@  C# adaptation for @mfdlabs/environment. - - 1.0.8 diff --git a/src/configuration/core/Configuration.Core.csproj b/src/configuration/core/Configuration.Core.csproj index 51b512f..d3df9cd 100644 --- a/src/configuration/core/Configuration.Core.csproj +++ b/src/configuration/core/Configuration.Core.csproj @@ -1,8 +1,6 @@ Helpers for configuration in applications. - - 1.0.5 diff --git a/src/file-system/file-system/FileSystem.csproj b/src/file-system/file-system/FileSystem.csproj index a974152..3570f45 100644 --- a/src/file-system/file-system/FileSystem.csproj +++ b/src/file-system/file-system/FileSystem.csproj @@ -1,8 +1,6 @@  File system helpers and more! - - 1.0.5 diff --git a/src/floodcheckers/core/FloodCheckers.Core.csproj b/src/floodcheckers/core/FloodCheckers.Core.csproj index bbeba35..b127e4d 100644 --- a/src/floodcheckers/core/FloodCheckers.Core.csproj +++ b/src/floodcheckers/core/FloodCheckers.Core.csproj @@ -1,8 +1,6 @@  Core library for flood checker implementation! - - 1.0.4 diff --git a/src/floodcheckers/redis/FloodCheckers.Redis.csproj b/src/floodcheckers/redis/FloodCheckers.Redis.csproj index 828f9db..56d2b0d 100644 --- a/src/floodcheckers/redis/FloodCheckers.Redis.csproj +++ b/src/floodcheckers/redis/FloodCheckers.Redis.csproj @@ -1,8 +1,6 @@  Library for interacting with Redis based flood checkers! - - 1.0.5 diff --git a/src/grid/client/Grid.Client.csproj b/src/grid/client/Grid.Client.csproj index 05affb9..730d7ff 100644 --- a/src/grid/client/Grid.Client.csproj +++ b/src/grid/client/Grid.Client.csproj @@ -1,8 +1,6 @@  SOAP library shared by grid-server processes! - - 1.0.4 diff --git a/src/grid/commands/Grid.Commands.csproj b/src/grid/commands/Grid.Commands.csproj index 5c0ad03..37cbfbc 100644 --- a/src/grid/commands/Grid.Commands.csproj +++ b/src/grid/commands/Grid.Commands.csproj @@ -1,8 +1,6 @@  Shared library containing models used by grid-servers for specific script execution actions. - - 1.0.4 diff --git a/src/grid/port-management/Grid.PortManagement.csproj b/src/grid/port-management/Grid.PortManagement.csproj index f782629..cde2123 100644 --- a/src/grid/port-management/Grid.PortManagement.csproj +++ b/src/grid/port-management/Grid.PortManagement.csproj @@ -1,8 +1,6 @@  Shared library for port allocation on grid-servers! - - 1.0.5 Grid diff --git a/src/grid/process-management-core/Grid.ProcessManagement.Core.csproj b/src/grid/process-management-core/Grid.ProcessManagement.Core.csproj index e022a83..3888b51 100644 --- a/src/grid/process-management-core/Grid.ProcessManagement.Core.csproj +++ b/src/grid/process-management-core/Grid.ProcessManagement.Core.csproj @@ -1,8 +1,6 @@  Shared library for core interaction with grid server processes! - - 1.0.5 Grid diff --git a/src/grid/process-management-docker/Grid.ProcessManagement.Docker.csproj b/src/grid/process-management-docker/Grid.ProcessManagement.Docker.csproj index eb29fa9..33c800e 100644 --- a/src/grid/process-management-docker/Grid.ProcessManagement.Docker.csproj +++ b/src/grid/process-management-docker/Grid.ProcessManagement.Docker.csproj @@ -1,8 +1,6 @@ Shared library for interaction with Docker based grid server processes! - - 1.0.4 Grid diff --git a/src/grid/process-management/Grid.ProcessManagement.csproj b/src/grid/process-management/Grid.ProcessManagement.csproj index b1e7ebb..a514cb8 100644 --- a/src/grid/process-management/Grid.ProcessManagement.csproj +++ b/src/grid/process-management/Grid.ProcessManagement.csproj @@ -1,8 +1,6 @@  Shared library for allocating grid-server processes! - - 1.0.5 Grid diff --git a/src/hashing/hashing/Hashing.csproj b/src/hashing/hashing/Hashing.csproj index a4b52e5..6e5194a 100644 --- a/src/hashing/hashing/Hashing.csproj +++ b/src/hashing/hashing/Hashing.csproj @@ -1,7 +1,5 @@ Shared library to help with hashing. - - 1.0.4 diff --git a/src/logging/logging/Logging.csproj b/src/logging/logging/Logging.csproj index 9f9b4d9..3944c1c 100644 --- a/src/logging/logging/Logging.csproj +++ b/src/logging/logging/Logging.csproj @@ -1,8 +1,6 @@  Shared logging library! - - 1.0.4 diff --git a/src/networking/networking/Networking.csproj b/src/networking/networking/Networking.csproj index d58b5e9..4aa452e 100644 --- a/src/networking/networking/Networking.csproj +++ b/src/networking/networking/Networking.csproj @@ -1,8 +1,6 @@  Library used for IP address parsing and more! - - 1.0.6 diff --git a/src/random/random/Random.csproj b/src/random/random/Random.csproj index 9881586..a222192 100644 --- a/src/random/random/Random.csproj +++ b/src/random/random/Random.csproj @@ -1,7 +1,5 @@ Random helpers and more! - - 1.0.4 diff --git a/src/redis/redis/Redis.csproj b/src/redis/redis/Redis.csproj index 28ccf47..5e9a1db 100644 --- a/src/redis/redis/Redis.csproj +++ b/src/redis/redis/Redis.csproj @@ -1,8 +1,6 @@  Redis client used for easy interaction with Redis servers. - - 1.0.4 diff --git a/src/service-discovery/service-discovery/ServiceDiscovery.csproj b/src/service-discovery/service-discovery/ServiceDiscovery.csproj index 5b5344d..7c4afee 100644 --- a/src/service-discovery/service-discovery/ServiceDiscovery.csproj +++ b/src/service-discovery/service-discovery/ServiceDiscovery.csproj @@ -1,8 +1,6 @@  Shared library to help with resolution of addresses from sources like Consul. - - 1.0.5 diff --git a/src/text/text/Text.csproj b/src/text/text/Text.csproj index f1a9ad7..0bcd385 100644 --- a/src/text/text/Text.csproj +++ b/src/text/text/Text.csproj @@ -1,8 +1,6 @@ Shared text helpers! - - 1.0.4 diff --git a/src/threading/threading/Threading.csproj b/src/threading/threading/Threading.csproj index 1bd1007..cd39130 100644 --- a/src/threading/threading/Threading.csproj +++ b/src/threading/threading/Threading.csproj @@ -1,8 +1,6 @@  Helpers to aid with multi-threaded environments. - - 1.0.4 preview diff --git a/src/vault/vault/Vault.csproj b/src/vault/vault/Vault.csproj index e1b4a7f..4e9ba66 100644 --- a/src/vault/vault/Vault.csproj +++ b/src/vault/vault/Vault.csproj @@ -1,8 +1,6 @@ Helper to aid with creation of a shared Vault client. - - 1.0.5 From eb2260e8f6d8fd436747cdf6fb65dd2483bacf9c Mon Sep 17 00:00:00 2001 From: Nikita Petko Date: Fri, 4 Oct 2024 23:15:46 +0100 Subject: [PATCH 2/3] #30: Sync Logging with EventLog ~ Convert LogLevel property into a getter for dynamic runtime fetching of the log level. ~ Add a fix on Windows for color logging failing by manually updating the console mode flags with ENABLE_VIRTUAL_TERMINAL_PROCESSING. ~ Rename LogLevel.Trace and ILogger.Trace to Verbose and add better docomments to each method and log level to better explain their use cases. ~ Remove ILogger.Log as this was technically an alias to ILogger.Information as it did the same thing. ~ Update all log methods to now take the raw message getter instead of formatting the messages themselves, this will help to introduce better performance by not evaluating the message straight away. ~ Add another ILogger.Error method with an argument to supply a message that precedes the exception trace back. --- src/logging/logging/Enums/LogLevel.cs | 59 +++--- src/logging/logging/Implementation/Logger.cs | 202 +++++++++---------- src/logging/logging/Interfaces/ILogger.cs | 70 ++++--- src/logging/logging/Logging.csproj | 9 + src/logging/logging/NativeMethods.txt | 6 + 5 files changed, 188 insertions(+), 158 deletions(-) create mode 100644 src/logging/logging/NativeMethods.txt diff --git a/src/logging/logging/Enums/LogLevel.cs b/src/logging/logging/Enums/LogLevel.cs index 85471eb..2607438 100644 --- a/src/logging/logging/Enums/LogLevel.cs +++ b/src/logging/logging/Enums/LogLevel.cs @@ -1,38 +1,37 @@ -namespace Logging +namespace Logging; + +/// +/// Represents the logging level. +/// +public enum LogLevel { /// - /// Represents the logging level. + /// NoOp log, no log messages are ever written. /// - public enum LogLevel - { - /// - /// No-Op - /// - None, + None, - /// - /// Error messagw - /// - Error, + /// + /// Only critical error messages are written. + /// + Error, - /// - /// Warn - /// - Warning, + /// + /// Error messages as well as warning type messages are written. + /// + Warning, - /// - /// Info - /// - Information, + /// + /// More chatty information based messages are written as well. + /// + Information, - /// - /// Debug - /// - Debug, + /// + /// Verbose logging information is written. + /// + Debug, - /// - /// Trace - /// - Trace - } -} + /// + /// Extremely verbose and possibly spammy logging messages are written. + /// + Verbose +} \ No newline at end of file diff --git a/src/logging/logging/Implementation/Logger.cs b/src/logging/logging/Implementation/Logger.cs index 0fdc859..52f1710 100644 --- a/src/logging/logging/Implementation/Logger.cs +++ b/src/logging/logging/Implementation/Logger.cs @@ -17,6 +17,9 @@ using System.Threading.Tasks; #endif +using Windows.Win32; +using Windows.Win32.System.Console; + // ReSharper disable LocalizableElement // ReSharper disable InconsistentNaming // ReSharper disable UseStringInterpolationWhenPossible @@ -134,6 +137,35 @@ static Logger() ) { AutoFlush = true } ); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + + unsafe + { + CONSOLE_MODE lpMode; + + var hStdout = PInvoke.GetStdHandle(STD_HANDLE.STD_OUTPUT_HANDLE); + if (PInvoke.GetConsoleMode(hStdout, &lpMode)) + { + if (!lpMode.HasFlag(CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING)) + { + lpMode |= CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING; + + PInvoke.SetConsoleMode(hStdout, lpMode); + } + } + + var hStderr = PInvoke.GetStdHandle(STD_HANDLE.STD_ERROR_HANDLE); + if (PInvoke.GetConsoleMode(hStderr, &lpMode)) + { + if (!lpMode.HasFlag(CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING)) + { + lpMode |= CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING; + + PInvoke.SetConsoleMode(hStderr, lpMode); + } + } + } } ////////////////////////////////////////////////////////////////////////////// @@ -240,7 +272,7 @@ private static LogLevel _defaultLogLevel ////////////////////////////////////////////////////////////////////////////// private string _name; - private LogLevel _logLevel; + private Func _logLevelGetter; private bool _logToConsole; private bool _logToFileSystem; private bool _logThreadId; @@ -301,6 +333,11 @@ private static bool _terminalAvailable() } } + private static string _format(string format, params object[] args) + => args is { Length: > 0 } + ? string.Format(format, args) + : format; + private static string _getCurrentThreadIdFormatted() => Thread.CurrentThread.ManagedThreadId .ToString("x") .PadLeft(4, '0'); @@ -479,15 +516,10 @@ protected static string _getConstructedCustomColorLogPrefix(List> p /// Constructs the full non-color log message. /// /// The log level. - /// The format string. - /// The args for the format. + /// The message getter. /// The constructed string. - protected string _constructNonColorLogMessage(LogLevel logLevel, string format, params object[] args) + protected string _constructNonColorLogMessage(LogLevel logLevel, Func messageGetter) { - var formattedMessage = args is { Length: 0 } - ? format - : string.Format(format, args); - if (_cutLogPrefix) return string.Format( "[{0}]{1}{2}{3}{4}[{5}] {6}\n", @@ -497,7 +529,7 @@ protected string _constructNonColorLogMessage(LogLevel logLevel, string format, Logger._getConstructedCustomNonColorLogPrefix(this._logPrefixes), Logger._getConstructedCustomNonColorLogPrefix(Logger._globalLogPrefixes), logLevel.ToString().ToUpper(), - formattedMessage + messageGetter() ); return string.Format( @@ -509,7 +541,7 @@ protected string _constructNonColorLogMessage(LogLevel logLevel, string format, Logger._getConstructedCustomNonColorLogPrefix(this._logPrefixes), Logger._getConstructedCustomNonColorLogPrefix(Logger._globalLogPrefixes), logLevel.ToString().ToUpper(), - formattedMessage + messageGetter() ); } @@ -518,15 +550,10 @@ protected string _constructNonColorLogMessage(LogLevel logLevel, string format, /// /// The log level. /// The log color. - /// The format string. - /// The args for the format. + /// The format string. /// The constructed string. - protected string _constructColorLogMessage(LogLevel logLevel, Logger.LogColor color, string format, params object[] args) + protected string _constructColorLogMessage(LogLevel logLevel, Logger.LogColor color, Func messageGetter) { - var formattedMessage = args is { Length: 0 } - ? format - : string.Format(format, args); - if (_cutLogPrefix) return string.Format( "{0}{1}{2}{3}{4}{5} {6}{7}{8}\n", @@ -537,7 +564,7 @@ protected string _constructColorLogMessage(LogLevel logLevel, Logger.LogColor co Logger._getConstructedCustomColorLogPrefix(Logger._globalLogPrefixes), Logger._getColorSection(color, logLevel.ToString().ToUpper()), Logger._getAnsiColorByLogColor(color), - formattedMessage, + messageGetter(), Logger._getAnsiColorByLogColor(LogColor.Reset) ); @@ -551,12 +578,12 @@ protected string _constructColorLogMessage(LogLevel logLevel, Logger.LogColor co Logger._getConstructedCustomColorLogPrefix(Logger._globalLogPrefixes), Logger._getColorSection(color, logLevel.ToString().ToUpper()), Logger._getAnsiColorByLogColor(color), - formattedMessage, + messageGetter(), Logger._getAnsiColorByLogColor(LogColor.Reset) ); } - private void _writeLogToFileSystem(LogLevel logLevel, string format, params object[] args) + private void _writeLogToFileSystem(LogLevel logLevel, Func messageGetter) { lock (this._fileSystemLock) { @@ -570,7 +597,7 @@ private void _writeLogToFileSystem(LogLevel logLevel, string format, params obje try { - this._lockedFileWriteStream?.Write(this._constructNonColorLogMessage(logLevel, format, args)); + this._lockedFileWriteStream?.Write(this._constructNonColorLogMessage(logLevel, messageGetter)); } catch (Exception ex) { @@ -582,31 +609,31 @@ private void _writeLogToFileSystem(LogLevel logLevel, string format, params obje } } - private void _writeLogToConsole(LogLevel logLevel, Logger.LogColor color, string format, params object[] args) + private void _writeLogToConsole(LogLevel logLevel, Logger.LogColor color, Func messageGetter) { lock (this._consoleLock) { if (!this._logToConsole) return; - var message = this._logWithColor - ? this._constructColorLogMessage(logLevel, color, format, args) - : this._constructNonColorLogMessage(logLevel, format, args); + var result = this._logWithColor + ? this._constructColorLogMessage(logLevel, color, messageGetter) + : this._constructNonColorLogMessage(logLevel, messageGetter); if (logLevel == LogLevel.Error) - Console.Error.Write(message); + Console.Error.Write(result); else - Console.Out.Write(message); + Console.Out.Write(result); } } - private void _queueOrLog(LogLevel logLevel, Logger.LogColor color, string format, params object[] args) + private void _queueOrLog(LogLevel logLevel, Logger.LogColor color, Func messageGetter) { if (this._disposed) throw new ObjectDisposedException(this.GetType().Name); #if CONCURRENT_LOGGING_ENABLED Task.Factory.StartNew(() => { - try { this._writeLog(logLevel, color, format, args); } + try { this._writeLog(logLevel, color, messageGetter); } catch (Exception ex) { #if DEBUG @@ -615,7 +642,7 @@ private void _queueOrLog(LogLevel logLevel, Logger.LogColor color, string format } }); #else - try { this._writeLog(logLevel, color, format, args); } + try { this._writeLog(logLevel, color, messageGetter); } catch (Exception ex) { #if DEBUG @@ -630,15 +657,14 @@ private void _queueOrLog(LogLevel logLevel, Logger.LogColor color, string format /// /// The log level. /// The color. - /// The format. - /// The format arga. - protected virtual void _writeLog(LogLevel logLevel, Logger.LogColor color, string format, params object[] args) + /// The message getter. + protected virtual void _writeLog(LogLevel logLevel, Logger.LogColor color, Func messageGetter) { - this._writeLogToConsole(logLevel, color, format, args); - this._writeLogToFileSystem(logLevel, format, args); + this._writeLogToConsole(logLevel, color, messageGetter); + this._writeLogToFileSystem(logLevel, messageGetter); } - private bool _checkLogLevel(LogLevel logLevel) => this._logLevel >= logLevel; + private bool _checkLogLevel(LogLevel logLevel) => this._logLevelGetter() >= logLevel; private void _createFileStream() => this._lockedFileWriteStream ??= new(this._fullyQualifiedFileName); private void _closeFileStream() { @@ -692,7 +718,7 @@ private void _createFileName() /// public static void TryClearLocalLog() { - Logger.Singleton.Log("Try clear local log files..."); + Logger.Singleton.Information("Try clear local log files..."); var fileSystemLoggers = Logger._loggers.Where(logger => logger._logToFileSystem == true); @@ -735,7 +761,7 @@ public static void TryClearLocalLog() /// public static void TryClearAllLoggers() { - Logger.Singleton.Log("Try clear all loggers..."); + Logger.Singleton.Information("Try clear all loggers..."); try { @@ -756,7 +782,7 @@ public static void TryClearAllLoggers() /// Construct a new instance of . /// /// The name of the logger. - /// The log level of the logger. + /// The log level of the logger. /// If true, the logger will log to the file system. /// If true, the logger will log to the console. /// If true, the logger will cut the log prefix. @@ -771,7 +797,7 @@ public static void TryClearAllLoggers() // ReSharper disable once MemberCanBeProtected.Global public Logger( string name, - LogLevel logLevel = LogLevel.Information, + Func logLevelGetter = default, bool logToFileSystem = true, bool logToConsole = true, bool cutLogPrefix = true, @@ -793,8 +819,10 @@ public Logger( lock (Logger._loggers) Logger._loggers.Add(this); + if (logLevelGetter == default) logLevelGetter = () => LogLevel.Information; + this._name = name; - this._logLevel = logLevel; + this._logLevelGetter = logLevelGetter; this._logToFileSystem = logToFileSystem; this._logToConsole = logToConsole; this._cutLogPrefix = cutLogPrefix; @@ -821,7 +849,7 @@ public static Logger Singleton lock (Logger._singletonLock) return Logger._singleton ??= new( _defaultLoggerName, - _defaultLogLevel + () => _defaultLogLevel ); } } @@ -836,7 +864,7 @@ public static Logger NoopSingleton lock (Logger._noopSingletonLock) return Logger._noopSingleton ??= new( "_noop", - LogLevel.None, + () => LogLevel.None, false, false ); @@ -873,12 +901,13 @@ public string Name /// public LogLevel LogLevel { - get => this._logLevel; + get => this._logLevelGetter(); set { if (this._disposed) throw new ObjectDisposedException(this.GetType().Name); + if (this._logLevelGetter() == value) return; - this._logLevel = value; + this._logLevelGetter = () => value; } } @@ -972,36 +1001,13 @@ public List> CustomLogPrefixes // Public Log Methods //////////////////////////////////////////////////////////////////////////////// - /// - public void Log(string format, params object[] args) - { - if (string.IsNullOrEmpty(format)) throw new ArgumentNullException(nameof(format)); - if (args == null) throw new ArgumentNullException(nameof(args)); - - if (!this._checkLogLevel(LogLevel.Information)) return; - - this._queueOrLog(LogLevel.Information, Logger.LogColor.BrightWhite, format, args); - } - - /// - public void Log(Func messageGetter) - { - if (messageGetter == null) throw new ArgumentNullException(nameof(messageGetter)); - - if (!this._checkLogLevel(LogLevel.Information)) return; - - this._queueOrLog(LogLevel.Information, Logger.LogColor.BrightWhite, messageGetter()); - } - /// public void Warning(string format, params object[] args) { if (string.IsNullOrEmpty(format)) throw new ArgumentNullException(nameof(format)); if (args == null) throw new ArgumentNullException(nameof(args)); - if (!this._checkLogLevel(LogLevel.Warning)) return; - - this._queueOrLog(LogLevel.Warning, Logger.LogColor.BrightYellow, format, args); + this.Warning(() => Logger._format(format, args)); } /// @@ -1011,36 +1017,28 @@ public void Warning(Func messageGetter) if (!this._checkLogLevel(LogLevel.Warning)) return; - this._queueOrLog(LogLevel.Warning, Logger.LogColor.BrightYellow, messageGetter()); + this._queueOrLog(LogLevel.Warning, Logger.LogColor.BrightYellow, messageGetter); } - /// - public void Trace(string format, params object[] args) + /// + public void Verbose(string format, params object[] args) { if (string.IsNullOrEmpty(format)) throw new ArgumentNullException(nameof(format)); if (args == null) throw new ArgumentNullException(nameof(args)); - if (!this._checkLogLevel(LogLevel.Trace)) return; - - var formattedMessage = args is { Length: 0 } - ? format - : string.Format(format, args); - - var message = string.Format("{0}\n{1}", formattedMessage, Environment.StackTrace); + if (!this._checkLogLevel(LogLevel.Verbose)) return; - this._queueOrLog(LogLevel.Trace, Logger.LogColor.BrightMagenta, message); + this.Verbose(() => Logger._format(format, args)); } - /// - public void Trace(Func messageGetter) + /// + public void Verbose(Func messageGetter) { if (messageGetter == null) throw new ArgumentNullException(nameof(messageGetter)); - if (!this._checkLogLevel(LogLevel.Trace)) return; + if (!this._checkLogLevel(LogLevel.Verbose)) return; - var message = string.Format("{0}\n{1}", messageGetter(), Environment.StackTrace); - - this._queueOrLog(LogLevel.Trace, Logger.LogColor.BrightMagenta, message); + this._queueOrLog(LogLevel.Verbose, Logger.LogColor.BrightMagenta, messageGetter); } /// @@ -1049,9 +1047,7 @@ public void Debug(string format, params object[] args) if (string.IsNullOrEmpty(format)) throw new ArgumentNullException(nameof(format)); if (args == null) throw new ArgumentNullException(nameof(args)); - if (!this._checkLogLevel(LogLevel.Debug)) return; - - this._queueOrLog(LogLevel.Debug, Logger.LogColor.BrightMagenta, format, args); + this.Debug(() => Logger._format(format, args)); } /// @@ -1061,7 +1057,7 @@ public void Debug(Func messageGetter) if (!this._checkLogLevel(LogLevel.Debug)) return; - this._queueOrLog(LogLevel.Debug, Logger.LogColor.BrightMagenta, messageGetter()); + this._queueOrLog(LogLevel.Debug, Logger.LogColor.BrightMagenta, messageGetter); } /// @@ -1070,9 +1066,7 @@ public void Information(string format, params object[] args) if (string.IsNullOrEmpty(format)) throw new ArgumentNullException(nameof(format)); if (args == null) throw new ArgumentNullException(nameof(args)); - if (!this._checkLogLevel(LogLevel.Information)) return; - - this._queueOrLog(LogLevel.Information, Logger.LogColor.BrightBlue, format, args); + this.Information(() => Logger._format(format, args)); } /// @@ -1082,7 +1076,7 @@ public void Information(Func messageGetter) if (!this._checkLogLevel(LogLevel.Information)) return; - this._queueOrLog(LogLevel.Information, Logger.LogColor.BrightBlue, messageGetter()); + this._queueOrLog(LogLevel.Information, Logger.LogColor.BrightBlue, messageGetter); } /// @@ -1090,9 +1084,16 @@ public void Error(Exception ex) { if (ex == null) throw new ArgumentNullException(nameof(ex)); - if (!this._checkLogLevel(LogLevel.Error)) return; + this.Error(() => ex.ToString()); + } - this._queueOrLog(LogLevel.Error, Logger.LogColor.BrightRed, ex.ToString()); + /// + public void Error(Exception ex, string message) + { + if (ex == null) throw new ArgumentNullException(nameof(ex)); + if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message)); + + this.Error(() => string.Format("{0}\nTrace Back: {1}", message, ex.ToString())); } /// @@ -1101,9 +1102,7 @@ public void Error(string format, params object[] args) if (format == null) throw new ArgumentNullException(nameof(format)); if (args == null) throw new ArgumentNullException(nameof(args)); - if (!this._checkLogLevel(LogLevel.Error)) return; - - this._queueOrLog(LogLevel.Error, Logger.LogColor.BrightRed, format, args); + this.Error(() => Logger._format(format, args)); } /// @@ -1113,7 +1112,7 @@ public void Error(Func messageGetter) if (!this._checkLogLevel(LogLevel.Error)) return; - this._queueOrLog(LogLevel.Error, Logger.LogColor.BrightRed, messageGetter()); + this._queueOrLog(LogLevel.Error, Logger.LogColor.BrightRed, messageGetter); } /// @@ -1128,8 +1127,9 @@ public void Dispose() lock (Logger._loggers) Logger._loggers.Remove(this); + this._closeFileStream(); this._disposed = true; } -} +} \ No newline at end of file diff --git a/src/logging/logging/Interfaces/ILogger.cs b/src/logging/logging/Interfaces/ILogger.cs index 0b010b0..37725e1 100644 --- a/src/logging/logging/Interfaces/ILogger.cs +++ b/src/logging/logging/Interfaces/ILogger.cs @@ -42,6 +42,9 @@ public interface ILogger : IDisposable /// /// Gets or sets the value that determines if the logger should log with color to the console. /// + /// + /// This is only applicable when the OS supports ANSI escape codes. + /// bool LogWithColor { get; set; } /// @@ -60,86 +63,99 @@ public interface ILogger : IDisposable string FullyQualifiedFileName { get; } /// - /// Log a log message. - /// - /// The message format - /// Optional arguments. - void Log(string format, params object[] args); - - /// - /// Log a log message. - /// - /// A function that returns a message - void Log(Func messageGetter); - - /// - /// Log a warning message. + /// Log a warning message, usually suited for messages that are not too important + /// but are not expected behaviour. /// /// The message format /// Optional arguments. void Warning(string format, params object[] args); /// - /// Log a warning message. + /// Log a warning message, usually suited for messages that are not too important + /// but are not expected behaviour. /// /// A function that returns a message void Warning(Func messageGetter); /// - /// Log a trace message. + /// Logs a verbose message, suited for extremely verbose logging, very + /// expensive logging, and even very spammy logging, usually this should be + /// disabled if you wish to reduce resource usage on the app. + /// ignore /// /// The message format /// Optional arguments. - void Trace(string format, params object[] args); + void Verbose(string format, params object[] args); /// - /// Log a trace message. + /// Logs a verbose message, suited for extremely verbose logging, very + /// expensive logging, and even very spammy logging, usually this should be + /// disabled if you wish to reduce resource usage on the app. /// /// A function that returns a message - void Trace(Func messageGetter); + void Verbose(Func messageGetter); /// - /// Log a debug message. + /// Logs a debug message, typically verbose information that + /// reveals workings behind the code. This differs from + /// in the way that it is for more basic verbose logging, stuff that + /// can assist in debugging. /// /// The message format /// Optional arguments. void Debug(string format, params object[] args); /// - /// Log a debug message. + /// Logs a debug message, typically verbose information that + /// reveals workings behind the code. This differs from + /// in the way that it is for more basic verbose logging, stuff that + /// can assist in debugging. /// /// A function that returns a message void Debug(Func messageGetter); /// - /// Log an information message. + /// Logs an informational message, usually used for logging information + /// such as starting points, life cycle events and general basic information. /// /// The message format /// Optional arguments. void Information(string format, params object[] args); /// - /// Log an information message. + /// Logs an informational message, usually used for logging information + /// such as starting points, life cycle events and general basic information. /// /// A function that returns a message void Information(Func messageGetter); /// - /// Log an error message. + /// Log an error message, this should be used for critical + /// errors that should be seen. /// /// An exception to format. void Error(Exception ex); /// - /// Log an error message. + /// Log an error message, this should be used for critical + /// errors that should be seen. + /// + /// An exception to format. + /// The message format + void Error(Exception ex, string message); + + /// + /// Log an error message, this should be used for critical + /// errors that should be seen. /// /// The message format /// Optional arguments. void Error(string format, params object[] args); /// - /// Log an error message. + /// Log an error message, this should be used for critical + /// errors that should be seen. /// /// A function that returns a message void Error(Func messageGetter); -} +} \ No newline at end of file diff --git a/src/logging/logging/Logging.csproj b/src/logging/logging/Logging.csproj index 3944c1c..f435258 100644 --- a/src/logging/logging/Logging.csproj +++ b/src/logging/logging/Logging.csproj @@ -6,4 +6,13 @@ $(DefineConstants);CONCURRENT_LOGGING_ENABLED + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/logging/logging/NativeMethods.txt b/src/logging/logging/NativeMethods.txt new file mode 100644 index 0000000..a3973bf --- /dev/null +++ b/src/logging/logging/NativeMethods.txt @@ -0,0 +1,6 @@ +GetStdHandle +GetConsoleMode +SetConsoleMode + +STD_HANDLE +CONSOLE_MODE \ No newline at end of file From 5e2bcc8c1878d0027b38f314d87a9b5a5fe58f44 Mon Sep 17 00:00:00 2001 From: Nikita Petko Date: Fri, 4 Oct 2024 23:26:11 +0100 Subject: [PATCH 3/3] Update Configuration: Convert IVaultProvider to IDisposable Update current registered auto refresh providers to a dictionary to make concurrent removal easier. Expose the current cached values to inheritied classes. Convert mount and path to virutal to not introduce a breaking change by adding set to the mix. Move the refresh thread from a simple thread wait to a wait event system to allow manual refreshing of all global providers. Add argument to constructors to control if the provider should be added to the auto refresh thread or not. Add extra constructor to allow setting mount and path via constructor. Implement IDisposable --- .../Implementation/EnvironmentProvider.cs | 2 - .../Implementation/VaultProvider.cs | 102 +++++++++++++----- .../Interfaces/IVaultProvider.cs | 4 +- 3 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/configuration/configuration/Implementation/EnvironmentProvider.cs b/src/configuration/configuration/Implementation/EnvironmentProvider.cs index 1749e2b..35c8d26 100644 --- a/src/configuration/configuration/Implementation/EnvironmentProvider.cs +++ b/src/configuration/configuration/Implementation/EnvironmentProvider.cs @@ -1,8 +1,6 @@ namespace Configuration; using System; -using System.Linq; -using System.Collections; /// /// Implementation for that uses Environment variables. diff --git a/src/configuration/configuration/Implementation/VaultProvider.cs b/src/configuration/configuration/Implementation/VaultProvider.cs index 458c17d..861fd09 100644 --- a/src/configuration/configuration/Implementation/VaultProvider.cs +++ b/src/configuration/configuration/Implementation/VaultProvider.cs @@ -15,8 +15,10 @@ using Vault; using Logging; + using Threading.Extensions; + /// /// Implementation for via Vault. /// @@ -40,19 +42,25 @@ private static TimeSpan _defaultRefreshInterval nameof(Path), }; - private static readonly ConcurrentBag _providers = new(); + private static readonly ConcurrentDictionary _providers = new(); - private IDictionary _cachedValues = new Dictionary(); + private bool _disposed = false; + + /// + /// The raw cached values. + /// + protected IDictionary _CachedValues = new Dictionary(); + private static readonly ManualResetEvent _refreshRequestEvent = new(false); private static readonly Thread _refreshThread; private static readonly IVaultClient _client = VaultClientFactory.Singleton.GetClient(); private static readonly ILogger _staticLogger = Logger.Singleton; /// - public abstract string Mount { get; } + public virtual string Mount { get; set; } /// - public abstract string Path { get; } + public virtual string Path { get; set; } /// /// Gets the refresh interval for the settings provider. @@ -78,16 +86,25 @@ static VaultProvider() _staticLogger?.Debug("VaultProvider: Started refresh thread!"); } + /// + /// Refresh all the currently registered providers immediately, this method is asynchronous + /// and will return immediately. + /// + public static void RefreshAllProviders() + { + _refreshRequestEvent.Set(); + } + /// protected override void SetRawValue(string variable, T value) { if (_client == null) return; - _logger?.Debug("VaultProvider: Set value in vault at path '{0}/{1}/{2}'", Mount, Path, variable); + _logger?.Information("VaultProvider: Set value in vault at path '{0}/{1}/{2}'", Mount, Path, variable); var realValue = ConvertFrom(value, typeof(T)); - _cachedValues[variable] = realValue; + _CachedValues[variable] = realValue; ApplyCurrent(); } @@ -100,7 +117,7 @@ public void ApplyCurrent() // Build the current from the getters. var values = GetLatestValues(); - _logger?.Debug("VaultProvider: Writing secret '{0}/{1}' to Vault!", Mount, Path); + _logger?.Information("VaultProvider: Writing secret '{0}/{1}' to Vault!", Mount, Path); _client?.V1.Secrets.KeyValue.V2.WriteSecretAsync( mountPoint: Mount, @@ -128,7 +145,7 @@ private Dictionary GetLatestValues() try { - _logger?.Debug("VaultProvider: Fetching initial value for {0}.{1}", GetType().Name, getterName); + _logger?.Verbose("VaultProvider: Fetching initial value for {0}.{1}", GetType().Name, getterName); var value = getter.GetGetMethod().Invoke(this, Array.Empty()); var realValue = value?.ToString() ?? string.Empty; @@ -140,7 +157,7 @@ private Dictionary GetLatestValues() } catch (TargetInvocationException ex) { - _logger?.Debug("VaultProvider: Error occurred when fetching getter for '{0}.{1}': {2}", GetType().Name, getterName, ex.InnerException.Message); + _logger?.Verbose("VaultProvider: Error occurred when fetching getter for '{0}.{1}': {2}", GetType().Name, getterName, ex.InnerException.Message); newCachedValues.Add(getterName, string.Empty); } @@ -153,37 +170,55 @@ private Dictionary GetLatestValues() /// Construct a new instance of /// /// The - protected VaultProvider(ILogger logger = null) + /// Should this periodically refresh? + protected VaultProvider(ILogger logger = null, bool periodicRefresh = true) { logger ??= Logger.Singleton; SetLogger(logger); - _logger?.Debug("VaultProvider: Setup for '{0}/{1}' to refresh every '{2}' interval!", Mount, Path, RefreshInterval); - - if (_providers.Contains(this)) + if (periodicRefresh) { - _logger?.Debug("VaultProvider: Skipping setup for '{0}/{1}' because it is already setup!", Mount, Path); + _logger?.Debug("VaultProvider: Setup for '{0}/{1}' to refresh every '{2}' interval!", Mount, Path, RefreshInterval); - return; - } + if (_providers.TryGetValue(GetType().ToString(), out _)) + { + _logger?.Debug("VaultProvider: Skipping setup for '{0}/{1}' because it is already setup!", Mount, Path); + + return; + } - _providers.Add(this); + _providers.TryAdd(GetType().ToString(), this); + } DoRefresh(); } + /// + /// Construct a new instance of + /// + /// The + /// The + /// The + /// Should this periodically refresh? + protected VaultProvider(string mount, string path = "", ILogger logger = null, bool periodicRefresh = true) + : this(logger, periodicRefresh) + { + Mount = mount; + Path = path; + } + private static void RefreshThread() { while (true) { var providers = _providers.ToArray(); - foreach (var provider in providers) + foreach (var kvp in providers) { try { - provider.DoRefresh(); + kvp.Value.DoRefresh(); } catch (Exception ex) { @@ -191,7 +226,8 @@ private static void RefreshThread() } } - Thread.Sleep(RefreshInterval); // SetClient makes DoRefresh call. + _refreshRequestEvent.WaitOne(RefreshInterval); // SetClient makes DoRefresh call. + _refreshRequestEvent.Reset(); } } @@ -211,8 +247,8 @@ private void DoRefresh() var values = secret.Data.Data; InvokePropertyChangedForChangedValues(values); - lock (_cachedValues) - _cachedValues = values; + lock (_CachedValues) + _CachedValues = values; } catch (VaultApiException ex) { @@ -245,7 +281,6 @@ private bool HasProperty(ref string name) if (property == null) { _logger?.Debug("VaultProvider: Skipping property changed handler for '{0}' because settings provider '{1}' does not define it!", name, this); - _logger?.Warning("{0}: Unknown property '{1}', make sure it is defined in the settings provider or has a appropriate [{2}] attribute!", GetType().Name, name, nameof(SettingNameAttribute)); return false; } @@ -258,7 +293,7 @@ private bool HasProperty(ref string name) private void InvokePropertyChangedForChangedValues(IDictionary newValues) { - if (_cachedValues.Count == 0) + if (_CachedValues.Count == 0) { foreach (var kvp in newValues) { @@ -272,7 +307,7 @@ private void InvokePropertyChangedForChangedValues(IDictionary n var propertyName = kvp.Key; if (!HasProperty(ref propertyName)) continue; - _logger?.Debug("VaultProvider: Invoking property changed handler for '{0}'", propertyName); + _logger?.Verbose("VaultProvider: Invoking property changed handler for '{0}'", propertyName); PropertyChanged?.Invoke(this, new(propertyName)); } @@ -282,7 +317,7 @@ private void InvokePropertyChangedForChangedValues(IDictionary n foreach (var kvp in newValues) { - if (_cachedValues.TryGetValue(kvp.Key, out var value)) + if (_CachedValues.TryGetValue(kvp.Key, out var value)) if (value.ToString().Equals(kvp.Value.ToString())) continue; if (_propertyNamesIgnored.Contains(kvp.Key)) @@ -309,8 +344,8 @@ protected override bool GetRawValue(string key, out string value) { object v; - lock (_cachedValues) - if (!_cachedValues.TryGetValue(key, out v)) + lock (_CachedValues) + if (!_CachedValues.TryGetValue(key, out v)) return base.GetRawValue(key, out value); if (v is JsonElement element) @@ -320,4 +355,15 @@ protected override bool GetRawValue(string key, out string value) return true; } + + /// + public void Dispose() + { + if (_disposed) return; + + GC.SuppressFinalize(this); + + _providers.TryRemove(GetType().ToString(), out _); + _disposed = true; + } } \ No newline at end of file diff --git a/src/configuration/configuration/Interfaces/IVaultProvider.cs b/src/configuration/configuration/Interfaces/IVaultProvider.cs index a340d6d..b8c4eb4 100644 --- a/src/configuration/configuration/Interfaces/IVaultProvider.cs +++ b/src/configuration/configuration/Interfaces/IVaultProvider.cs @@ -1,11 +1,11 @@ namespace Configuration; -using VaultSharp; +using System; /// /// Represents a backed by Vault. /// -public interface IVaultProvider : IConfigurationProvider +public interface IVaultProvider : IConfigurationProvider, IDisposable { /// /// Gets the mount path.