using System.Collections.Concurrent; namespace LumaTunnel.Server; internal sealed class RollingFileLoggerProvider(string directory) : ILoggerProvider { private readonly ConcurrentDictionary _loggers = new(StringComparer.Ordinal); private readonly SharedWriter _writer = new(directory); public ILogger CreateLogger(string categoryName) => _loggers.GetOrAdd(categoryName, category => new RollingFileLogger(category, _writer)); public void Dispose() { _writer.Dispose(); _loggers.Clear(); GC.SuppressFinalize(this); } private sealed class RollingFileLogger(string category, SharedWriter writer) : ILogger { 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; var message = formatter(state, exception).Replace("\r", " ", StringComparison.Ordinal).Replace("\n", " ", StringComparison.Ordinal); writer.Write($"{DateTimeOffset.Now:O} [{logLevel}] {category}: {message}{(exception is null ? string.Empty : " | " + exception.GetType().Name)}"); } } private sealed class SharedWriter : IDisposable { private readonly object _gate = new(); private readonly string _directory; private DateOnly _date; private StreamWriter? _writer; private int _segment; public SharedWriter(string directory) { _directory = directory; Directory.CreateDirectory(directory); } public void Write(string line) { lock (_gate) { EnsureWriter(); _writer!.WriteLine(line); _writer.Flush(); } } public void Dispose() { lock (_gate) { _writer?.Dispose(); _writer = null; } } private void EnsureWriter() { var today = DateOnly.FromDateTime(DateTime.Today); if (_writer is null || today != _date || _writer.BaseStream.Length >= 10 * 1024 * 1024) { _writer?.Dispose(); if (today != _date) _segment = 0; else _segment++; _date = today; var suffix = _segment == 0 ? string.Empty : $"-{_segment}"; var path = Path.Combine(_directory, $"lumatunnel-{today:yyyyMMdd}{suffix}.log"); _writer = new StreamWriter(new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read), System.Text.Encoding.UTF8); foreach (var old in Directory.GetFiles(_directory, "lumatunnel-*.log").OrderByDescending(static item => item, StringComparer.OrdinalIgnoreCase).Skip(10)) File.Delete(old); } } } }