using System.IO; namespace ytLive.Helpers; /// /// Size-capped log rotation. /// When a log file grows past a cap, it is rolled to <name>.1.log and a /// fresh one is started, keeping a bounded number of rolled files. The pattern is the /// one every sized logger uses — Serilog's file sink pairs fileSizeLimitBytes /// (the cap) with retainedFileCountLimit (how many rolled files to keep): /// https://github.com/serilog/serilog-sinks-file /// Why it lives here and not in the logger: 's call surface /// (156 call sites) must not change, and the rotation rule is worth testing on its own — /// so the policy is a pure function over a path, and just calls it. /// public static class LogRotation { /// /// Rolls if it has reached , /// keeping rolled copies /// (name.1.log, name.2.log, …). Does nothing when the file is missing /// or still under the cap. /// Never throws: a failed rotation must not take the app down — the log just /// keeps growing, which is the old behaviour. /// public static void RollIfNeeded(string logPath, long maxBytes, int retainedFiles) { try { var info = new FileInfo(logPath); if (!info.Exists || info.Length < maxBytes) { return; } // Oldest first: name.1 → name.2 → … → name.N, then the live file → name.1. for (var i = retainedFiles; i >= 1; i--) { var source = i == 1 ? logPath : RolledPath(logPath, i - 1); var target = RolledPath(logPath, i); if (!File.Exists(source)) { continue; } File.Move(source, target, overwrite: true); } } catch { // Never let logging itself crash the app. } } /// The rolled path for a given index: startup.log → startup.1.log. public static string RolledPath(string logPath, int index) { var directory = Path.GetDirectoryName(logPath) ?? string.Empty; var name = Path.GetFileNameWithoutExtension(logPath); var extension = Path.GetExtension(logPath); return Path.Combine(directory, $"{name}.{index}{extension}"); } }