From 4284c4a1722e006701a9d7a81341f171851b52a3 Mon Sep 17 00:00:00 2001 From: Dennis Dyall Date: Tue, 13 Aug 2024 20:25:54 +0200 Subject: [PATCH] Add thread safe initialization --- Yubico.Core/src/Yubico/Core/Logging/Log.cs | 91 ++++++++++++++++------ 1 file changed, 66 insertions(+), 25 deletions(-) diff --git a/Yubico.Core/src/Yubico/Core/Logging/Log.cs b/Yubico.Core/src/Yubico/Core/Logging/Log.cs index b92c8e1e..603e4f97 100644 --- a/Yubico.Core/src/Yubico/Core/Logging/Log.cs +++ b/Yubico.Core/src/Yubico/Core/Logging/Log.cs @@ -13,9 +13,7 @@ // limitations under the License. using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -95,14 +93,54 @@ namespace Yubico.Core.Logging /// public static partial class Log { + private static ILoggerFactory? _instance; + private static readonly object Lock = new object(); + /// - /// This is the used to internally create loggers. - /// By default, it's instantiated by using the Logging-section in your + /// Gets or sets the global instance used for logging throughout the application. + /// By default, it's instantiated by using the Logging-section in your /// appsettings.jsonfile. - /// Refer to the class for additional information. + /// Refer to the class for additional information. /// - public static ILoggerFactory Instance { get; set; } = GetDefaultLoggerFactory(); + // This property uses double-checked locking to ensure thread-safe lazy initialization. + // The getter initializes the factory if it hasn't been set, while the setter allows + // for custom factory configuration. + public static ILoggerFactory Instance + { + + get + { + // First check: Quick return if instance is already initialized + if (_instance != null) + { + return _instance; + } + + // Second check: Thread-safe initialization if instance is null + lock (Lock) + { + // Use null-coalescing assignment to initialize if still null + // This prevents multiple initializations in case of concurrent access + return _instance ??= GetDefaultLoggerFactory(); + } + } + set + { + // Ensure thread-safe assignment of new logger factory + lock (Lock) + { + // Prevent setting a null value to maintain a valid logger factory + _instance = value ?? throw new ArgumentNullException(nameof(value)); + } + } + } + /// + public static ILogger GetLogger() => Instance.CreateLogger(); + + /// + public static ILogger GetLogger(string categoryName) => Instance.CreateLogger(categoryName); + /// /// /// From your project, you can set up logging dynamically like this, if you don't use this, @@ -118,34 +156,37 @@ public static partial class Log public static void ConfigureLoggerFactory(Action configure) => Instance = Microsoft.Extensions.Logging.LoggerFactory.Create(configure); - /// - public static ILogger GetLogger() => Instance.CreateLogger(); - - /// - public static ILogger GetLogger(string categoryName) => Instance.CreateLogger(categoryName); - //Creates a logging factory based on a JsonConfiguration in appsettings.json private static ILoggerFactory GetDefaultLoggerFactory() { - IConfigurationRoot configuration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: true) - .AddJsonFile("appsettings.Development.json", optional: true) - .Build(); + ILoggerFactory? configuredLoggingFactory = null; + try + { + IConfigurationRoot configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true) + .AddJsonFile("appsettings.Development.json", optional: true) + .Build(); - IEnumerable hasSections = configuration.GetChildren(); - return hasSections.Any() - ? Microsoft.Extensions.Logging.LoggerFactory.Create( + configuredLoggingFactory = Microsoft.Extensions.Logging.LoggerFactory.Create( builder => { IConfigurationSection loggingSection = configuration.GetSection("Logging"); _ = builder.AddConfiguration(loggingSection); _ = builder.AddConsole(); - }) - : Microsoft.Extensions.Logging.LoggerFactory.Create( - builder => builder - .AddConsole() - .SetMinimumLevel(LogLevel.Error)); + }); + } + #pragma warning disable CA1031 + catch (Exception e) + #pragma warning restore CA1031 + { + Console.Error.WriteLine(e); + } + + return configuredLoggingFactory ?? Microsoft.Extensions.Logging.LoggerFactory.Create( + builder => builder + .AddConsole() + .SetMinimumLevel(LogLevel.Error)); } } }