| | | 1 | | using System; |
| | | 2 | | using System.Collections.Generic; |
| | | 3 | | using System.Linq; |
| | | 4 | | using System.Threading; |
| | | 5 | | using System.Threading.Tasks; |
| | | 6 | | |
| | | 7 | | namespace 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> |
| | | 30 | | public class AmbientLogSplitter : IAmbientLogger, IAmbientStructuredLogger |
| | | 31 | | { |
| | 2 | 32 | | private readonly List<IAmbientLogger> _ambientLoggers = new(); |
| | 2 | 33 | | private readonly List<IAmbientStructuredLogger> _ambientStructuredLoggers = new(); |
| | | 34 | | |
| | | 35 | | /// <summary> |
| | | 36 | | /// Constructs a default log splitter. |
| | | 37 | | /// </summary> |
| | 2 | 38 | | public AmbientLogSplitter() |
| | | 39 | | { |
| | 2 | 40 | | } |
| | | 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 | | { |
| | 2 | 48 | | _ambientLoggers.Add(logger); |
| | 2 | 49 | | if (logger is IAmbientStructuredLogger structuredLogger) |
| | | 50 | | { |
| | 2 | 51 | | _ambientStructuredLoggers.Add(structuredLogger); |
| | | 52 | | } |
| | 2 | 53 | | } |
| | | 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 | | { |
| | 2 | 61 | | _ambientLoggers.Remove(logger); |
| | 2 | 62 | | if (logger is IAmbientStructuredLogger structuredLogger) |
| | | 63 | | { |
| | 2 | 64 | | _ambientStructuredLoggers.Remove(structuredLogger); |
| | | 65 | | } |
| | 2 | 66 | | } |
| | | 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 | | { |
| | 2 | 74 | | _ambientStructuredLoggers.Add(structuredLogger); |
| | 2 | 75 | | if (structuredLogger is IAmbientLogger logger) |
| | | 76 | | { |
| | 2 | 77 | | _ambientLoggers.Add(logger); |
| | | 78 | | } |
| | 2 | 79 | | } |
| | | 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 | | { |
| | 2 | 87 | | _ambientStructuredLoggers.Remove(structuredLogger); |
| | 2 | 88 | | if (structuredLogger is IAmbientLogger logger) |
| | | 89 | | { |
| | 2 | 90 | | _ambientLoggers.Remove(logger); |
| | | 91 | | } |
| | 2 | 92 | | } |
| | | 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 |
| | 2 | 100 | | ArgumentNullException.ThrowIfNull(structuredData); |
| | | 101 | | #else |
| | | 102 | | if (structuredData is null) throw new ArgumentNullException(nameof(structuredData)); |
| | | 103 | | #endif |
| | 2 | 104 | | foreach (IAmbientStructuredLogger structuredLogger in _ambientStructuredLoggers) |
| | | 105 | | { |
| | 2 | 106 | | structuredLogger.Log(structuredData); |
| | | 107 | | } |
| | 2 | 108 | | } |
| | | 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 | | { |
| | 2 | 115 | | foreach (IAmbientLogger logger in _ambientLoggers) |
| | | 116 | | { |
| | 2 | 117 | | logger.Log(message); |
| | | 118 | | } |
| | 2 | 119 | | } |
| | | 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 | | { |
| | 2 | 126 | | foreach (IAmbientLogger logger in _ambientLoggers) |
| | | 127 | | { |
| | 2 | 128 | | await logger.Flush(cancel); |
| | | 129 | | } |
| | 2 | 130 | | foreach (IAmbientStructuredLogger structuredLogger in _ambientStructuredLoggers) |
| | | 131 | | { |
| | 2 | 132 | | await structuredLogger.Flush(cancel); |
| | | 133 | | } |
| | 2 | 134 | | } |
| | | 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 | | { |
| | 2 | 141 | | return string.Join(",", _ambientLoggers.Select(l => l.ToString())) + "/" + string.Join(",", _ambientStructuredLo |
| | | 142 | | } |
| | | 143 | | } |