* some trivial changes in visibility, regions etc. * additional functions, properties, members that aren't yet used from existing code. * NextCheckDate -> GetRollDateTimeRelative: Function works identically if called with relativePeriod==1. Existing code does this. Additionally any count of positive or negative period jumps can be calculated. * Fully isolated getting last write time in a function GetLastWriteTime * Split CombinePath into a static version and a member function to call it. Needed to allow implementing and testing one of the new functions as static function. That new, static function uses CombinePath so i needed a static version.
diff -r 76c5f9136b8f -r 569e06c6cfb3 src/Appender/RollingFileAppender.cs --- a/src/Appender/RollingFileAppender.cs Tue Jan 22 14:31:24 2013 +0100 +++ b/src/Appender/RollingFileAppender.cs Tue Jan 22 14:31:43 2013 +0100 @@ -169,10 +169,6 @@ Composite = 3 } - #endregion - - #region Protected Enums - /// <summary> /// The code assumes that the following 'time' constants are in a increasing sequence. /// </summary> @@ -181,7 +177,7 @@ /// The code assumes that the following 'time' constants are in a increasing sequence. /// </para> /// </remarks> - protected enum RollPoint + public enum RollPoint { /// <summary> /// Roll the log not based on the date @@ -219,7 +215,7 @@ TopOfMonth = 5 } - #endregion Protected Enums + #endregion Public Enums #region Public Instance Constructors @@ -339,6 +335,35 @@ } /// <summary> + /// Gets or sets the maximum number of periods to keep backups for. + /// </summary> + /// <value> + /// The maximum number of periods to keep backups for. + /// </value> + /// <remarks> + /// <para> + /// The value refers to the count of periods backups shall be kept for. Any + /// backups that are older than MaxDateRollBackups * Period get deleted, + /// regardless whether or how many backups exist younger than this exist. + /// </para> + /// <para> + /// Example: If set to 10 and roll pattern is daily everything older than 10 + /// days gets deleted. + /// </para> + /// <para> + /// If set to zero, then there will be no time roll backup files. + /// </para> + /// <para> + /// If a negative number is supplied then no deletions will be made. + /// </para> + /// </remarks> + public int MaxDateRollBackups + { + get { return m_maxDateRollBackups; } + set { m_maxDateRollBackups = value; } + } + + /// <summary> /// Gets or sets the maximum size that the output file is allowed to reach /// before being rolled over to backup files. /// </summary> @@ -598,7 +623,7 @@ if (n >= m_nextCheck) { m_now = n; - m_nextCheck = NextCheckDate(m_now, m_rollPoint); + m_nextCheck = GetRollDateTimeRelative(m_now, m_rollPoint, 1); RollOverTime(true); } @@ -797,18 +822,7 @@ DateTime last; using(SecurityContext.Impersonate(this)) { -#if !NET_1_0 && !CLI_1_0 && !NETCF - if (DateTimeStrategy is UniversalDateTime) - { - last = System.IO.File.GetLastWriteTimeUtc(m_baseFileName); - } - else - { -#endif - last = System.IO.File.GetLastWriteTime(m_baseFileName); -#if !NET_1_0 && !CLI_1_0 && !NETCF - } -#endif + last = GetLastWriteTime(m_baseFileName); } LogLog.Debug(declaringType, "["+last.ToString(m_datePattern,System.Globalization.DateTimeFormatInfo.InvariantInfo)+"] vs. ["+m_now.ToString(m_datePattern,System.Globalization.DateTimeFormatInfo.InvariantInfo)+"]"); @@ -824,6 +838,29 @@ } /// <summary> + /// Gets the last write time for the file given + /// </summary> + /// <param name="fileName">the path to the file</param> + /// <returns>the time of last write</returns> + private DateTime GetLastWriteTime(string fileName) + { + DateTime last; +#if !NET_1_0 && !CLI_1_0 && !NETCF + if (DateTimeStrategy is UniversalDateTime) + { + last = System.IO.File.GetLastWriteTimeUtc(fileName); + } + else + { +#endif + last = System.IO.File.GetLastWriteTime(fileName); +#if !NET_1_0 && !CLI_1_0 && !NETCF + } +#endif + return last; + } + + /// <summary> /// Initializes based on existing conditions at time of <see cref="ActivateOptions"/>. /// </summary> /// <remarks> @@ -1036,7 +1073,7 @@ for(int i = (int)RollPoint.TopOfMinute; i <= (int)RollPoint.TopOfMonth; i++) { // Get string representation of next pattern - string r1 = NextCheckDate(s_date1970, (RollPoint)i).ToString(datePattern, System.Globalization.DateTimeFormatInfo.InvariantInfo); + string r1 = GetRollDateTimeRelative(s_date1970, (RollPoint)i, 1).ToString(datePattern, System.Globalization.DateTimeFormatInfo.InvariantInfo); LogLog.Debug(declaringType, "Type = ["+i+"], r0 = ["+r0+"], r1 = ["+r1+"]"); @@ -1090,7 +1127,7 @@ } // next line added as this removes the name check in rollOver - m_nextCheck = NextCheckDate(m_now, m_rollPoint); + m_nextCheck = GetRollDateTimeRelative(m_now, m_rollPoint, 1); } else { @@ -1131,15 +1168,29 @@ #region Roll File /// <summary> - /// + /// Convenience member function used to call its static equivalent. /// </summary> - /// <param name="path1"></param> - /// <param name="path2">.1, .2, .3, etc.</param> - /// <returns></returns> - private string CombinePath(string path1, string path2) + /// <param name="path1">unindexed raw log file path</param> + /// <param name="path2">date and/or size roll index. .1, .20121224, .20121224.2 or something like that</param> + /// <returns>the combined path</returns> + protected string CombinePath(string path1, string path2) + { + return CombinePath(path1, path2, m_preserveLogFileNameExtension); + } + + /// <summary> + /// Combines a path from the given elements such that if preserveLogFileNameExtension is + /// set to true the date and/or size roll index given in path2 gets inserted or appended correctly + /// into/behind the unindexed path given in path1 + /// </summary> + /// <param name="path1">unindexed raw log file path</param> + /// <param name="path2">date and/or size roll index. .1, .20121224, .20121224.2 or something like that</param> + /// <param name="preserveLogFileNameExtension">preserve the log file extension (insert before) or not (append)</param> + /// <returns>the combined path</returns> + protected static string CombinePath(string path1, string path2, bool preserveLogFileNameExtension) { string extension = Path.GetExtension(path1); - if (m_preserveLogFileNameExtension && extension.Length > 0) + if (preserveLogFileNameExtension && extension.Length > 0) { return Path.Combine(Path.GetDirectoryName(path1), Path.GetFileNameWithoutExtension(path1) + path2 + extension); } @@ -1212,6 +1263,83 @@ } /// <summary> + /// Deletes all files that are outdated considering m_maxDateRollBackups. + /// </summary> + protected void DeleteOutdatedFiles() + { + if (m_maxDateRollBackups >= 0) + { + // find all matching files. + // we don't care about roll indexes or roll date patterns that cannot have been created by us. + // we just handle each file that has the matching base name and the matching extension. + string[] fileNames = Directory.GetFiles(Path.GetDirectoryName(File), Path.GetFileNameWithoutExtension(CombinePath(m_baseFileName, ".*", m_preserveLogFileNameExtension))); + ArrayList fileDeleteList = GetFileDeleteList(fileNames, DateTimeStrategy.Now, m_baseFileName, m_datePattern, m_rollPoint, + m_maxDateRollBackups, m_preserveLogFileNameExtension); + foreach (string fileName in fileDeleteList) + { + DeleteFile(fileName); + } + } + + } + + /// <summary> + /// Gets a list of files to delete considering the rollPoint, maxDateRollBackups, + /// the given current time now, the baseFile name and the datePattern given by + /// filtering the file name list given with a generated positive (keep) list. + /// Files in fileNames that don't appear in the internal keep pattern list get listed in the + /// file list returned and can be in turn deleted by the caller. + /// </summary> + /// <param name="fileNames">list of file names to check for deletion</param> + /// <param name="now">the point of time to be used for deciding whether files are outdated or not</param> + /// <param name="baseFile">the base file name of all log files</param> + /// <param name="datePattern">the date pattern to be used</param> + /// <param name="rollPoint">the roll point to be used</param> + /// <param name="maxDateRollBackups">the maximum number of date roll backups</param> + /// <param name="preserveLogFileNameExtension">preserve log file extensions (insert date/size index) or not (append date/size index)</param> + /// <returns>list of files to be deleted as a filtering result on fileNames at input</returns> + protected static ArrayList GetFileDeleteList(string[] fileNames, DateTime now, string baseFile, string datePattern, RollPoint rollPoint, int maxDateRollBackups, bool preserveLogFileNameExtension) + { + ArrayList list = new ArrayList(); + if (maxDateRollBackups >= 0) + { + // remark: we cannot use file write time here because + // * file might have been touched somehow after period + // * file gets written to after period if a footer is configured and thus been added when file is closed + // therefore we now create a positive pattern list, match each file name found against it and if + // it doesn't match delete the file + + string[] positiveList = new string[maxDateRollBackups + 1]; + for (int i = 0; i <= maxDateRollBackups; i++) + { + DateTime periodStart = GetRollDateTimeRelative(now, rollPoint, -i); + string periodPattern = periodStart.ToString(datePattern, System.Globalization.DateTimeFormatInfo.InvariantInfo) + "*"; + string periodPatternPath = CombinePath(baseFile, periodPattern, preserveLogFileNameExtension); + positiveList[i] = Path.GetFileName(periodPatternPath).Split(new Char[] { '*' })[0]; + } + + foreach (string fileName in fileNames) + { + string fn = Path.GetFileName(fileName); + bool keep = false; + foreach (string fileStart in positiveList) + { + if (fn.StartsWith(fileStart)) + { + keep = true; + break; + } + } + if (!keep) + { + list.Add(fileName); + } + } + } + return list; + } + + /// <summary> /// Renames file <paramref name="fromFile"/> to file <paramref name="toFile"/>. /// </summary> /// <param name="fromFile">Name of existing file to roll.</param> @@ -1466,90 +1594,94 @@ } } - #endregion - - #region NextCheckDate - /// <summary> - /// Get the start time of the next window for the current rollpoint + /// Get the beginning of the relativePeriod-th following or preceeding period relative to the + /// the period that contains the given dateTime. /// </summary> - /// <param name="currentDateTime">the current date</param> + /// <param name="dateTime">the date</param> /// <param name="rollPoint">the type of roll point we are working with</param> - /// <returns>the start time for the next roll point an interval after the currentDateTime date</returns> + /// <param name="relativePeriod"> + /// the number of the period relative to the dateTime given. + /// 0 means the beginning of the period that contains the given dateTime. + /// 1 means the beginning of the period following the period that contains the given dateTime. + /// -7 means the beginning of the 7th period before the period that contains the given dateTime. + /// </param> + /// <returns>the beginning of the period requested.</returns> /// <remarks> /// <para> - /// Returns the date of the next roll point after the currentDateTime date passed to the method. + /// Get the beginning of the relativePeriod-th following or preceeding period relative to the + /// the period that contains the given dateTime. /// </para> /// <para> /// The basic strategy is to subtract the time parts that are less significant /// than the rollpoint from the current time. This should roll the time back to - /// the start of the time window for the current rollpoint. Then we add 1 window - /// worth of time and get the start time of the next window for the rollpoint. + /// the start of the time window for the current rollpoint. Then we add relativePeriod periods + /// worth of time and get the beginning of the period requested. /// </para> /// </remarks> - protected DateTime NextCheckDate(DateTime currentDateTime, RollPoint rollPoint) + protected static DateTime GetRollDateTimeRelative(DateTime dateTime, RollPoint rollPoint, int relativePeriod) { // Local variable to work on (this does not look very efficient) - DateTime current = currentDateTime; + DateTime result = dateTime; // Do slightly different things depending on what the type of roll point we want. switch(rollPoint) { case RollPoint.TopOfMinute: - current = current.AddMilliseconds(-current.Millisecond); - current = current.AddSeconds(-current.Second); - current = current.AddMinutes(1); + result = result.AddMilliseconds(-result.Millisecond); + result = result.AddSeconds(-result.Second); + result = result.AddMinutes(relativePeriod); break; case RollPoint.TopOfHour: - current = current.AddMilliseconds(-current.Millisecond); - current = current.AddSeconds(-current.Second); - current = current.AddMinutes(-current.Minute); - current = current.AddHours(1); + result = result.AddMilliseconds(-result.Millisecond); + result = result.AddSeconds(-result.Second); + result = result.AddMinutes(-result.Minute); + result = result.AddHours(relativePeriod); break; case RollPoint.HalfDay: - current = current.AddMilliseconds(-current.Millisecond); - current = current.AddSeconds(-current.Second); - current = current.AddMinutes(-current.Minute); + result = result.AddMilliseconds(-result.Millisecond); + result = result.AddSeconds(-result.Second); + result = result.AddMinutes(-result.Minute); - if (current.Hour < 12) + if (result.Hour < 12) { - current = current.AddHours(12 - current.Hour); + result = result.AddHours(-result.Hour); } else { - current = current.AddHours(-current.Hour); - current = current.AddDays(1); + result = result.AddHours(12-result.Hour); } + result = result.AddHours(12 * relativePeriod); break; case RollPoint.TopOfDay: - current = current.AddMilliseconds(-current.Millisecond); - current = current.AddSeconds(-current.Second); - current = current.AddMinutes(-current.Minute); - current = current.AddHours(-current.Hour); - current = current.AddDays(1); + result = result.AddMilliseconds(-result.Millisecond); + result = result.AddSeconds(-result.Second); + result = result.AddMinutes(-result.Minute); + result = result.AddHours(-result.Hour); + result = result.AddDays(relativePeriod); break; case RollPoint.TopOfWeek: - current = current.AddMilliseconds(-current.Millisecond); - current = current.AddSeconds(-current.Second); - current = current.AddMinutes(-current.Minute); - current = current.AddHours(-current.Hour); - current = current.AddDays(7 - (int)current.DayOfWeek); + result = result.AddMilliseconds(-result.Millisecond); + result = result.AddSeconds(-result.Second); + result = result.AddMinutes(-result.Minute); + result = result.AddHours(-result.Hour); + result = result.AddDays(7*relativePeriod - (int)result.DayOfWeek); break; case RollPoint.TopOfMonth: - current = current.AddMilliseconds(-current.Millisecond); - current = current.AddSeconds(-current.Second); - current = current.AddMinutes(-current.Minute); - current = current.AddHours(-current.Hour); - current = current.AddDays(1 - current.Day); /* first day of month is 1 not 0 */ - current = current.AddMonths(1); + result = result.AddMilliseconds(-result.Millisecond); + result = result.AddSeconds(-result.Second); + result = result.AddMinutes(-result.Minute); + result = result.AddHours(-result.Hour); + result = result.AddDays(1 - result.Day); /* first day of month is 1 not 0 */ + result = result.AddMonths(relativePeriod); break; } - return current; + return result; } #endregion @@ -1612,6 +1744,11 @@ private int m_countDirection = -1; /// <summary> + /// There is zero backup files by default + /// </summary> + private int m_maxDateRollBackups = 0; + + /// <summary> /// The rolling mode used in this appender. /// </summary> private RollingMode m_rollingStyle = RollingMode.Composite;