Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/changelog/3.5.1/334-rollingfileappender-backup-index.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://logging.apache.org/xml/ns"
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
type="fixed">
<issue id="334" link="https://github.com/apache/logging-log4net/pull/334"/>
<description format="asciidoc">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)</description>
</entry>
157 changes: 157 additions & 0 deletions src/log4net.Tests/Appender/RollingFileAppenderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1664,6 +1664,163 @@ public void TestInitializeRollBackups4()
VerifyInitializeRollBackups(8, maxRollBackups);
}

/// <summary>
/// Tests that a date roll file using a date pattern without "-" (yyyyMMdd)
/// is not mistaken for a backup index, whatever the backup limit
/// </summary>
[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<string> 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));
}

/// <summary>
/// Tests that counting up continues from the highest index, even when
/// the oldest backups were deleted and the indexes passed the backup limit
/// </summary>
[Test]
public void TestInitializeRollBackupsCountsUpPastLimit()
{
const string baseFile = "LogFile.log";
List<string> 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));
}

/// <summary>
/// Tests that backups already rolled to an earlier date are not counted
/// as backups of the current date
/// </summary>
[Test]
public void TestInitializeRollBackupsIgnoresOtherDatePeriods()
{
const string baseFile = "LogFile.log";
List<string> 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));
}

/// <summary>
/// Tests that backups of the current date are found when the date pattern
/// supplies the extension and the extension is preserved
/// </summary>
[Test]
public void TestInitializeRollBackupsDatePatternWithExtension()
{
const string baseFile = "log4net";
List<string> 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));
}

/// <summary>
/// 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
/// </summary>
[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));
}

/// <summary>
/// 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
/// </summary>
[Test]
public void TestInitializeRollBackupsShortDatePatternCountsIndexes()
{
const string baseFile = "log.txt";
List<string> 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));
}

/// <summary>
/// Ensures that no problems result from creating and then closing the appender
/// when it has not also been initialized with ActivateOptions().
Expand Down
46 changes: 31 additions & 15 deletions src/log4net/Appender/RollingFileAppender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -945,22 +945,28 @@ 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.
/// </summary>
/// <remarks>
/// Certain date pattern extensions like yyyyMMdd will be parsed as valid backup indexes.
/// </remarks>
private int GetBackupIndex(string curFileName)
private int GetBackupIndex(string baseFile, string curFileName)
{
int result = -1;
string fileName = 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);
Expand All @@ -969,6 +975,16 @@ private int GetBackupIndex(string curFileName)
return result;
}

/// <summary>
/// Determines whether <paramref name="suffix"/> is a date longer than four characters after
/// the dot, such as <c>.yyyyMMdd</c>. Shorter dates (<c>.dd</c> writes <c>.10</c>) cannot be
/// told from a backup index and keep counting as one.
/// </summary>
private bool IsDateSuffix(string suffix)
=> _rollDate && DatePattern is not null && suffix.Length > 5
&& DateTime.TryParseExact(suffix, DatePattern, DateTimeFormatInfo.InvariantInfo,
DateTimeStyles.None, out _);

/// <summary>
/// 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.
Expand Down
Loading