Fri, Aug 12 2005 1:57 PM Erwyn van der Meer

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);
}
Filed under:

# re: Fixed RollingFileSink to circumvent file system tunneling

Monday, September 19, 2005 2:06 PM by Kaushik

I went through you article of fixing the rolling sink problem and the fix around it. I would appreciate if you can also upload the dll's.

# re: Fixed RollingFileSink to circumvent file system tunneling

Tuesday, September 20, 2005 8:33 AM by 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
{
/// <summary>
/// Used internally by the RollingFileSink to evaluate rollover thresholds.
/// Also is used to rename a log file that has exceeded thresholds.
/// </summary>
internal class LogRoller
{
private RollingFileSinkData _data;
private FileInfo _info;
private FilenameBuilder _builder;

/// <summary>
/// Create an instance of the LogRoller.
/// </summary>
/// <param name="data">Rolling File Sink configuration data.</param>
public LogRoller(RollingFileSinkData data)
{
_data = data;

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

/// <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();
if(CheckExceededThresholds())
File.Move(_info.FullName, newName);
}
}

/// <summary>
/// Evaluate the age and size threshold.
/// </summary>
/// <returns>Return true if the file has exceeded the thresholds.</returns>
public bool CheckExceededThresholds()
{
if (!File.Exists(_info.FullName)) return false;

return CheckExceededByteThreshold() || CheckExceededAgeThreshold();
}


/// <summary>
/// Evaluate the file size threshold.
/// </summary>
/// <returns>Returns true if the file has grown larger than the AgeThreshold.</returns>
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;
}

/// <summary>
/// Evaluate the age threshold by comparing the file's creation date against today.
/// </summary>
/// <returns>Returns true if the file has grown larger than the AgeThreshold.</returns>
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);
}
}
}
}
}

# Rolling File Trace Listener Extension for Enterprise Library 2.0

Wednesday, February 15, 2006 11:42 AM by Erwyn van der Meer

I decided to create an EntLib 2.0 version of the Rolling File Sink. My extension is called the Rolling File Trace Listener. It allows log files to roll over based on both age and size limits.