// Copyright Epic Games, Inc. All Rights Reserved. using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Threading.Tasks; using System.Threading; using System; namespace EpicGames.UBA { /// /// Threaded logging for use by UBAExecutor /// public class ThreadedLogger : Microsoft.Extensions.Logging.ILogger, IDisposable { interface ILogEntry { void Log(); } readonly Microsoft.Extensions.Logging.ILogger _internalLogger; readonly Task _logTask; readonly object _logQueueLock; readonly CancellationToken _token; readonly EventWaitHandle _processLogQueue; List _logQueue; long _running; /// /// Constructor /// /// The logger public ThreadedLogger(Microsoft.Extensions.Logging.ILogger logger) { _internalLogger = logger; _running = 1; _logQueueLock = new object(); _processLogQueue = new EventWaitHandle(false, EventResetMode.AutoReset); _logQueue = []; _token = new CancellationToken(); _logTask = Task.Factory.StartNew(() => { List logQueue2 = []; while (Interlocked.Read(ref _running) == 1) { _processLogQueue.WaitOne(); lock (_logQueueLock) { (logQueue2, _logQueue) = (_logQueue, logQueue2); } foreach (Action entry in logQueue2) { entry(); } logQueue2.Clear(); } }, _token, TaskCreationOptions.LongRunning, TaskScheduler.Default); } #region IDisposable /// /// Destructor /// ~ThreadedLogger() => Dispose(false); /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Protected dispose /// protected virtual void Dispose(bool disposing) { if (disposing) { _processLogQueue.Dispose(); } } #endregion #region Microsoft.Extensions.Logging.ILogger /// public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { if (logLevel < LogLevel.Error) { lock (_logQueueLock) { if (_running == 0) { _internalLogger.Log(logLevel, eventId, state, exception, formatter); return; } _logQueue.Add(() => _internalLogger.Log(logLevel, eventId, state, exception, formatter)); } _processLogQueue.Set(); } else { using EventWaitHandle waitForLog = new(false, EventResetMode.ManualReset); lock (_logQueueLock) { if (_running == 0) { _internalLogger.Log(logLevel, eventId, state, exception, formatter); return; } _logQueue.Add(() => { _internalLogger.Log(logLevel, eventId, state, exception, formatter); waitForLog.Set(); }); } _processLogQueue.Set(); waitForLog.WaitOne(); } } /// public bool IsEnabled(LogLevel logLevel) { return _internalLogger.IsEnabled(logLevel); } /// public IDisposable? BeginScope(TState state) where TState : notnull { return _internalLogger.BeginScope(state); } #endregion /// /// Finish logging async /// public async Task FinishAsync() { lock (_logQueueLock) { Interlocked.Exchange(ref _running, 0); _processLogQueue.Set(); } await _logTask.WaitAsync(_token); } } }