< Summary

Information
Class: SwiftCollections.Diagnostics.DiagnosticLogger
Assembly: SwiftCollections
File(s): /home/runner/work/SwiftCollections/SwiftCollections/src/SwiftCollections/Diagnostics/DiagnosticLogger.cs
Line coverage
100%
Covered lines: 46
Uncovered lines: 0
Coverable lines: 46
Total lines: 181
Line coverage: 100%
Branch coverage
100%
Covered branches: 12
Total branches: 12
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Channel()100%11100%
get_DebugChannel()100%11100%
get_EnableDebugLogging()100%11100%
set_EnableDebugLogging(...)100%11100%
get_MinimumLevel()100%11100%
set_MinimumLevel(...)100%11100%
get_LogHandler()100%11100%
set_LogHandler(...)100%22100%
get_CustomFormatter()100%11100%
set_CustomFormatter(...)100%22100%
IsEnabled(...)100%11100%
DefaultLogHandler(...)100%22100%
DefaultLogFormatter(...)100%22100%
HandleDiagnosticEvent(...)100%11100%
ResolveSource(...)100%22100%
CreateChannel(...)100%11100%
RefreshDebugMinimumLevel()100%22100%

File(s)

/home/runner/work/SwiftCollections/SwiftCollections/src/SwiftCollections/Diagnostics/DiagnosticLogger.cs

#LineLine coverage
 1//=======================================================================
 2// DiagnosticLogger.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8namespace SwiftCollections.Diagnostics;
 9
 10using System;
 11using System.Diagnostics.CodeAnalysis;
 12
 13/// <summary>
 14/// Provides reusable logger state and diagnostic channels for libraries built on SwiftCollections diagnostics.
 15/// </summary>
 16/// <remarks>
 17/// Static logger facades can delegate to a derived instance of this type while keeping their existing public API.
 18/// </remarks>
 19public abstract class DiagnosticLogger
 20{
 21    private readonly DiagnosticChannel _channel;
 22    private readonly DiagnosticChannel _debugChannel;
 23    private Action<DiagnosticLevel, string, string> _logHandler;
 24    private Func<DiagnosticLevel, string, string, string> _customFormatter;
 25    private bool _enableDebugLogging;
 26
 27    /// <summary>
 28    /// Initializes a new instance of the <see cref="DiagnosticLogger"/> class.
 29    /// </summary>
 30    /// <param name="channelName">The diagnostic channel name.</param>
 731    protected DiagnosticLogger(string channelName)
 32    {
 733        _channel = CreateChannel(channelName);
 734        _debugChannel = CreateChannel(channelName);
 735        Name = _channel.Name;
 736        _logHandler = DefaultLogHandler;
 737        _customFormatter = DefaultLogFormatter;
 738        MinimumLevel = DiagnosticLevel.Warning;
 739        RefreshDebugMinimumLevel();
 740    }
 41
 42    /// <summary>
 43    /// Gets the logger name.
 44    /// </summary>
 45    public string Name { get; }
 46
 47    /// <summary>
 48    /// Gets the diagnostic channel used for normal diagnostics.
 49    /// </summary>
 350    public DiagnosticChannel Channel => _channel;
 51
 52    /// <summary>
 53    /// Gets the diagnostic channel used for verbose debug diagnostics.
 54    /// </summary>
 555    public DiagnosticChannel DebugChannel => _debugChannel;
 56
 57    /// <summary>
 58    /// Gets or sets a value indicating whether verbose debug diagnostics should be emitted.
 59    /// </summary>
 60    public bool EnableDebugLogging
 61    {
 162        get => _enableDebugLogging;
 63        set
 64        {
 165            _enableDebugLogging = value;
 166            RefreshDebugMinimumLevel();
 167        }
 68    }
 69
 70    /// <summary>
 71    /// Gets or sets the minimum severity required for normal diagnostics to be emitted.
 72    /// </summary>
 73    public DiagnosticLevel MinimumLevel
 74    {
 175        get => _channel.MinimumLevel;
 76        set
 77        {
 1178            _channel.MinimumLevel = value;
 1179            RefreshDebugMinimumLevel();
 1180        }
 81    }
 82
 83    /// <summary>
 84    /// Gets or sets the delegate used to write formatted log messages.
 85    /// Assigning <see langword="null"/> restores <see cref="DefaultLogHandler"/>.
 86    /// </summary>
 87    [AllowNull]
 88    public Action<DiagnosticLevel, string, string> LogHandler
 89    {
 190        get => _logHandler;
 491        set => _logHandler = value ?? DefaultLogHandler;
 92    }
 93
 94    /// <summary>
 95    /// Gets or sets the formatter used to transform log arguments into a final log entry.
 96    /// Assigning <see langword="null"/> restores <see cref="DefaultLogFormatter"/>.
 97    /// </summary>
 98    [AllowNull]
 99    public Func<DiagnosticLevel, string, string, string> CustomFormatter
 100    {
 4101        get => _customFormatter;
 2102        set => _customFormatter = value ?? DefaultLogFormatter;
 103    }
 104
 105    /// <summary>
 106    /// Determines whether normal diagnostics at the specified level are currently enabled.
 107    /// </summary>
 108    /// <param name="level">The diagnostic level to evaluate.</param>
 109    /// <returns><see langword="true"/> when messages at <paramref name="level"/> will be emitted; otherwise, <see langw
 110    public bool IsEnabled(DiagnosticLevel level)
 111    {
 2112        return _channel.IsEnabled(level);
 113    }
 114
 115    /// <summary>
 116    /// Writes a log message using the current <see cref="CustomFormatter"/>.
 117    /// </summary>
 118    /// <param name="level">The severity level of the log message.</param>
 119    /// <param name="message">The log message.</param>
 120    /// <param name="source">The source of the log message.</param>
 121    public virtual void DefaultLogHandler(DiagnosticLevel level, string message, string source)
 122    {
 2123        string entry = CustomFormatter(level, message, source);
 2124        if (level == DiagnosticLevel.Error)
 1125            Console.Error.WriteLine(entry);
 126        else
 1127            Console.WriteLine(entry);
 1128    }
 129
 130    /// <summary>
 131    /// Formats a log entry using a deterministic, source-first layout.
 132    /// </summary>
 133    /// <param name="level">The severity level of the log message.</param>
 134    /// <param name="message">The log message.</param>
 135    /// <param name="source">The source of the log message.</param>
 136    /// <returns>A formatted log entry.</returns>
 137    public virtual string DefaultLogFormatter(DiagnosticLevel level, string message, string source)
 138    {
 5139        if (string.IsNullOrWhiteSpace(source))
 2140            return $"[{level}] {Name}: {message}";
 141
 3142        return $"[{level}] {Name}.{source}: {message}";
 143    }
 144
 145    /// <summary>
 146    /// Receives diagnostics emitted by either channel.
 147    /// </summary>
 148    /// <param name="diagnostic">The emitted diagnostic event.</param>
 149    protected virtual void HandleDiagnosticEvent(in DiagnosticEvent diagnostic)
 150    {
 2151        _logHandler(diagnostic.Level, diagnostic.Message, ResolveSource(diagnostic));
 2152    }
 153
 154    /// <summary>
 155    /// Resolves the source value passed to <see cref="LogHandler"/>.
 156    /// </summary>
 157    /// <param name="diagnostic">The emitted diagnostic event.</param>
 158    /// <returns>The resolved source.</returns>
 159    protected virtual string ResolveSource(in DiagnosticEvent diagnostic)
 160    {
 2161        return string.IsNullOrWhiteSpace(diagnostic.Source)
 2162            ? diagnostic.Channel
 2163            : diagnostic.Source;
 164    }
 165
 166    private DiagnosticChannel CreateChannel(string channelName)
 167    {
 14168        return new DiagnosticChannel(channelName)
 14169        {
 14170            MinimumLevel = DiagnosticLevel.Warning,
 14171            Sink = HandleDiagnosticEvent
 14172        };
 173    }
 174
 175    private void RefreshDebugMinimumLevel()
 176    {
 19177        _debugChannel.MinimumLevel = _enableDebugLogging
 19178            ? _channel.MinimumLevel
 19179            : DiagnosticLevel.None;
 19180    }
 181}