diff --git a/src/changelog/3.5.1/334-rollingfileappender-backup-index.xml b/src/changelog/3.5.1/334-rollingfileappender-backup-index.xml new file mode 100644 index 00000000..76148837 --- /dev/null +++ b/src/changelog/3.5.1/334-rollingfileappender-backup-index.xml @@ -0,0 +1,10 @@ + + + + Count only the current date's backups when `RollingFileAppender` + starts. A date pattern without separators (`.yyyyMMdd`) no longer counts as a backup number, and + backups of earlier dates no longer raise the count (reported by @Snotface, fixed by @FreeAndNil) + diff --git a/src/log4net.Tests/Appender/RollingFileAppenderTest.cs b/src/log4net.Tests/Appender/RollingFileAppenderTest.cs index f67c8818..8ef01257 100644 --- a/src/log4net.Tests/Appender/RollingFileAppenderTest.cs +++ b/src/log4net.Tests/Appender/RollingFileAppenderTest.cs @@ -1664,6 +1664,163 @@ public void TestInitializeRollBackups4() VerifyInitializeRollBackups(8, maxRollBackups); } + /// + /// Tests that a date roll file using a date pattern without "-" (yyyyMMdd) + /// is not mistaken for a backup index, whatever the backup limit + /// + [TestCase(5, 1)] + [TestCase(5, -1)] + [TestCase(-1, 1)] + [TestCase(-1, -1)] + public void TestInitializeRollBackupsIgnoresDateWithoutDashes(int maxSizeRollBackups, + int countDirection) + { + const string baseFile = "LogFile.log"; + List files = ["LogFile.log", "LogFile.log.20261003", "LogFile.log.1", "LogFile.log.2"]; + + RollingFileAppender rfa = new() + { + RollingStyle = RollingFileAppender.RollingMode.Composite, + DatePattern = ".yyyyMMdd", + CountDirection = countDirection, + MaxSizeRollBackups = maxSizeRollBackups, + CurrentSizeRollBackups = 0 + }; + rfa.InitializeRollBackups(baseFile, files); + + Assert.That(rfa.CurrentSizeRollBackups, Is.EqualTo(2)); + } + + /// + /// Tests that counting up continues from the highest index, even when + /// the oldest backups were deleted and the indexes passed the backup limit + /// + [Test] + public void TestInitializeRollBackupsCountsUpPastLimit() + { + const string baseFile = "LogFile.log"; + List files = ["LogFile.log", "LogFile.log.5", "LogFile.log.6", "LogFile.log.7"]; + + RollingFileAppender rfa = new() + { + RollingStyle = RollingFileAppender.RollingMode.Composite, + CountDirection = 1, + MaxSizeRollBackups = 3, + CurrentSizeRollBackups = 0 + }; + rfa.InitializeRollBackups(baseFile, files); + + Assert.That(rfa.CurrentSizeRollBackups, Is.EqualTo(7)); + } + + /// + /// Tests that backups already rolled to an earlier date are not counted + /// as backups of the current date + /// + [Test] + public void TestInitializeRollBackupsIgnoresOtherDatePeriods() + { + const string baseFile = "LogFile.log"; + List files = + [ + "LogFile.log", + "LogFile.log.20261003", + "LogFile.log.20261003.1", + "LogFile.log.20261003.2", + "LogFile.log.20261003.3", + "LogFile.log.1", + ]; + + RollingFileAppender rfa = new() + { + RollingStyle = RollingFileAppender.RollingMode.Composite, + DatePattern = ".yyyyMMdd", + MaxSizeRollBackups = -1, + CurrentSizeRollBackups = 0 + }; + rfa.InitializeRollBackups(baseFile, files); + + Assert.That(rfa.CurrentSizeRollBackups, Is.EqualTo(1)); + } + + /// + /// Tests that backups of the current date are found when the date pattern + /// supplies the extension and the extension is preserved + /// + [Test] + public void TestInitializeRollBackupsDatePatternWithExtension() + { + const string baseFile = "log4net"; + List files = + [ + "log4net.2026-10-04.log", + "log4net.2026-10-04.1.log", + "log4net.2026-10-04.2.log", + "log4net.2026-10-03.log", + "log4net.2026-10-03.5.log", + ]; + + RollingFileAppender rfa = new() + { + RollingStyle = RollingFileAppender.RollingMode.Composite, + DatePattern = ".yyyy-MM-dd'.log'", + DateTimeStrategy = new Integration.MockDateTime(new DateTime(2026, 10, 4, 9, 0, 0)), + StaticLogFileName = false, + PreserveLogFileNameExtension = true, + MaxSizeRollBackups = -1, + CurrentSizeRollBackups = 0 + }; + rfa.InitializeRollBackups(baseFile, files); + + Assert.That(rfa.CurrentSizeRollBackups, Is.EqualTo(2)); + } + + /// + /// Tests that the current date's file of a non-static name is not mistaken for a backup index + /// when the date pattern has no separators + /// + [TestCase("log.txt", ".yyyyMMdd", false, "log.txt.20261004", "log.txt.20261004.1")] + [TestCase("log", ".yyyyMMdd'.log'", true, "log.20261004.log", "log.20261004.1.log")] + public void TestInitializeRollBackupsNotStaticDateWithoutDashes(string baseFile, + string datePattern, bool preserveExtension, string datedFile, string backupFile) + { + RollingFileAppender rfa = new() + { + RollingStyle = RollingFileAppender.RollingMode.Composite, + DatePattern = datePattern, + DateTimeStrategy = new Integration.MockDateTime(new DateTime(2026, 10, 4, 9, 0, 0)), + StaticLogFileName = false, + PreserveLogFileNameExtension = preserveExtension, + MaxSizeRollBackups = -1, + CurrentSizeRollBackups = 0 + }; + rfa.InitializeRollBackups(baseFile, [datedFile, backupFile]); + + Assert.That(rfa.CurrentSizeRollBackups, Is.EqualTo(1)); + } + + /// + /// Tests that with a short date pattern the suffixes of a static file name count as backup + /// indexes, as a date like .10 cannot be told apart from backup 10 + /// + [Test] + public void TestInitializeRollBackupsShortDatePatternCountsIndexes() + { + const string baseFile = "log.txt"; + List files = ["log.txt", .. Enumerable.Range(1, 10).Select(i => $"log.txt.{i}")]; + + RollingFileAppender rfa = new() + { + RollingStyle = RollingFileAppender.RollingMode.Date, + DatePattern = ".dd", + MaxSizeRollBackups = -1, + CurrentSizeRollBackups = 0 + }; + rfa.InitializeRollBackups(baseFile, files); + + Assert.That(rfa.CurrentSizeRollBackups, Is.EqualTo(10)); + } + /// /// Ensures that no problems result from creating and then closing the appender /// when it has not also been initialized with ActivateOptions(). diff --git a/src/log4net/Appender/RollingFileAppender.cs b/src/log4net/Appender/RollingFileAppender.cs index d6ad43f6..0cbdd144 100644 --- a/src/log4net/Appender/RollingFileAppender.cs +++ b/src/log4net/Appender/RollingFileAppender.cs @@ -879,29 +879,29 @@ private void InitializeFromOneFile(string baseFile, string curFileName) if (_rollDate && !StaticLogFileName) { string date = DateTimeStrategy.Now.ToString(DatePattern, DateTimeFormatInfo.InvariantInfo).ToLowerInvariant(); - string prefix = (PreserveLogFileNameExtension - ? Path.GetFileNameWithoutExtension(baseFile) + date - : baseFile + date).ToLowerInvariant(); - string suffix = PreserveLogFileNameExtension - ? Path.GetExtension(baseFile).ToLowerInvariant() - : ""; + // the date pattern may supply the extension, so take it from the dated name + string dated = Path.GetFileName(CombinePath(baseFile, date)); + string suffix = PreserveLogFileNameExtension ? Path.GetExtension(dated) : ""; + string prefix = dated.Substring(0, dated.Length - suffix.Length); string curFileNameWithoutDir = Path.GetFileName(curFileName); if (!curFileNameWithoutDir.StartsWith(prefix) || !curFileNameWithoutDir.EndsWith(suffix)) { LogLog.Debug(_declaringType, $"Ignoring file [{curFileName}] because it is from a different date period"); return; } + + // the dated file itself has no index, and a date like .yyyyMMdd would parse as one + if (curFileNameWithoutDir.Length <= dated.Length) + { + return; + } } try { // Bump the counter up to the highest count seen so far - int backup = GetBackupIndex(curFileName); + int backup = GetBackupIndex(baseFile, curFileName); - // caution: we might get a false positive when certain - // date patterns such as yyyyMMdd are used...those are - // valid number but aren't the kind of back up index - // we're looking for if (backup > CurrentSizeRollBackups) { if (0 == MaxSizeRollBackups) @@ -945,10 +945,7 @@ private void InitializeFromOneFile(string baseFile, string curFileName) /// Attempts to extract a number from the end of the file name that indicates /// the number of the times the file has been rolled over. /// - /// - /// Certain date pattern extensions like yyyyMMdd will be parsed as valid backup indexes. - /// - private int GetBackupIndex(string curFileName) + private int GetBackupIndex(string baseFile, string curFileName) { int result = -1; string fileName = curFileName; @@ -956,11 +953,20 @@ private int GetBackupIndex(string curFileName) if (PreserveLogFileNameExtension) { fileName = Path.GetFileNameWithoutExtension(fileName); + baseFile = Path.GetFileNameWithoutExtension(baseFile); } int index = fileName.LastIndexOf(".", StringComparison.Ordinal); if (index > 0) { + // with a static name, log.txt.20261003.1 is a backup of an earlier date + if (StaticLogFileName + && (!string.Equals(fileName.Substring(0, index), baseFile, StringComparison.Ordinal) + || IsDateSuffix(fileName.Substring(index)))) + { + return -1; + } + // if the "yyyy-MM-dd" component of file.log.yyyy-MM-dd is passed to TryParse // it will gracefully fail and return backUpIndex will be 0 _ = SystemInfo.TryParse(fileName.Substring(index + 1), out result); @@ -969,6 +975,16 @@ private int GetBackupIndex(string curFileName) return result; } + /// + /// Determines whether is a date longer than four characters after + /// the dot, such as .yyyyMMdd. Shorter dates (.dd writes .10) cannot be + /// told from a backup index and keep counting as one. + /// + private bool IsDateSuffix(string suffix) + => _rollDate && DatePattern is not null && suffix.Length > 5 + && DateTime.TryParseExact(suffix, DatePattern, DateTimeFormatInfo.InvariantInfo, + DateTimeStyles.None, out _); + /// /// Takes a list of files and a base file name, and looks for 'incremented' versions of the base file. /// Bumps the max count up to the highest count seen.