< 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@5f1d2b981c9d7292c11fd448428c6ab6c811c5de
Line coverage
88%
Covered lines: 74
Uncovered lines: 10
Coverable lines: 84
Total lines: 284
Line coverage: 88%
Branch coverage
82%
Covered branches: 33
Total branches: 40
Branch coverage: 82.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 11/19/2025 - 17:40:50 Line coverage: 32.1% (27/84) Branch coverage: 30% (12/40) Total lines: 288 Tag: Kestrun/Kestrun@fcf33342333cef0516fe0d0912a86709874fd02612/15/2025 - 02:23:46 Line coverage: 32.5% (28/86) Branch coverage: 30% (12/40) Total lines: 288 Tag: Kestrun/Kestrun@7a3839f4de2254e22daae81ab8dc7cb2f40c833012/15/2025 - 04:25:23 Line coverage: 88.3% (76/86) Branch coverage: 82.5% (33/40) Total lines: 288 Tag: Kestrun/Kestrun@e333660af9731cab5ae4c14a12f3bb84a8fabc7d03/26/2026 - 03:54:59 Line coverage: 88% (74/84) Branch coverage: 82.5% (33/40) Total lines: 288 Tag: Kestrun/Kestrun@844b5179fb0492dc6b1182bae3ff65fa7365521d04/19/2026 - 15:52:57 Line coverage: 88% (74/84) Branch coverage: 82.5% (33/40) Total lines: 284 Tag: Kestrun/Kestrun@765a8f13c573c01494250a29d6392b6037f087c9 11/19/2025 - 17:40:50 Line coverage: 32.1% (27/84) Branch coverage: 30% (12/40) Total lines: 288 Tag: Kestrun/Kestrun@fcf33342333cef0516fe0d0912a86709874fd02612/15/2025 - 02:23:46 Line coverage: 32.5% (28/86) Branch coverage: 30% (12/40) Total lines: 288 Tag: Kestrun/Kestrun@7a3839f4de2254e22daae81ab8dc7cb2f40c833012/15/2025 - 04:25:23 Line coverage: 88.3% (76/86) Branch coverage: 82.5% (33/40) Total lines: 288 Tag: Kestrun/Kestrun@e333660af9731cab5ae4c14a12f3bb84a8fabc7d03/26/2026 - 03:54:59 Line coverage: 88% (74/84) Branch coverage: 82.5% (33/40) Total lines: 288 Tag: Kestrun/Kestrun@844b5179fb0492dc6b1182bae3ff65fa7365521d04/19/2026 - 15:52:57 Line coverage: 88% (74/84) Branch coverage: 82.5% (33/40) Total lines: 284 Tag: Kestrun/Kestrun@765a8f13c573c01494250a29d6392b6037f087c9

Coverage delta

