Fixed RollingFileSink to circumvent file system tunneling

Monday I blogged about a problem I encountered with the RollingFileSink. Unfortunately deleting the logfile once and waiting for more than 15 seconds before creating a new log file solves the problem only for one day (or whatever age limit you have set). To fix the problem completely I had to change the RollingFileSink. Fortunately that is possible because Hisham Baz released RollingFileSink in source code form.

I fixed the LogRoller class. You can find the code that I changed and added below or you download LogRoller.cs. The line with CreateNewLogFile(); in the PerformRenameRollover method is new and the CreateNewLogFile method itself is new. (Note that the indentation of the code is lost due to problems with our blog engine).

/// <summary>
/// Archive the current log file by renaming it with today's timestamp.
/// Generate a new filename for the current log file using a <see cref="FilenameBuilder"/>.
/// </summary>
public void PerformRenameRollover()
{
Purge();
if (File.Exists(_info.FullName))
{
string newName = _builder.CreateNewFilename();
File.Move(_info.FullName, newName);
CreateNewLogFile();
}
}
/// <summary>
/// Creates a new current log file and explicitly sets its creation <see cref="DateTime"/> to <see cref="DateTime.Now"/>.
/// </summary>
/// <remarks>Explicitly creating the new file and explicitly setting its creation <see cref="DateTime"/> is
/// necessary due to file system tunneling. Due the file system tunneling a new file will get the creation <see cref="DateTime"/>
/// of an older file that existed with the same name but that was deleted or renamed within 15 seconds of
/// the creation operation.</remarks>
private void CreateNewLogFile()
{
FileStream newLogFileStream = File.Create(_info.FullName, 1);
newLogFileStream.Close();
File.SetCreationTime(_info.FullName, DateTime.Now);
}

One thought on “Fixed RollingFileSink to circumvent file system tunneling

  1. Kaushik Roy

    I went throught he road as suggested by you but tstill faced the problem. To circumvent the problem I modified the file LogRoller.cs. Right now it does not create any more 1K files, and behaves perfectly.

    Actually I modified the following method -
    public void PerformRenameRollover()
    {
    Purge();
    if (File.Exists(_info.FullName))
    {
    string newName = _builder.CreateNewFilename();
    if(CheckExceededThresholds())
    File.Move(_info.FullName, newName);
    }
    }

    The entire source code for LogRoller.cs is as follows -

    using System;
    using System.Collections;
    using System.IO;

    namespace Avanade.Baz.Logging.Sinks
    {
    ///

    /// Used internally by the RollingFileSink to evaluate rollover thresholds.
    /// Also is used to rename a log file that has exceeded thresholds.
    ///

    internal class LogRoller
    {
    private RollingFileSinkData _data;
    private FileInfo _info;
    private FilenameBuilder _builder;

    ///

    /// Create an instance of the LogRoller.
    ///

    /// Rolling File Sink configuration data.
    public LogRoller(RollingFileSinkData data)
    {
    _data = data;

    _builder = new FilenameBuilder(_data);
    string existingFileWithFullPath = _builder.FormatCurrentFilename();
    _info = new FileInfo(existingFileWithFullPath);
    }

    ///

    /// Archive the current log file by renaming it with today's timestamp.
    /// Generate a new filename for the current log file using a .
    ///

    public void PerformRenameRollover()
    {
    Purge();
    if (File.Exists(_info.FullName))
    {
    string newName = _builder.CreateNewFilename();
    if(CheckExceededThresholds())
    File.Move(_info.FullName, newName);
    }
    }

    ///

    /// Evaluate the age and size threshold.
    ///

    /// Return true if the file has exceeded the thresholds.
    public bool CheckExceededThresholds()
    {
    if (!File.Exists(_info.FullName)) return false;

    return CheckExceededByteThreshold() || CheckExceededAgeThreshold();
    }

    ///

    /// Evaluate the file size threshold.
    ///

    /// Returns true if the file has grown larger than the AgeThreshold.
    private bool CheckExceededByteThreshold()
    {
    bool exceed = false;

    if (_data.ByteThreshold > 0)
    {
    long threshold = _data.ByteThreshold * Convert.ToInt32(_data.ByteUnit);
    if (_info.Length >= threshold)
    {
    exceed = true;
    }
    }
    return exceed;
    }

    ///

    /// Evaluate the age threshold by comparing the file's creation date against today.
    ///

    /// Returns true if the file has grown larger than the AgeThreshold.
    private bool CheckExceededAgeThreshold()
    {
    bool exceed = false;
    if (_data.AgeThreshold > 0)
    {
    double elapsedValue = GetElapsedAgeValue();

    if (elapsedValue >= _data.AgeThreshold)
    {
    exceed = true;
    }
    }

    return exceed;
    }

    private double GetElapsedAgeValue()
    {
    TimeSpan age = DateTime.Now.Subtract(_info.CreationTime);

    double elapsedValue = 0;
    switch (_data.AgeUnit)
    {
    case (AgeThresholdUnit.Minutes):
    elapsedValue = age.TotalMinutes;
    break;
    case (AgeThresholdUnit.Hours):
    elapsedValue = age.TotalHours;
    break;
    case (AgeThresholdUnit.Days):
    elapsedValue = age.TotalDays;
    break;
    case (AgeThresholdUnit.Weeks):
    elapsedValue = age.TotalDays / 7;
    break;
    case (AgeThresholdUnit.Months):
    elapsedValue = age.TotalDays / 30;
    break;
    default:
    break;
    }
    return elapsedValue;
    }

    private void Purge()
    {
    if (_data.MaximumLogFilesBeforePurge > 0)
    {
    ArrayList sortedFiles = _builder.GetSortedFiles();
    DeleteFiles(sortedFiles);
    }
    }

    private void DeleteFiles(ArrayList sortedFiles)
    {
    int numFilesToDelete = sortedFiles.Count - _data.MaximumLogFilesBeforePurge;
    if (numFilesToDelete > 0)
    {
    int start = 0;
    if (sortedFiles[0].ToString().IndexOf(_data.BaseFilename) > -1)
    {
    start = 1;
    }
    for (int i = start; i < numFilesToDelete + start; i++)
    {
    File.Delete(sortedFiles[i] as string);
    }
    }
    }
    }
    }

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *