using System.Globalization; namespace Agent.App.Logging; public sealed class RollingFileLog : IDisposable { private readonly object _sync = new(); private readonly string _path; private readonly long _maxBytes; private readonly int _retainedFiles; private StreamWriter _writer; public RollingFileLog(string directory, long maxBytes, int retainedFiles) { Directory.CreateDirectory(directory); _path = Path.Combine(directory, "application.log"); _maxBytes = maxBytes; _retainedFiles = retainedFiles; _writer = Open(); } public void Information(string message) => Write("INF", message); public void Error(string message) => Write("ERR", message); private void Write(string level, string message) { lock (_sync) { if (_writer.BaseStream.Length >= _maxBytes) Rotate(); string line = string.Create(CultureInfo.InvariantCulture, $"{DateTimeOffset.UtcNow:O} [{level}] {message}"); Console.Error.WriteLine(line); _writer.WriteLine(line); _writer.Flush(); } } private StreamWriter Open() => new(new FileStream(_path, FileMode.Append, FileAccess.Write, FileShare.Read), new System.Text.UTF8Encoding(false)); private void Rotate() { _writer.Dispose(); for (int index = _retainedFiles - 1; index >= 1; index--) { string source = $"{_path}.{index}"; if (File.Exists(source)) File.Move(source, $"{_path}.{index + 1}", true); } if (File.Exists(_path)) File.Move(_path, $"{_path}.1", true); _writer = Open(); } public void Dispose() { lock (_sync) _writer.Dispose(); } }