38public static partial class EngineLogger
40 private static ILoggerFactory? _loggerFactory;
41 private static bool _usingExternalLoggerFactory;
42 private static readonly
object _factoryLock =
new();
43 private static readonly Dictionary<string, Func<ILoggerProvider>> _persistentProviderFactories =
44 new(StringComparer.Ordinal);
47 private static readonly ConcurrentDictionary<Type, ILogger> _loggerCache =
new();
56 public static event EventHandler<LoggingErrorEventArgs>? LoggingError;
77 EnsureAsyncStarted(forceRestart:
false);
82 private static readonly
object _asyncGate =
new();
83 private static Channel<LogEvent>? _channel;
84 private static Task? _worker;
85 private static CancellationTokenSource? _cts;
88 private const int DefaultCapacity = 8192;
89 private static int _capacity = DefaultCapacity;
97 private static ILoggerFactory GetOrCreateLoggerFactory()
99 if (_loggerFactory !=
null)
100 return _loggerFactory;
104 if (_loggerFactory !=
null)
105 return _loggerFactory;
108 bool isBrowser = OperatingSystem.IsBrowser();
110 _loggerFactory = LoggerFactory.Create(builder =>
117 builder.AddConsole();
119 AddPersistentProviders(builder);
122 return _loggerFactory;
130 public static void StartAsyncLogging(
int capacity = DefaultCapacity)
133 throw new ArgumentOutOfRangeException(nameof(capacity),
"Capacity must be > 0.");
137 _capacity = capacity;
140 EnsureAsyncStarted(forceRestart:
false);
148 public static void StopAsyncLogging(
bool flush =
true, TimeSpan? flushTimeout =
null)
150 Channel<LogEvent>? ch;
152 CancellationTokenSource? cts;
160 if (ch ==
null || worker ==
null)
164 try { ch.Writer.TryComplete(); }
catch { }
172 try { cts?.Cancel(); }
catch { }
176 var timeout = flushTimeout ?? TimeSpan.FromSeconds(2);
177 try { worker!.Wait(timeout); }
catch { }
180 try { cts?.Dispose(); }
catch { }
187 public static void SwitchToSyncAndFlush(TimeSpan? flushTimeout =
null)
190 StopAsyncLogging(flush:
true, flushTimeout: flushTimeout);
199 public static void SwitchToAsync(
int? capacity =
null)
203 if (capacity is not
null)
205 if (capacity.Value <= 0)
206 throw new ArgumentOutOfRangeException(nameof(capacity),
"Capacity must be > 0.");
207 _capacity = capacity.Value;
213 EnsureAsyncStarted(forceRestart:
false);
216 internal static void Initialize(ILoggerFactory loggerFactory)
220 _loggerFactory = loggerFactory ??
throw new ArgumentNullException(nameof(loggerFactory));
222 foreach (Func<ILoggerProvider> providerFactory
in _persistentProviderFactories.Values)
223 _loggerFactory.AddProvider(providerFactory());
225 _usingExternalLoggerFactory =
true;
226 _loggerCache.Clear();
236 internal static void RegisterPersistentProvider(
238 Func<ILoggerProvider> providerFactory)
240 ArgumentException.ThrowIfNullOrWhiteSpace(key);
241 ArgumentNullException.ThrowIfNull(providerFactory);
245 if (_persistentProviderFactories.ContainsKey(key))
248 _persistentProviderFactories.Add(key, providerFactory);
249 _loggerFactory?.AddProvider(providerFactory());
261 public static ILoggerFactory EngineLoggerFactory => GetOrCreateLoggerFactory();
276 public static ILogger<T> GetLogger<T>() =>
277 (ILogger<T>)_loggerCache.GetOrAdd(typeof(T),
static _ =>
new ModeLogger<T>());
291 public static void SetLogLevel(LogLevel level)
295 if (_usingExternalLoggerFactory)
299 bool isBrowser = OperatingSystem.IsBrowser();
301 _loggerFactory = LoggerFactory.Create(builder =>
304 .SetMinimumLevel(level);
308 builder.AddConsole();
310 AddPersistentProviders(builder);
313 _loggerCache.Clear();
317 private static void AddPersistentProviders(ILoggingBuilder builder)
319 foreach (Func<ILoggerProvider> providerFactory
in _persistentProviderFactories.Values)
320 builder.AddProvider(providerFactory());
323 private static void EnsureAsyncStarted(
bool forceRestart)
327 EnsureAsyncStarted_NoLock(forceRestart);
332 private static void EnsureAsyncStarted_NoLock(
bool forceRestart)
337 if (!forceRestart && _worker !=
null)
340 if (forceRestart && _channel !=
null)
343 try { _channel.Writer.TryComplete(); }
catch { }
346 int capacityLocal = _capacity;
348 var channel = Channel.CreateBounded<LogEvent>(
new BoundedChannelOptions(capacityLocal)
351 SingleWriter =
false,
352 FullMode = BoundedChannelFullMode.DropWrite
358 _cts =
new CancellationTokenSource();
361 var reader = channel.Reader;
362 var token = _cts.Token;
363 _worker = Task.Run(() => WorkerLoop(reader, token));
366 private static bool TryEnqueue(in LogEvent ev)
373 EnsureAsyncStarted_NoLock(forceRestart:
false);
380 return ch.Writer.TryWrite(ev);
384 private static async Task WorkerLoop(ChannelReader<LogEvent> reader, CancellationToken ct)
388 while (await reader.WaitToReadAsync(ct).ConfigureAwait(
false))
390 while (reader.TryRead(out var ev))
395 var logger = GetOrCreateLoggerFactory().CreateLogger(ev.CategoryName);
396 logger.Log(ev.LogLevel, ev.EventId, ev.State, ev.Exception, ev.Formatter);
401 Debug.WriteLine($
"[EngineLogger] Logging failed: {ex.GetType().Name}: {ex.Message}");
416 catch (System.OperationCanceledException) when (ct.IsCancellationRequested)
430public sealed class LoggingErrorEventArgs : EventArgs
449 Exception = exception ??
throw new ArgumentNullException(nameof(exception));
450 CategoryName = categoryName ??
throw new ArgumentNullException(nameof(categoryName));