Coverage delta 56 -56

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
EnsureSwitch(...)100%11100%
Register(...)100%88100%
GetDefault()100%11100%
CreateBaselineLogger()100%11100%
New(...)100%11100%
SetLevelSwitch(...)100%11100%
GetLevelSwitch(...)100%22100%
ListLevels()100%11100%
CloseAndFlush(...)62.5%9871.43%
CloseAndFlush(...)62.5%9877.78%
GetName(...)100%44100%
TryGetName(...)100%11100%
Contains(...)100%11100%
Contains(...)100%11100%
Contains(...)100%11100%
get_DefaultLoggerName()100%11100%
set_DefaultLoggerName(...)100%22100%
get_DefaultLogger()100%11100%
set_DefaultLogger(...)50%22100%
Get(...)100%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    internal static LoggingLevelSwitch EnsureSwitch(string name, LogEventLevel initial = LogEventLevel.Information)
 1128        => _switches.GetOrAdd(name, _ => new LoggingLevelSwitch(initial));
 29
 30    /// <summary>
 31    /// Register an existing Serilog logger instance under a name.
 32    /// </summary>
 33    /// <param name="name">The name of the logger.</param>
 34    /// <param name="logger">The logger instance to register.</param>
 35    /// <param name="setAsDefault">If true, sets the registered logger as the Serilog default logger.</param>
 36    /// <param name="levelSwitch">An optional logging level switch to associate with the logger for dynamic level contro
 37    /// <returns>The registered logger instance.</returns>
 38    public static Serilog.ILogger Register(string name, Serilog.ILogger logger, bool setAsDefault = false, LoggingLevelS
 39    {
 2340        if (_loggers.TryGetValue(name, out var oldLogger) && oldLogger is IDisposable d)
 41        {
 142            d.Dispose();
 43        }
 44
 2345        _loggers[name] = logger;
 2346        if (setAsDefault)
 47        {
 748            Log.Logger = logger;
 49        }
 2350        if (levelSwitch != null)
 51        {
 252            _switches[name] = levelSwitch;
 53        }
 2354        return logger;
 55    }
 56
 57    /// <summary>
 58    /// Returns the current Serilog default logger (Log.Logger).
 59    /// </summary>
 160    public static Serilog.ILogger GetDefault() => Log.Logger;
 61
 4662    private static Serilog.ILogger CreateBaselineLogger() => new LoggerConfiguration().CreateLogger();
 63
 64    /// <summary>
 65    /// Create a new <see cref="LoggerConfiguration"/> associated with a name.
 66    /// </summary>
 67    /// <param name="name">The name of the logger configuration.</param>
 68    /// <returns>The new logger configuration.</returns>
 69    public static LoggerConfiguration New(string name)
 70    {
 271        var cfg = new LoggerConfiguration()
 272            .Enrich.WithProperty("LoggerName", name);
 273        _configs[name] = cfg;
 274        return cfg;
 75    }
 76
 77    /// <summary>Set the minimum level for a named logger’s switch.</summary>
 78    /// <param name="name">The name of the logger.</param>
 79    /// <param name="level">The new minimum level to set.</param>
 80    /// <remarks>
 81    /// If the logger or switch does not exist, they will be created.
 82    /// </remarks>
 83    public static void SetLevelSwitch(string name, LogEventLevel level)
 84    {
 685        var sw = EnsureSwitch(name, level);
 686        sw.MinimumLevel = level;
 687    }
 88
 89    /// <summary>Get the current minimum level for a named logger’s switch.</summary>
 90    /// <param name="name">The name of the logger.</param>
 91    /// <returns>The current minimum level, or null if the logger or switch is not found.</returns>
 92    public static LogEventLevel? GetLevelSwitch(string name)
 393        => _switches.TryGetValue(name, out var sw) ? sw.MinimumLevel : null;
 94
 95    /// <summary>List all switches and their current levels.</summary>
 96    /// <returns>A dictionary of logger names and their minimum levels.</returns>
 97    public static IReadOnlyDictionary<string, LogEventLevel> ListLevels()
 798        => _switches.ToDictionary(kv => kv.Key, kv => kv.Value.MinimumLevel, StringComparer.OrdinalIgnoreCase);
 99
 100    /// <summary>CloseAndFlush a logger by name.</summary>
 101    /// <param name="name">The name of the logger to close and flush.</param>
 102    /// <returns> True if the logger was found and closed; otherwise, false.</returns>
 103    public static bool CloseAndFlush(string name)
 104    {
 2105        if (!_loggers.TryRemove(name, out var logger))
 106        {
 1107            return false;
 108        }
 109
 110        bool wasDefault;
 111        // Capture & decide inside lock to avoid race with other threads mutating Log.Logger
 112        lock (_sync)
 113        {
 1114            wasDefault = ReferenceEquals(Log.Logger, logger);
 1115        }
 116
 1117        if (logger is IDisposable d)
 118        {
 119            // Dispose outside lock (Serilog flush/dispose can perform I/O)
 1120            d.Dispose();
 121        }
 1122        _ = _configs.TryRemove(name, out _);
 1123        _ = _switches.TryRemove(name, out _);
 124
 1125        if (wasDefault)
 0126        {
 127            lock (_sync)
 128            {
 129                // Re-check in case default changed while disposing
 0130                if (ReferenceEquals(Log.Logger, logger))
 131                {
 0132                    Log.Logger = CreateBaselineLogger();
 133                }
 0134            }
 135        }
 1136        return true;
 137    }
 138
 139    /// <summary>
 140    /// CloseAndFlush a logger instance.
 141    /// </summary>
 142    /// <param name="logger">The logger instance to close and flush.</param>
 143    /// <returns>True if the logger was found and closed; otherwise, false.</returns>
 144    public static bool CloseAndFlush(Serilog.ILogger logger)
 2145    {
 146        bool wasDefault;
 147        lock (_sync)
 148        {
 2149            wasDefault = ReferenceEquals(Log.Logger, logger);
 2150        }
 151
 2152        if (logger is IDisposable d)
 153        {
 2154            d.Dispose();
 155        }
 156
 2157        var removed = false;
 4158        var keys = _loggers.Where(kv => ReferenceEquals(kv.Value, logger)).Select(kv => kv.Key).ToList();
 6159        foreach (var key in keys)
 160        {
 1161            _ = _loggers.TryRemove(key, out _);
 1162            _ = _configs.TryRemove(key, out _);
 1163            _ = _switches.TryRemove(key, out _);
 1164            removed = true;
 165        }
 166
 2167        if (wasDefault)
 0168        {
 169            lock (_sync)
 170            {
 0171                if (ReferenceEquals(Log.Logger, logger))
 172                {
 0173                    Log.Logger = CreateBaselineLogger();
 174                }
 0175            }
 176        }
 2177        return removed;
 178    }
 179
 180    /// <summary>
 181    /// Get the name of a registered logger instance.
 182    /// </summary>
 183    /// <param name="logger">The logger instance.</param>
 184    /// <returns>The name of the logger, or null if not found.</returns>
 185    public static string? GetName(Serilog.ILogger logger)
 186    {
 10187        foreach (var kv in _loggers)
 188        {
 2189            if (ReferenceEquals(kv.Value, logger))
 190            {
 2191                return kv.Key;
 192            }
 193        }
 194
 2195        return null;
 2196    }
 197
 198    /// <summary>
 199    /// Try to get the name of a registered logger instance.
 200    /// </summary>
 201    /// <param name="logger">The logger instance.</param>
 202    /// <param name="name">The name of the logger, if found.</param>
 203    /// <returns>True if the name was found; otherwise, false.</returns>
 204    public static bool TryGetName(Serilog.ILogger logger, out string? name)
 205    {
 2206        name = GetName(logger);
 2207        return name is not null;
 208    }
 209    /// <summary>
 210    /// Check if a logger, configuration, or name exists.
 211    /// </summary>
 212    /// <param name="name">The name of the logger.</param>
 213    /// <returns> True if the logger exists; otherwise, false.</returns>
 2214    public static bool Contains(string name) => _loggers.ContainsKey(name);
 215
 216    /// <summary>
 217    /// Check if a logger, configuration, or name exists.
 218    /// </summary>
 219    /// <param name="logger">The logger instance.</param>
 220    /// <returns> True if the logger exists; otherwise, false.</returns>
 2221    public static bool Contains(Serilog.ILogger logger) => _loggers.Values.Contains(logger);
 222
 223    /// <summary>
 224    /// Check if a logger, configuration, or name exists.
 225    /// </summary>
 226    /// <param name="config">The logger configuration instance.</param>
 227    /// <returns> True if the configuration exists; otherwise, false.</returns>
 1228    public static bool Contains(LoggerConfiguration config) => _configs.Values.Contains(config);
 229
 230    /// <summary>The name of the logger currently set as the Serilog default.</summary>
 231    /// <exception cref="ArgumentException">When the specified logger name is not found.</exception>
 232    public static string DefaultLoggerName
 233    {
 2234        get => _loggers.FirstOrDefault(x => x.Value == Log.Logger).Key;
 2235        set => Log.Logger = _loggers.TryGetValue(value, out var logger) ? logger :
 2236            throw new ArgumentException($"Logger '{value}' not found.", nameof(value));
 237    }
 238
 239    /// <summary>Access the Serilog default logger.</summary>
 240    /// <remarks>Setting this property to null resets the default logger to a new empty logger.</remarks>
 241    public static Serilog.ILogger DefaultLogger
 242    {
 1243        get => Log.Logger;
 1244        set => Log.Logger = value ?? new LoggerConfiguration().CreateLogger();
 245    }
 246
 247    /// <summary>Get a logger by name, or null if not found.</summary>
 248    /// <param name="name">The name of the logger.</param>
 249    /// <returns>The logger instance, or null if not found.</returns>
 9250    public static Serilog.ILogger? Get(string name) => _loggers.TryGetValue(name, out var logger) ? logger : null;
 251
 252    /// <summary>List all registered logger names.</summary>
 0253    public static string[] List() => [.. _loggers.Keys];
 254
 255    /// <summary>
 256    /// List all registered logger instances.
 257    /// </summary>
 258    /// <remarks>
 259    /// The returned array is a snapshot; subsequent registrations or disposals will not affect it.
 260    /// </remarks>
 0261    public static Serilog.ILogger[] ListLoggers() => [.. _loggers.Values];
 262
 263    /// <summary>Remove and dispose all registered loggers.</summary>
 264    /// <remarks>Also clears the default logger.</remarks>
 265    public static void Clear()
 266    {
 267        // Snapshot keys to minimize time under lock and avoid enumerating while mutated
 46268        var snapshot = _loggers.ToArray();
 130269        foreach (var (_, logger) in snapshot)
 270        {
 19271            if (logger is IDisposable d)
 272            {
 38273                try { d.Dispose(); } catch { /* swallow to ensure all loggers attempt disposal */ }
 274            }
 275        }
 46276        _loggers.Clear();
 46277        _configs.Clear();
 46278        _switches.Clear();
 279        lock (_sync)
 280        {
 46281            Log.Logger = CreateBaselineLogger();
 46282        }
 46283    }
 284}