using System; using System.IO; using System.Text; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Logging.Formatters; namespace GFramework.Core.Logging.Appenders; /// /// 滚动文件日志输出器,支持按大小自动轮转日志文件 /// public sealed class RollingFileAppender : ILogAppender, IDisposable { private readonly string _baseFilePath; private readonly ILogFilter? _filter; private readonly ILogFormatter _formatter; private readonly object _lock = new(); private readonly int _maxFileCount; private readonly long _maxFileSize; private long _currentSize; private bool _disposed; private StreamWriter? _writer; /// /// 创建滚动文件日志输出器 /// /// 基础文件路径(例如: logs/app.log) /// 单个文件最大大小(字节),默认 10MB /// 保留的文件数量,默认 5 /// 日志格式化器 /// 日志过滤器(可选) public RollingFileAppender( string baseFilePath, long maxFileSize = 10 * 1024 * 1024, int maxFileCount = 5, ILogFormatter? formatter = null, ILogFilter? filter = null) { if (string.IsNullOrWhiteSpace(baseFilePath)) throw new ArgumentException("Base file path cannot be null or whitespace.", nameof(baseFilePath)); if (maxFileSize <= 0) throw new ArgumentException("Max file size must be greater than 0.", nameof(maxFileSize)); if (maxFileCount <= 0) throw new ArgumentException("Max file count must be greater than 0.", nameof(maxFileCount)); _baseFilePath = baseFilePath; _maxFileSize = maxFileSize; _maxFileCount = maxFileCount; _formatter = formatter ?? new DefaultLogFormatter(); _filter = filter; EnsureDirectoryExists(); InitializeWriter(); } /// /// 释放资源 /// public void Dispose() { if (_disposed) return; lock (_lock) { _writer?.Flush(); _writer?.Dispose(); _writer = null; _disposed = true; } } /// /// 追加日志条目到文件 /// /// 日志条目 public void Append(LogEntry entry) { if (_disposed) throw new ObjectDisposedException(nameof(RollingFileAppender)); if (_filter != null && !_filter.ShouldLog(entry)) return; var message = _formatter.Format(entry); var messageBytes = Encoding.UTF8.GetByteCount(message) + global::System.Environment.NewLine.Length; lock (_lock) { // 检查是否需要轮转 if (_currentSize + messageBytes > _maxFileSize) { RollFiles(); } _writer?.WriteLine(message); _currentSize += messageBytes; } } /// /// 刷新文件缓冲区 /// public void Flush() { lock (_lock) { _writer?.Flush(); } } /// /// 轮转日志文件 /// private void RollFiles() { // 关闭当前文件 _writer?.Flush(); _writer?.Dispose(); _writer = null; // 删除最旧的文件(如果存在) var oldestFile = GetRolledFileName(_maxFileCount - 1); if (File.Exists(oldestFile)) { try { File.Delete(oldestFile); } catch { // 忽略删除错误 } } // 重命名现有文件: app.log -> app.1.log -> app.2.log -> ... for (int i = _maxFileCount - 2; i >= 0; i--) { var sourceFile = i == 0 ? _baseFilePath : GetRolledFileName(i); var targetFile = GetRolledFileName(i + 1); if (File.Exists(sourceFile)) { try { if (File.Exists(targetFile)) { File.Delete(targetFile); } File.Move(sourceFile, targetFile); } catch { // 忽略移动错误 } } } // 重新初始化写入器 InitializeWriter(); } /// /// 获取轮转后的文件名 /// /// 文件索引 /// 轮转后的文件路径 private string GetRolledFileName(int index) { var directory = Path.GetDirectoryName(_baseFilePath); var fileNameWithoutExt = Path.GetFileNameWithoutExtension(_baseFilePath); var extension = Path.GetExtension(_baseFilePath); var rolledFileName = $"{fileNameWithoutExt}.{index}{extension}"; return string.IsNullOrEmpty(directory) ? rolledFileName : Path.Combine(directory, rolledFileName); } /// /// 确保目录存在 /// private void EnsureDirectoryExists() { var directory = Path.GetDirectoryName(_baseFilePath); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } } /// /// 初始化写入器 /// private void InitializeWriter() { _writer = new StreamWriter(_baseFilePath, append: true, Encoding.UTF8) { AutoFlush = true }; // 获取当前文件大小 _currentSize = File.Exists(_baseFilePath) ? new FileInfo(_baseFilePath).Length : 0; } }