< Summary

Information
Class: AmbientServices.AmbientLogSplitter
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/AlternateImplementations/AmbientLogSplitter.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 33
Uncovered lines: 0
Coverable lines: 33
Total lines: 143
Line coverage: 100%
Branch coverage
100%
Covered branches: 20
Total branches: 20
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%
AddSimpleLogger(...)100%22100%
RemoveSimpleLogger(...)100%22100%
AddLogger(...)100%22100%
RemoveLogger(...)100%22100%
Log(...)100%22100%
Log(...)100%22100%
Flush()100%44100%
ToString()100%44100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/AlternateImplementations/AmbientLogSplitter.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Threading;
 5using System.Threading.Tasks;
 6
 7namespace AmbientServices;
 8
 9/// <summary>
 10/// A basic implementation of <see cref="IAmbientLogger"/> and <see cref="IAmbientStructuredLogger"/> that fans each log
 11/// </summary>
 12/// <remarks>
 13/// <pitch>Fan-out: register it as the one ambient logger and it forwards every entry to any number of underlying logger
 14/// <pledge><see cref="IAmbientLogger"/></pledge>
 15/// <pledge><see cref="IAmbientStructuredLogger"/></pledge>
 16/// <pledge>
 17/// Every simple log entry is forwarded to each registered simple logger and every structured entry to each registered s
 18/// Registration and removal are <em>not</em> thread-safe and must complete during application initialization, before co
 19/// </pledge>
 20/// <plan>
 21/// Two plain <see cref="List{T}"/>s (simple and structured); each add/remove cross-enrolls the logger in the other list
 22/// Trade-off profile: per-entry cost is one virtual call per target with zero added allocation; latency and durability 
 23/// </plan>
 24/// <priority>
 25/// <see cref="IAmbientLogger"/>
 26/// 1. A lock-free logging path over registration you can change at any time: registration and removal are deliberately 
 27/// 2. Adding nothing of its own over being useful in the middle: no buffering, filtering, or rendering happens here — e
 28/// </priority>
 29/// </remarks>
 30public class AmbientLogSplitter : IAmbientLogger, IAmbientStructuredLogger
 31{
 232    private readonly List<IAmbientLogger> _ambientLoggers = new();
 233    private readonly List<IAmbientStructuredLogger> _ambientStructuredLoggers = new();
 34
 35    /// <summary>
 36    /// Constructs a default log splitter.
 37    /// </summary>
 238    public AmbientLogSplitter()
 39    {
 240    }
 41    /// <summary>
 42    /// Adds the specified logger to the ambient loggers.
 43    /// This function is *not* thread-safe, so it should only be called during application initialization.
 44    /// </summary>
 45    /// <param name="logger">The <see cref="IAmbientLogger"/> to start logging to.</param>
 46    public void AddSimpleLogger(IAmbientLogger logger)
 47    {
 248        _ambientLoggers.Add(logger);
 249        if (logger is IAmbientStructuredLogger structuredLogger)
 50        {
 251            _ambientStructuredLoggers.Add(structuredLogger);
 52        }
 253    }
 54    /// <summary>
 55    /// Removes the specified logger from the ambient loggers.
 56    /// This function is *not* thread-safe, so it should only be called during application initialization.
 57    /// </summary>
 58    /// <param name="logger">The <see cref="IAmbientLogger"/> to stop logging to.</param>
 59    public void RemoveSimpleLogger(IAmbientLogger logger)
 60    {
 261        _ambientLoggers.Remove(logger);
 262        if (logger is IAmbientStructuredLogger structuredLogger)
 63        {
 264            _ambientStructuredLoggers.Remove(structuredLogger);
 65        }
 266    }
 67    /// <summary>
 68    /// Adds the specified structured logger to the ambient structured loggers.
 69    /// This function is *not* thread-safe, so it should only be called during application initialization.
 70    /// </summary>
 71    /// <param name="structuredLogger">The <see cref="IAmbientStructuredLogger"/> to start logging to.</param>
 72    public void AddLogger(IAmbientStructuredLogger structuredLogger)
 73    {
 274        _ambientStructuredLoggers.Add(structuredLogger);
 275        if (structuredLogger is IAmbientLogger logger)
 76        {
 277            _ambientLoggers.Add(logger);
 78        }
 279    }
 80    /// <summary>
 81    /// Removes the specified structured logger from the ambient structured loggers.
 82    /// This function is *not* thread-safe, so it should only be called during application initialization.
 83    /// </summary>
 84    /// <param name="structuredLogger">The <see cref="IAmbientStructuredLogger"/> to stop logging to.</param>
 85    public void RemoveLogger(IAmbientStructuredLogger structuredLogger)
 86    {
 287        _ambientStructuredLoggers.Remove(structuredLogger);
 288        if (structuredLogger is IAmbientLogger logger)
 89        {
 290            _ambientLoggers.Remove(logger);
 91        }
 292    }
 93    /// <summary>
 94    /// Buffers the specified structured data to be asynchronously logged.
 95    /// </summary>
 96    /// <param name="structuredData">The structured data object.</param>
 97    public void Log(object structuredData)
 98    {
 99#if NET5_0_OR_GREATER
 2100        ArgumentNullException.ThrowIfNull(structuredData);
 101#else
 102    if (structuredData is null) throw new ArgumentNullException(nameof(structuredData));
 103#endif
 2104        foreach (IAmbientStructuredLogger structuredLogger in _ambientStructuredLoggers)
 105        {
 2106            structuredLogger.Log(structuredData);
 107        }
 2108    }
 109    /// <summary>
 110    /// Buffers the specified message to be asynchronously logged.
 111    /// </summary>
 112    /// <param name="message">The message to log.</param>
 113    public void Log(string message)
 114    {
 2115        foreach (IAmbientLogger logger in _ambientLoggers)
 116        {
 2117            logger.Log(message);
 118        }
 2119    }
 120    /// <summary>
 121    /// Flushes everything that has been previously logged to the appropriate file on disk.
 122    /// </summary>
 123    /// <param name="cancel">A <see cref="CancellationToken"/> to cancel the operation before it finishes.</param>
 124    public async ValueTask Flush(CancellationToken cancel = default)
 125    {
 2126        foreach (IAmbientLogger logger in _ambientLoggers)
 127        {
 2128            await logger.Flush(cancel);
 129        }
 2130        foreach (IAmbientStructuredLogger structuredLogger in _ambientStructuredLoggers)
 131        {
 2132            await structuredLogger.Flush(cancel);
 133        }
 2134    }
 135    /// <summary>
 136    /// Gets the string representation of the ambient log splitter.
 137    /// </summary>
 138    /// <returns>The string representation of the ambient log splitter.</returns>
 139    public override string ToString()
 140    {
 2141        return string.Join(",", _ambientLoggers.Select(l => l.ToString())) + "/" + string.Join(",", _ambientStructuredLo
 142    }
 143}