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}");
}
}