< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Logging.LoggerManager
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Logging/LoggerManager.cs
Tag: Kestrun/Kestrun@2d87023b37eb91155071c91dd3d6a2eeb3004705
Line coverage
32%
Covered lines: 27
Uncovered lines: 57
Coverable lines: 84
Total lines: 288
Line coverage: 32.1%
Branch coverage
30%
Covered branches: 12
Total branches: 40
Branch coverage: 30%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 08/26/2025 - 01:25:22 Line coverage: 82.2% (37/45) Branch coverage: 70% (21/30) Total lines: 127 Tag: Kestrun/Kestrun@07f821172e5dc3657f1be7e6818f18d6721cf38a09/06/2025 - 18:30:33 Line coverage: 64.9% (37/57) Branch coverage: 61.7% (21/34) Total lines: 191 Tag: Kestrun/Kestrun@aeddbedb8a96e9137aac94c2d5edd011b57ac87109/07/2025 - 04:51:39 Line coverage: 34.4% (21/61) Branch coverage: 37.5% (12/32) Total lines: 226 Tag: Kestrun/Kestrun@461ff737fcae3442df54fb34b135b2349f239c4f10/13/2025 - 16:52:37 Line coverage: 32.1% (27/84) Branch coverage: 30% (12/40) Total lines: 288 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e 08/26/2025 - 01:25:22 Line coverage: 82.2% (37/45) Branch coverage: 70% (21/30) Total lines: 127 Tag: Kestrun/Kestrun@07f821172e5dc3657f1be7e6818f18d6721cf38a09/06/2025 - 18:30:33 Line coverage: 64.9% (37/57) Branch coverage: 61.7% (21/34) Total lines: 191 Tag: Kestrun/Kestrun@aeddbedb8a96e9137aac94c2d5edd011b57ac87109/07/2025 - 04:51:39 Line coverage: 34.4% (21/61) Branch coverage: 37.5% (12/32) Total lines: 226 Tag: Kestrun/Kestrun@461ff737fcae3442df54fb34b135b2349f239c4f10/13/2025 - 16:52:37 Line coverage: 32.1% (27/84) Branch coverage: 30% (12/40) Total lines: 288 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
EnsureSwitch(...)100%210%
Register(...)87.5%8887.5%
GetDefault()100%210%
CreateBaselineLogger()100%11100%
New(...)100%11100%
SetLevelSwitch(...)100%210%
GetLevelSwitch(...)0%620%
ListLevels()100%210%
CloseAndFlush(...)0%7280%
CloseAndFlush(...)0%7280%
GetName(...)0%2040%
TryGetName(...)100%210%
Contains(...)100%210%
Contains(...)100%210%
Contains(...)100%210%
get_DefaultLoggerName()100%210%
set_DefaultLoggerName(...)0%620%
get_DefaultLogger()100%210%
set_DefaultLogger(...)0%620%
Get(...)50%22100%
List()100%210%
ListLoggers()100%210%
Clear()100%44100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Logging/LoggerManager.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using Serilog;
 3using Serilog.Core;
 4using Serilog.Events;
 5
 6namespace Kestrun.Logging;
 7
 8/// <summary>
 9/// Manages a collection of named Serilog loggers and their configurations.
 10/// </summary>
 11public static class LoggerManager
 12{
 113    private static readonly ConcurrentDictionary<string, Serilog.ILogger> _loggers = new(StringComparer.OrdinalIgnoreCas
 114    private static readonly ConcurrentDictionary<string, LoggerConfiguration> _configs = new(StringComparer.OrdinalIgnor
 15    // Synchronization object to guard global default logger transitions and coordinated disposal
 16#if NET9_0_OR_GREATER
 117    private static readonly Lock _sync = new();
 18#else
 19    private static readonly object _sync = new();
 20#endif
 21
 22    /// <summary>
 23    /// A collection of named logging level switches for dynamic log level control.
 24    /// </summary>
 125    private static readonly ConcurrentDictionary<string, LoggingLevelSwitch> _switches = new(StringComparer.OrdinalIgnor
 26
 27
 28    internal static LoggingLevelSwitch EnsureSwitch(string name, LogEventLevel initial = LogEventLevel.Information)
 029        => _switches.GetOrAdd(name, _ => new LoggingLevelSwitch(initial));
 30
 31    /// <summary>
 32    /// Register an existing Serilog logger instance under a name.
 33    /// </summary>
 34    /// <param name="name">The name of the logger.</param>
 35    /// <param name="logger">The logger instance to register.</param>
 36    /// <param name="setAsDefault">If true, sets the registered logger as the Serilog default logger.</param>
 37    /// <param name="levelSwitch">An optional logging level switch to associate with the logger for dynamic level contro
 38    /// <returns>The registered logger instance.</returns>
 39    public static Serilog.ILogger Register(string name, Serilog.ILogger logger, bool setAsDefault = false, LoggingLevelS
 40    {
 341        if (_loggers.TryGetValue(name, out var oldLogger) && oldLogger is IDisposable d)
 42        {
 143            d.Dispose();
 44        }
 45
 346        _loggers[name] = logger;
 347        if (setAsDefault)
 48        {
 349            Log.Logger = logger;
 50        }
 351        if (levelSwitch != null)
 52        {
 053            _switches[name] = levelSwitch;
 54        }
 355        return logger;
 56    }
 57
 58    /// <summary>
 59    /// Returns the current Serilog default logger (Log.Logger).
 60    /// </summary>
 061    public static Serilog.ILogger GetDefault() => Log.Logger;
 62
 563    private static Serilog.ILogger CreateBaselineLogger() => new LoggerConfiguration().CreateLogger();
 64
 65    /// <summary>
 66    /// Create a new <see cref="LoggerConfiguration"/> associated with a name.
 67    /// </summary>
 68    /// <param name="name">The name of the logger configuration.</param>
 69    /// <returns>The new logger configuration.</returns>
 70    public static LoggerConfiguration New(string name)
 71    {
 172        var cfg = new LoggerConfiguration()
 173            .Enrich.WithProperty("LoggerName", name);
 174        _configs[name] = cfg;
 175        return cfg;
 76    }
 77
 78
 79    /// <summary>Set the minimum level for a named logger’s switch.</summary>
 80    /// <param name="name">The name of the logger.</param>
 81    /// <param name="level">The new minimum level to set.</param>
 82    /// <remarks>
 83    /// If the logger or switch does not exist, they will be created.
 84    /// </remarks>
 85    public static void SetLevelSwitch(string name, LogEventLevel level)
 86    {
 087        var sw = EnsureSwitch(name, level);
 088        sw.MinimumLevel = level;
 089    }
 90
 91    /// <summary>Get the current minimum level for a named logger’s switch.</summary>
 92    /// <param name="name">The name of the logger.</param>
 93    /// <returns>The current minimum level, or null if the logger or switch is not found.</returns>
 94    public static LogEventLevel? GetLevelSwitch(string name)
 095        => _switches.TryGetValue(name, out var sw) ? sw.MinimumLevel : null;
 96
 97    /// <summary>List all switches and their current levels.</summary>
 98    /// <returns>A dictionary of logger names and their minimum levels.</returns>
 99    public static IReadOnlyDictionary<string, LogEventLevel> ListLevels()
 0100        => _switches.ToDictionary(kv => kv.Key, kv => kv.Value.MinimumLevel, StringComparer.OrdinalIgnoreCase);
 101
 102
 103    /// <summary>CloseAndFlush a logger by name.</summary>
 104    /// <param name="name">The name of the logger to close and flush.</param>
 105    /// <returns> True if the logger was found and closed; otherwise, false.</returns>
 106    public static bool CloseAndFlush(string name)
 107    {
 0108        if (!_loggers.TryRemove(name, out var logger))
 109        {
 0110            return false;
 111        }
 112
 113        bool wasDefault;
 114        // Capture & decide inside lock to avoid race with other threads mutating Log.Logger
 115        lock (_sync)
 116        {
 0117            wasDefault = ReferenceEquals(Log.Logger, logger);
 0118        }
 119
 0120        if (logger is IDisposable d)
 121        {
 122            // Dispose outside lock (Serilog flush/dispose can perform I/O)
 0123            d.Dispose();
 124        }
 0125        _ = _configs.TryRemove(name, out _);
 0126        _ = _switches.TryRemove(name, out _);
 127
 0128        if (wasDefault)
 0129        {
 130            lock (_sync)
 131            {
 132                // Re-check in case default changed while disposing
 0133                if (ReferenceEquals(Log.Logger, logger))
 134                {
 0135                    Log.Logger = CreateBaselineLogger();
 136                }
 0137            }
 138        }
 0139        return true;
 140    }
 141
 142    /// <summary>
 143    /// CloseAndFlush a logger instance.
 144    /// </summary>
 145    /// <param name="logger">The logger instance to close and flush.</param>
 146    /// <returns>True if the logger was found and closed; otherwise, false.</returns>
 147    public static bool CloseAndFlush(Serilog.ILogger logger)
 0148    {
 149        bool wasDefault;
 150        lock (_sync)
 151        {
 0152            wasDefault = ReferenceEquals(Log.Logger, logger);
 0153        }
 154
 0155        if (logger is IDisposable d)
 156        {
 0157            d.Dispose();
 158        }
 159
 0160        var removed = false;
 0161        var keys = _loggers.Where(kv => ReferenceEquals(kv.Value, logger)).Select(kv => kv.Key).ToList();
 0162        foreach (var key in keys)
 163        {
 0164            _ = _loggers.TryRemove(key, out _);
 0165            _ = _configs.TryRemove(key, out _);
 0166            _ = _switches.TryRemove(key, out _);
 0167            removed = true;
 168        }
 169
 0170        if (wasDefault)
 0171        {
 172            lock (_sync)
 173            {
 0174                if (ReferenceEquals(Log.Logger, logger))
 175                {
 0176                    Log.Logger = CreateBaselineLogger();
 177                }
 0178            }
 179        }
 0180        return removed;
 181    }
 182
 183    /// <summary>
 184    /// Get the name of a registered logger instance.
 185    /// </summary>
 186    /// <param name="logger">The logger instance.</param>
 187    /// <returns>The name of the logger, or null if not found.</returns>
 188    public static string? GetName(Serilog.ILogger logger)
 189    {
 0190        foreach (var kv in _loggers)
 191        {
 0192            if (ReferenceEquals(kv.Value, logger))
 193            {
 0194                return kv.Key;
 195            }
 196        }
 197
 0198        return null;
 0199    }
 200
 201    /// <summary>
 202    /// Try to get the name of a registered logger instance.
 203    /// </summary>
 204    /// <param name="logger">The logger instance.</param>
 205    /// <param name="name">The name of the logger, if found.</param>
 206    /// <returns>True if the name was found; otherwise, false.</returns>
 207    public static bool TryGetName(Serilog.ILogger logger, out string? name)
 208    {
 0209        name = GetName(logger);
 0210        return name is not null;
 211    }
 212    /// <summary>
 213    /// Check if a logger, configuration, or name exists.
 214    /// </summary>
 215    /// <param name="name">The name of the logger.</param>
 216    /// <returns> True if the logger exists; otherwise, false.</returns>
 0217    public static bool Contains(string name) => _loggers.ContainsKey(name);
 218
 219    /// <summary>
 220    /// Check if a logger, configuration, or name exists.
 221    /// </summary>
 222    /// <param name="logger">The logger instance.</param>
 223    /// <returns> True if the logger exists; otherwise, false.</returns>
 0224    public static bool Contains(Serilog.ILogger logger) => _loggers.Values.Contains(logger);
 225
 226    /// <summary>
 227    /// Check if a logger, configuration, or name exists.
 228    /// </summary>
 229    /// <param name="config">The logger configuration instance.</param>
 230    /// <returns> True if the configuration exists; otherwise, false.</returns>
 0231    public static bool Contains(LoggerConfiguration config) => _configs.Values.Contains(config);
 232
 233
 234    /// <summary>The name of the logger currently set as the Serilog default.</summary>
 235    /// <exception cref="ArgumentException">When the specified logger name is not found.</exception>
 236    public static string DefaultLoggerName
 237    {
 0238        get => _loggers.FirstOrDefault(x => x.Value == Log.Logger).Key;
 0239        set => Log.Logger = _loggers.TryGetValue(value, out var logger) ? logger :
 0240            throw new ArgumentException($"Logger '{value}' not found.", nameof(value));
 241    }
 242
 243    /// <summary>Access the Serilog default logger.</summary>
 244    /// <remarks>Setting this property to null resets the default logger to a new empty logger.</remarks>
 245    public static Serilog.ILogger DefaultLogger
 246    {
 0247        get => Log.Logger;
 0248        set => Log.Logger = value ?? new LoggerConfiguration().CreateLogger();
 249    }
 250
 251    /// <summary>Get a logger by name, or null if not found.</summary>
 252    /// <param name="name">The name of the logger.</param>
 253    /// <returns>The logger instance, or null if not found.</returns>
 2254    public static Serilog.ILogger? Get(string name) => _loggers.TryGetValue(name, out var logger) ? logger : null;
 255
 256    /// <summary>List all registered logger names.</summary>
 0257    public static string[] List() => [.. _loggers.Keys];
 258
 259    /// <summary>
 260    /// List all registered logger instances.
 261    /// </summary>
 262    /// <remarks>
 263    /// The returned array is a snapshot; subsequent registrations or disposals will not affect it.
 264    /// </remarks>
 0265    public static Serilog.ILogger[] ListLoggers() => [.. _loggers.Values];
 266
 267    /// <summary>Remove and dispose all registered loggers.</summary>
 268    /// <remarks>Also clears the default logger.</remarks>
 269    public static void Clear()
 270    {
 271        // Snapshot keys to minimize time under lock and avoid enumerating while mutated
 5272        var snapshot = _loggers.ToArray();
 14273        foreach (var (_, logger) in snapshot)
 274        {
 2275            if (logger is IDisposable d)
 276            {
 4277                try { d.Dispose(); } catch { /* swallow to ensure all loggers attempt disposal */ }
 278            }
 279        }
 5280        _loggers.Clear();
 5281        _configs.Clear();
 5282        _switches.Clear();
 283        lock (_sync)
 284        {
 5285            Log.Logger = CreateBaselineLogger();
 5286        }
 5287    }
 288}