using Microsoft.Extensions.Logging; namespace VFXReviewWorker; /// /// Minimal rolling file logger (ยง7.9): one file per day under /// %ProgramData%\VFXReviewWorker\logs, files older than 14 days pruned on /// startup and at midnight rollover. No external logging dependencies. /// public sealed class FileLoggerProvider : ILoggerProvider { private readonly object _lock = new(); private StreamWriter? _writer; private DateOnly _currentDay; public FileLoggerProvider() { Directory.CreateDirectory(WorkerOptions.LogsDir); Prune(); } public ILogger CreateLogger(string categoryName) => new FileLogger(this, categoryName); internal void Write(string category, LogLevel level, string message, Exception? ex) { lock (_lock) { var today = DateOnly.FromDateTime(DateTime.Now); if (_writer is null || today != _currentDay) { _writer?.Dispose(); _currentDay = today; _writer = new StreamWriter( Path.Combine(WorkerOptions.LogsDir, $"worker_{today:yyyyMMdd}.log"), append: true) { AutoFlush = true }; Prune(); } _writer.WriteLine($"{DateTime.Now:HH:mm:ss.fff} [{level,-5}] {Shorten(category)}: {message}{(ex is null ? "" : $"\n{ex}")}"); } } private static string Shorten(string category) => category[(category.LastIndexOf('.') + 1)..]; private static void Prune() { try { var cutoff = DateTime.Now.AddDays(-14); foreach (var f in Directory.EnumerateFiles(WorkerOptions.LogsDir, "*.log")) { if (File.GetLastWriteTime(f) < cutoff) File.Delete(f); } } catch { /* best effort */ } } public void Dispose() { lock (_lock) { _writer?.Dispose(); _writer = null; } } private sealed class FileLogger : ILogger { private readonly FileLoggerProvider _provider; private readonly string _category; public FileLogger(FileLoggerProvider provider, string category) { _provider = provider; _category = category; } public IDisposable? BeginScope(TState state) where TState : notnull => null; public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel.Information; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { if (!IsEnabled(logLevel)) return; _provider.Write(_category, logLevel, formatter(state, exception), exception); } } }