namespace Logger; /// /// Logger implementation that writes to file with rotation support /// public class FileLogger : ILogger { private readonly FileLoggerOptions _options; private readonly object _lockObject = new(); public FileLogger(FileLoggerOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); EnsureLogDirectory(); } public void Trace(string message) => Log(LogLevel.Trace, message); public void Debug(string message) => Log(LogLevel.Debug, message); public void Info(string message) => Log(LogLevel.Info, message); public void Warning(string message) => Log(LogLevel.Warning, message); public void Error(string message) => Log(LogLevel.Error, message); public void Fatal(string message) => Log(LogLevel.Fatal, message); public void Log(LogLevel level, string message) { if (level < _options.MinimumLevel) return; var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); var formattedMessage = $"[{timestamp}] [{level}] {message}"; lock (_lockObject) { try { RotateFileIfNeeded(); var logPath = GetLogFilePath(); File.AppendAllText(logPath, formattedMessage + Environment.NewLine); } catch (Exception ex) { Console.Error.WriteLine($"Error writing to log file: {ex.Message}"); } } } private void EnsureLogDirectory() { try { if (!Directory.Exists(_options.LogDirectory)) { Directory.CreateDirectory(_options.LogDirectory); } } catch (Exception ex) { Console.Error.WriteLine($"Error creating log directory: {ex.Message}"); } } private void RotateFileIfNeeded() { var logPath = GetLogFilePath(); if (!File.Exists(logPath)) return; var fileInfo = new FileInfo(logPath); if (fileInfo.Length >= _options.MaxFileSizeBytes) { ArchiveCurrentLog(logPath); } } private void ArchiveCurrentLog(string currentLogPath) { try { var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(_options.FileName); var fileExtension = Path.GetExtension(_options.FileName); var timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); var archivePath = Path.Combine(_options.LogDirectory, $"{fileNameWithoutExtension}_{timestamp}{fileExtension}"); File.Move(currentLogPath, archivePath, overwrite: true); CleanupOldBackups(); } catch (Exception ex) { Console.Error.WriteLine($"Error archiving log file: {ex.Message}"); } } private void CleanupOldBackups() { try { var directory = new DirectoryInfo(_options.LogDirectory); var pattern = $"{Path.GetFileNameWithoutExtension(_options.FileName)}_*"; var backupFiles = directory.GetFiles(pattern) .OrderByDescending(f => f.CreationTime) .Skip(_options.MaxBackupFiles) .ToList(); foreach (var file in backupFiles) { file.Delete(); } } catch (Exception ex) { Console.Error.WriteLine($"Error cleaning up old backup files: {ex.Message}"); } } private string GetLogFilePath() { return Path.Combine(_options.LogDirectory, _options.FileName); } }