< Summary

Information
Class: NexusLabs.Needlr.Logging.NeedlrCancellationLogging
Assembly: NexusLabs.Needlr.Logging
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Logging/NeedlrCancellationLogging.cs
Line coverage
100%
Covered lines: 36
Uncovered lines: 0
Coverable lines: 36
Total lines: 164
Line coverage: 100%
Branch coverage
100%
Covered branches: 24
Total branches: 24
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
get_Behavior()100%22100%
set_Behavior(...)100%11100%
get_DemotedLevel()100%22100%
set_DemotedLevel(...)100%11100%
get_IsCancellationPredicate()100%22100%
set_IsCancellationPredicate(...)100%22100%
IsCancellation(...)100%22100%
DefaultIsCancellation(...)100%11100%
ParseBehavior(...)100%88100%
ParseDemotedLevel(...)100%66100%
ReadBehaviorFromEnvironment()100%11100%
ReadDemotedLevelFromEnvironment()100%11100%
ResetForTests()100%11100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Logging/NeedlrCancellationLogging.cs

#LineLine coverage
 1using System;
 2using System.Threading;
 3
 4using Microsoft.Extensions.Logging;
 5
 6namespace NexusLabs.Needlr.Logging;
 7
 8/// <summary>
 9/// Global, process-wide policy that Needlr source-generated logging methods consult when their
 10/// exception argument is a cancellation. Provides an escape hatch from the opinionated default
 11/// (<see cref="CancellationLoggingBehavior.Skip"/>).
 12/// </summary>
 13/// <remarks>
 14/// <para>
 15/// This is a deliberate <em>global feature switch</em> (comparable to an
 16/// <see cref="AppContext"/> switch), not a service-locator: it must be reachable from allocation-free
 17/// generated method bodies without threading state through every call site. Set the properties once at
 18/// application startup — for example, to differ between Debug and Release builds.
 19/// </para>
 20/// <para>
 21/// The initial defaults are read <strong>once</strong>, lazily, the first time they are needed, from
 22/// environment variables, and then cached for the lifetime of the process:
 23/// </para>
 24/// <list type="bullet">
 25/// <item>
 26/// <description>
 27/// <see cref="BehaviorEnvironmentVariable"/> — <c>skip</c> (default), <c>log</c>, or <c>demote</c>
 28/// (case-insensitive).
 29/// </description>
 30/// </item>
 31/// <item>
 32/// <description>
 33/// <see cref="DemotedLevelEnvironmentVariable"/> — a <see cref="LogLevel"/> name (e.g. <c>Debug</c>).
 34/// </description>
 35/// </item>
 36/// </list>
 37/// <para>
 38/// Assigning any property overrides the environment-derived default from that point on.
 39/// </para>
 40/// </remarks>
 41public static class NeedlrCancellationLogging
 42{
 43    /// <summary>
 44    /// The environment variable that supplies the initial <see cref="Behavior"/> default:
 45    /// <c>skip</c>, <c>log</c>, or <c>demote</c> (case-insensitive). Unrecognized or missing values
 46    /// fall back to <see cref="CancellationLoggingBehavior.Skip"/>.
 47    /// </summary>
 48    public const string BehaviorEnvironmentVariable = "__NEEDLR_CANCELLATION_LOGGING_BEHAVIOR";
 49
 50    /// <summary>
 51    /// The environment variable that supplies the initial <see cref="DemotedLevel"/> default: any
 52    /// <see cref="LogLevel"/> name (case-insensitive). Unrecognized or missing values fall back to
 53    /// <see cref="LogLevel.Debug"/>.
 54    /// </summary>
 55    public const string DemotedLevelEnvironmentVariable = "__NEEDLR_CANCELLATION_LOGGING_DEMOTED_LEVEL";
 56
 157    private static Lazy<CancellationLoggingBehavior> _defaultBehavior =
 158        new(ReadBehaviorFromEnvironment, LazyThreadSafetyMode.ExecutionAndPublication);
 59
 160    private static Lazy<LogLevel> _defaultDemotedLevel =
 161        new(ReadDemotedLevelFromEnvironment, LazyThreadSafetyMode.ExecutionAndPublication);
 62
 63    private static CancellationLoggingBehavior? _behaviorOverride;
 64    private static LogLevel? _demotedLevelOverride;
 65    private static Func<Exception, bool>? _isCancellationOverride;
 66
 67    /// <summary>
 68    /// Gets or sets how generated logging methods treat a cancellation exception. Defaults to the
 69    /// value of <see cref="BehaviorEnvironmentVariable"/>, or
 70    /// <see cref="CancellationLoggingBehavior.Skip"/> when unset.
 71    /// </summary>
 72    public static CancellationLoggingBehavior Behavior
 73    {
 1174        get => _behaviorOverride ?? _defaultBehavior.Value;
 575        set => _behaviorOverride = value;
 76    }
 77
 78    /// <summary>
 79    /// Gets or sets the level used when <see cref="Behavior"/> is
 80    /// <see cref="CancellationLoggingBehavior.Demote"/>. Defaults to the value of
 81    /// <see cref="DemotedLevelEnvironmentVariable"/>, or <see cref="LogLevel.Debug"/> when unset.
 82    /// </summary>
 83    public static LogLevel DemotedLevel
 84    {
 485        get => _demotedLevelOverride ?? _defaultDemotedLevel.Value;
 386        set => _demotedLevelOverride = value;
 87    }
 88
 89    /// <summary>
 90    /// Gets or sets the predicate that decides whether an exception counts as a cancellation. The
 91    /// default treats any <see cref="OperationCanceledException"/> (which includes
 92    /// <see cref="System.Threading.Tasks.TaskCanceledException"/>) as a cancellation.
 93    /// </summary>
 94    /// <exception cref="ArgumentNullException">Thrown when set to <see langword="null"/>.</exception>
 95    public static Func<Exception, bool> IsCancellationPredicate
 96    {
 1697        get => _isCancellationOverride ?? DefaultIsCancellation;
 398        set => _isCancellationOverride = value ?? throw new ArgumentNullException(nameof(value));
 99    }
 100
 101    /// <summary>
 102    /// Determines whether the supplied exception is considered a cancellation, according to
 103    /// <see cref="IsCancellationPredicate"/>. This is the entry point called by generated code.
 104    /// </summary>
 105    /// <param name="exception">The exception argument passed to a logging method; may be <see langword="null"/>.</param
 106    /// <returns>
 107    /// <see langword="true"/> when <paramref name="exception"/> is non-null and the predicate
 108    /// classifies it as a cancellation; otherwise <see langword="false"/>.
 109    /// </returns>
 110    public static bool IsCancellation(Exception? exception) =>
 17111        exception is not null && IsCancellationPredicate(exception);
 112
 113    private static bool DefaultIsCancellation(Exception exception) =>
 12114        exception is OperationCanceledException;
 115
 116    internal static CancellationLoggingBehavior ParseBehavior(string? raw)
 117    {
 17118        if (string.IsNullOrWhiteSpace(raw))
 119        {
 9120            return CancellationLoggingBehavior.Skip;
 121        }
 122
 8123        return raw!.Trim().ToLowerInvariant() switch
 8124        {
 2125            "skip" => CancellationLoggingBehavior.Skip,
 2126            "log" => CancellationLoggingBehavior.Log,
 3127            "demote" => CancellationLoggingBehavior.Demote,
 1128            _ => CancellationLoggingBehavior.Skip,
 8129        };
 130    }
 131
 132    internal static LogLevel ParseDemotedLevel(string? raw)
 133    {
 10134        if (!string.IsNullOrWhiteSpace(raw) &&
 10135            Enum.TryParse(raw!.Trim(), ignoreCase: true, out LogLevel level) &&
 10136            Enum.IsDefined(typeof(LogLevel), level))
 137        {
 5138            return level;
 139        }
 140
 5141        return LogLevel.Debug;
 142    }
 143
 144    private static CancellationLoggingBehavior ReadBehaviorFromEnvironment() =>
 7145        ParseBehavior(Environment.GetEnvironmentVariable(BehaviorEnvironmentVariable));
 146
 147    private static LogLevel ReadDemotedLevelFromEnvironment() =>
 2148        ParseDemotedLevel(Environment.GetEnvironmentVariable(DemotedLevelEnvironmentVariable));
 149
 150    /// <summary>
 151    /// Resets all overrides and re-arms the lazy environment reads. Intended for test isolation so a
 152    /// test can configure environment variables and observe the freshly-derived defaults.
 153    /// </summary>
 154    internal static void ResetForTests()
 155    {
 92156        _behaviorOverride = null;
 92157        _demotedLevelOverride = null;
 92158        _isCancellationOverride = null;
 92159        _defaultBehavior = new Lazy<CancellationLoggingBehavior>(
 92160            ReadBehaviorFromEnvironment, LazyThreadSafetyMode.ExecutionAndPublication);
 92161        _defaultDemotedLevel = new Lazy<LogLevel>(
 92162            ReadDemotedLevelFromEnvironment, LazyThreadSafetyMode.ExecutionAndPublication);
 92163    }
 164}