diff --git a/src/integration-testing/log4net-672/Program.cs b/src/integration-testing/log4net-672/Program.cs index 64be479b..79f47611 100644 --- a/src/integration-testing/log4net-672/Program.cs +++ b/src/integration-testing/log4net-672/Program.cs @@ -1,102 +1,91 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; -using System.Xml; using log4net; using log4net.Config; -using log4net.Core; -if (true) +const int NO_ERROR = 0; +const int MISSING_LOGS = 1; +const int OVERWRITTEN_LOGS = 2; + +var appPath = new Uri(Assembly.GetExecutingAssembly().Location).LocalPath; +var appFolder = Path.GetDirectoryName(appPath); +if (appFolder is null) +{ + throw new InvalidOperationException( + $"Can't determine app folder for {appPath}" + ); +} + +var logFolder = Path.Combine(appFolder, "Logs"); +if (Directory.Exists(logFolder)) +{ + Directory.Delete(logFolder, recursive: true); +} + +var configFile = Path.Combine(appFolder, "log4net.config"); +if (!File.Exists(configFile)) { - var appPath = new Uri(Assembly.GetExecutingAssembly().Location).LocalPath; - var appFolder = Path.GetDirectoryName(appPath); - if (appFolder is null) + throw new InvalidOperationException($"log4net.config not found at {configFile}"); +} + +var logCount = 10; +var identifiers = new List<Guid>(); +for (var i = 0; i < 10; i++) +{ + var identifier = Guid.NewGuid(); + identifiers.Add(identifier); + var logged = LogWith(identifier, logCount); + if (logged != logCount) { - throw new InvalidOperationException( - $"Can't determine app folder for {appPath}" - ); + Die($"Missing logs immediately for '{identifier}' - found {logged}/{logCount}", MISSING_LOGS); } +} - var configFile = Path.Combine(appFolder, "log4net.config"); - if (!File.Exists(configFile)) +foreach (var identifier in identifiers) +{ + var logged = CountIdentifierInLogs(identifier); + if (logged != logCount) { - throw new InvalidOperationException($"log4net.config not found at {configFile}"); + Die($"Logs have been overwritten for '{identifier}' - found {logged}/{logCount}", OVERWRITTEN_LOGS); } +} - var info = new FileInfo(configFile); +Console.WriteLine("All good: LOG4NET-672 is resolved"); +return NO_ERROR; - XmlConfigurator.Configure(info); +void Die(string message, int exitCode) +{ + Console.Error.WriteLine(message); + Environment.Exit(exitCode); +} + +int CountIdentifierInLogs(Guid id) +{ + return Directory.EnumerateFiles("Logs").Select( + filePath => CountIdentifierInFile(id, filePath) + ).Sum(); +} + +int CountIdentifierInFile(Guid id, string filePath) +{ + var contents = File.ReadAllLines(filePath); + return contents.Count(line => line.Contains(id.ToString())); +} +int LogWith(Guid identifier, int howManyLogs) +{ + var info = new FileInfo(configFile); + XmlConfigurator.Configure(info); var logger = LogManager.GetLogger("main"); - for (var i = 0; i < 10; i++) + for (var i = 0; i < howManyLogs; i++) { - logger.Info($"test log {i}"); + logger.Info($"test log {i} [{identifier}]"); } LogManager.Flush(int.MaxValue); -} - -// Sample.Main(); -// -// public class Sample -// { -// private const string filename = "sampledata.xml"; -// -// public static void Main() -// { -// -// XmlTextWriter writer = new XmlTextWriter (filename, null); -// //Use indenting for readability. -// writer.Formatting = Formatting.Indented; -// -// writer.WriteComment("sample XML fragment"); -// -// //Write an element (this one is the root). -// writer.WriteStartElement("bookstore"); -// -// //Write the namespace declaration. -// writer.WriteAttributeString("xmlns", "bk", null, "log4net"); -// -// writer.WriteStartElement("book"); -// -// //Lookup the prefix and then write the ISBN attribute. -// string prefix = writer.LookupPrefix("urn:samples"); -// writer.WriteStartAttribute(prefix, "ISBN", "urn:samples"); -// writer.WriteString("1-861003-78"); -// writer.WriteEndAttribute(); -// -// //Write the title. -// writer.WriteStartElement("title"); -// writer.WriteString("The Handmaid's Tale"); -// writer.WriteEndElement(); -// -// //Write the price. -// writer.WriteElementString("price", "19.95"); -// -// //Write the style element. -// writer.WriteStartElement(prefix, "style", "urn:samples"); -// writer.WriteString("hardcover"); -// writer.WriteEndElement(); -// -// //Write the end tag for the book element. -// writer.WriteEndElement(); -// -// //Write the close tag for the root element. -// writer.WriteEndElement(); -// -// //Write the XML to file and close the writer. -// writer.Flush(); -// writer.Close(); -// -// //Read the file back in and parse to ensure well formed XML. -// XmlDocument doc = new XmlDocument(); -// //Preserve white space for readability. -// doc.PreserveWhitespace = true; -// //Load the file -// doc.Load(filename); -// -// //Write the XML content to the console. -// Console.Write(doc.InnerXml); -// } -// } + return CountIdentifierInLogs(identifier); +} \ No newline at end of file diff --git a/src/integration-testing/log4net-672/log4net-672.csproj b/src/integration-testing/log4net-672/log4net-672.csproj index e0ac3505..5a134f92 100644 --- a/src/integration-testing/log4net-672/log4net-672.csproj +++ b/src/integration-testing/log4net-672/log4net-672.csproj @@ -20,4 +20,8 @@ </None> </ItemGroup> + <ItemGroup> + <PackageReference Include="PeanutButter.EasyArgs" Version="2.0.63" /> + </ItemGroup> + </Project> diff --git a/src/log4net.Tests/Appender/RollingFileAppenderTest.cs b/src/log4net.Tests/Appender/RollingFileAppenderTest.cs index 7280eec0..d6cee4b9 100644 --- a/src/log4net.Tests/Appender/RollingFileAppenderTest.cs +++ b/src/log4net.Tests/Appender/RollingFileAppenderTest.cs @@ -1,4 +1,5 @@ #region Apache License + // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with @@ -15,6 +16,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // + #endregion using System; @@ -23,2036 +25,2118 @@ using System.IO; using System.Text; using System.Text.RegularExpressions; - using log4net.Appender; using log4net.Core; using log4net.Layout; using log4net.Repository.Hierarchy; using log4net.Util; - using NUnit.Framework; using System.Globalization; namespace log4net.Tests.Appender { - /// <summary> - /// Used for internal unit testing the <see cref="RollingFileAppender"/> class. - /// </summary> - [TestFixture] - public class RollingFileAppenderTest - { - private const string c_fileName = "test_41d3d834_4320f4da.log"; - private const string c_testMessage98Chars = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567"; - private const string c_testMessage99Chars = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"; - private const int c_iMaximumFileSize = 450; // in bytes - private int _iMessagesLoggedThisFile = 0; - private int _iMessagesLogged = 0; - private int _iCountDirection = 0; - private int _MaxSizeRollBackups = 3; - private CountingAppender _caRoot; - private Logger _root; + /// <summary> + /// Used for internal unit testing the <see cref="RollingFileAppender"/> class. + /// </summary> + [TestFixture] + public class RollingFileAppenderTest + { + private const string c_fileName = "test_41d3d834_4320f4da.log"; + + private const string c_testMessage98Chars = + "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567"; + + private const string c_testMessage99Chars = + "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"; + + private const int c_iMaximumFileSize = 450; // in bytes + private int _iMessagesLoggedThisFile = 0; + private int _iMessagesLogged = 0; + private int _iCountDirection = 0; + private int _MaxSizeRollBackups = 3; + private CountingAppender _caRoot; + private Logger _root; #if !NETSTANDARD1_3 - private CultureInfo _currentCulture; - private CultureInfo _currentUICulture; + private CultureInfo _currentCulture; + private CultureInfo _currentUICulture; #endif - private class SilentErrorHandler : IErrorHandler - { - private StringBuilder m_buffer = new StringBuilder(); - - public string Message - { - get { return m_buffer.ToString(); } - } - - public void Error(string message) - { - m_buffer.Append(message + "\n"); - } - - public void Error(string message, Exception e) - { - m_buffer.Append(message + "\n" + e.Message + "\n"); - } - - public void Error(string message, Exception e, ErrorCode errorCode) - { - m_buffer.Append(message + "\n" + e.Message + "\n"); - } - } - - /// <summary> - /// Sets up variables used for the tests - /// </summary> - private void InitializeVariables() - { - _iMessagesLoggedThisFile = 0; - _iMessagesLogged = 0; - _iCountDirection = +1; // Up - _MaxSizeRollBackups = 3; - } - - /// <summary> - /// Shuts down any loggers in the hierarchy, along - /// with all appenders, and deletes any test files used - /// for logging. - /// </summary> - private static void ResetAndDeleteTestFiles() - { - // Regular users should not use the clear method lightly! - Utils.GetRepository().ResetConfiguration(); - Utils.GetRepository().Shutdown(); - ((Repository.Hierarchy.Hierarchy)Utils.GetRepository()).Clear(); - - DeleteTestFiles(); - } - - /// <summary> - /// Any initialization that happens before each test can - /// go here - /// </summary> - [SetUp] - public void SetUp() - { - ResetAndDeleteTestFiles(); - InitializeVariables(); + private class SilentErrorHandler : IErrorHandler + { + private StringBuilder m_buffer = new StringBuilder(); + + public string Message + { + get { return m_buffer.ToString(); } + } + + public void Error(string message) + { + m_buffer.Append(message + "\n"); + } + + public void Error(string message, Exception e) + { + m_buffer.Append(message + "\n" + e.Message + "\n"); + } + + public void Error(string message, Exception e, ErrorCode errorCode) + { + m_buffer.Append(message + "\n" + e.Message + "\n"); + } + } + + /// <summary> + /// Sets up variables used for the tests + /// </summary> + private void InitializeVariables() + { + _iMessagesLoggedThisFile = 0; + _iMessagesLogged = 0; + _iCountDirection = +1; // Up + _MaxSizeRollBackups = 3; + } + + /// <summary> + /// Shuts down any loggers in the hierarchy, along + /// with all appenders, and deletes any test files used + /// for logging. + /// </summary> + private static void ResetAndDeleteTestFiles() + { + // Regular users should not use the clear method lightly! + Utils.GetRepository().ResetConfiguration(); + Utils.GetRepository().Shutdown(); + ((Repository.Hierarchy.Hierarchy)Utils.GetRepository()).Clear(); + + DeleteTestFiles(); + } + + /// <summary> + /// Any initialization that happens before each test can + /// go here + /// </summary> + [SetUp] + public void SetUp() + { + ResetAndDeleteTestFiles(); + InitializeVariables(); #if !NETSTANDARD1_3 - // set correct thread culture - _currentCulture = System.Threading.Thread.CurrentThread.CurrentCulture; - _currentUICulture = System.Threading.Thread.CurrentThread.CurrentUICulture; - System.Threading.Thread.CurrentThread.CurrentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture; + // set correct thread culture + _currentCulture = System.Threading.Thread.CurrentThread.CurrentCulture; + _currentUICulture = System.Threading.Thread.CurrentThread.CurrentUICulture; + System.Threading.Thread.CurrentThread.CurrentCulture = + System.Threading.Thread.CurrentThread.CurrentUICulture = + System.Globalization.CultureInfo.InvariantCulture; #endif - } + } - /// <summary> - /// Any steps that happen after each test go here - /// </summary> - [TearDown] - public void TearDown() - { - ResetAndDeleteTestFiles(); + /// <summary> + /// Any steps that happen after each test go here + /// </summary> + [TearDown] + public void TearDown() + { + ResetAndDeleteTestFiles(); #if !NETSTANDARD1_3 - // restore previous culture - System.Threading.Thread.CurrentThread.CurrentCulture = _currentCulture; - System.Threading.Thread.CurrentThread.CurrentUICulture = _currentUICulture; + // restore previous culture + System.Threading.Thread.CurrentThread.CurrentCulture = _currentCulture; + System.Threading.Thread.CurrentThread.CurrentUICulture = _currentUICulture; #endif - } - - /// <summary> - /// Finds the number of files that match the base file name, - /// and matches the result against an expected count - /// </summary> - /// <param name="iExpectedCount"></param> - private static void VerifyFileCount(int iExpectedCount) - { - VerifyFileCount(iExpectedCount, false); + } + + /// <summary> + /// Finds the number of files that match the base file name, + /// and matches the result against an expected count + /// </summary> + /// <param name="iExpectedCount"></param> + private static void VerifyFileCount(int iExpectedCount) + { + VerifyFileCount(iExpectedCount, false); + } + + /// <summary> + /// Finds the number of files that match the base file name, + /// and matches the result against an expected count + /// </summary> + /// <param name="iExpectedCount"></param> + private static void VerifyFileCount(int iExpectedCount, bool preserveLogFileNameExtension) + { + ArrayList alFiles = GetExistingFiles(c_fileName, preserveLogFileNameExtension); + Assert.IsNotNull(alFiles); + Assert.AreEqual(iExpectedCount, alFiles.Count); + } + + /// <summary> + /// Creates a file with the given number, and the shared base file name + /// </summary> + /// <param name="iFileNumber"></param> + private static void CreateFile(int iFileNumber) + { + FileInfo fileInfo = new FileInfo(MakeFileName(c_fileName, iFileNumber)); + + FileStream fileStream = null; + try + { + fileStream = fileInfo.Create(); + } + finally + { + if (null != fileStream) + { + try + { + fileStream.Close(); + } + catch + { + } } - /// <summary> - /// Finds the number of files that match the base file name, - /// and matches the result against an expected count - /// </summary> - /// <param name="iExpectedCount"></param> - private static void VerifyFileCount(int iExpectedCount, bool preserveLogFileNameExtension) - { - ArrayList alFiles = GetExistingFiles(c_fileName, preserveLogFileNameExtension); - Assert.IsNotNull(alFiles); - Assert.AreEqual(iExpectedCount, alFiles.Count); - } - - /// <summary> - /// Creates a file with the given number, and the shared base file name - /// </summary> - /// <param name="iFileNumber"></param> - private static void CreateFile(int iFileNumber) - { - FileInfo fileInfo = new FileInfo(MakeFileName(c_fileName, iFileNumber)); - - FileStream fileStream = null; - try - { - fileStream = fileInfo.Create(); - } - finally - { - if (null != fileStream) - { - try - { - fileStream.Close(); - } - catch - { - } - } - } - } - - /// <summary> - /// Verifies that the code correctly loads all filenames - /// </summary> - [Test] - public void TestGetExistingFiles() - { - VerifyFileCount(0); - CreateFile(0); - VerifyFileCount(1); - CreateFile(1); - VerifyFileCount(2); - } - - [Test] - public void RollingCombinedWithPreserveExtension() + } + } + + /// <summary> + /// Verifies that the code correctly loads all filenames + /// </summary> + [Test] + public void TestGetExistingFiles() + { + VerifyFileCount(0); + CreateFile(0); + VerifyFileCount(1); + CreateFile(1); + VerifyFileCount(2); + } + + [Test] + public void RollingCombinedWithPreserveExtension() + { + _root = ((Repository.Hierarchy.Hierarchy)Utils.GetRepository()).Root; + _root.Level = Level.All; + PatternLayout patternLayout = new PatternLayout(); + patternLayout.ActivateOptions(); + + RollingFileAppender roller = new RollingFileAppender(); + roller.StaticLogFileName = false; + roller.Layout = patternLayout; + roller.AppendToFile = true; + roller.RollingStyle = RollingFileAppender.RollingMode.Composite; + roller.DatePattern = "dd_MM_yyyy"; + roller.MaxSizeRollBackups = 1; + roller.CountDirection = 1; + roller.PreserveLogFileNameExtension = true; + roller.MaximumFileSize = "10KB"; + roller.File = c_fileName; + roller.ActivateOptions(); + _root.AddAppender(roller); + + _root.Repository.Configured = true; + + for (int i = 0; i < 1000; i++) { - _root = ((Repository.Hierarchy.Hierarchy)Utils.GetRepository()).Root; - _root.Level = Level.All; - PatternLayout patternLayout = new PatternLayout(); - patternLayout.ActivateOptions(); - - RollingFileAppender roller = new RollingFileAppender(); - roller.StaticLogFileName = false; - roller.Layout = patternLayout; - roller.AppendToFile = true; - roller.RollingStyle = RollingFileAppender.RollingMode.Composite; - roller.DatePattern = "dd_MM_yyyy"; - roller.MaxSizeRollBackups = 1; - roller.CountDirection = 1; - roller.PreserveLogFileNameExtension = true; - roller.MaximumFileSize = "10KB"; - roller.File = c_fileName; - roller.ActivateOptions(); - _root.AddAppender(roller); - - _root.Repository.Configured = true; - - for (int i = 0; i < 1000; i++) + StringBuilder s = new StringBuilder(); + for (int j = 50; j < 100; j++) { - StringBuilder s = new StringBuilder(); - for (int j = 50; j < 100; j++) + if (j > 50) { - if (j > 50) { - s.Append(" "); - } - s.Append(j); + s.Append(" "); } - _root.Log(Level.Debug, s.ToString(), null); + + s.Append(j); } - VerifyFileCount(2, true); + + _root.Log(Level.Debug, s.ToString(), null); } - /// <summary> - /// Removes all test files that exist - /// </summary> - private static void DeleteTestFiles() - { - ArrayList alFiles = GetExistingFiles(c_fileName); - alFiles.AddRange(GetExistingFiles(c_fileName, true)); - foreach(string sFile in alFiles) - { - try - { - Debug.WriteLine("Deleting test file " + sFile); - File.Delete(sFile); - } - catch(Exception ex) - { - Debug.WriteLine("Exception while deleting test file " + ex); - } - } - } - - ///// <summary> - ///// Generates a file name associated with the count. - ///// </summary> - ///// <param name="iFileCount"></param> - ///// <returns></returns> - //private string MakeFileName(int iFileCount) - //{ - // return MakeFileName(_fileName, iFileCount); - //} - - /// <summary> - /// Generates a file name associated with the count, using - /// the base file name. - /// </summary> - /// <param name="sBaseFile"></param> - /// <param name="iFileCount"></param> - /// <returns></returns> - private static string MakeFileName(string sBaseFile, int iFileCount) - { - if (0 == iFileCount) - { - return sBaseFile; - } - return sBaseFile + "." + iFileCount; - } - - /// <summary> - /// Returns a RollingFileAppender using all the internal settings for maximum - /// file size and number of backups - /// </summary> - /// <returns></returns> - private RollingFileAppender CreateAppender() - { - return CreateAppender(new FileAppender.ExclusiveLock()); - } - - /// <summary> - /// Returns a RollingFileAppender using all the internal settings for maximum - /// file size and number of backups - /// </summary> - /// <param name="lockModel">The locking model to test</param> - /// <returns></returns> - private RollingFileAppender CreateAppender(FileAppender.LockingModelBase lockModel) - { - // - // Use a basic pattern that - // includes just the message and a CR/LF. - // - PatternLayout layout = new PatternLayout("%m%n"); - - // - // Create the new appender - // - RollingFileAppender appender = new RollingFileAppender(); - appender.Layout = layout; - appender.File = c_fileName; - appender.Encoding = Encoding.ASCII; - appender.MaximumFileSize = c_iMaximumFileSize.ToString(); - appender.MaxSizeRollBackups = _MaxSizeRollBackups; - appender.CountDirection = _iCountDirection; - appender.RollingStyle = RollingFileAppender.RollingMode.Size; - appender.LockingModel = lockModel; - - appender.ActivateOptions(); - - return appender; - } - - /// <summary> - /// Used for test purposes, a table of these objects can be used to identify - /// any existing files and their expected length. - /// </summary> - public class RollFileEntry - { - /// <summary> - /// Stores the name of the file - /// </summary> - private string m_fileName; - - /// <summary> - /// The expected length of the file - /// </summary> - private long m_fileLength; - - /// <summary> - /// Default constructor - /// </summary> - public RollFileEntry() - { - } - - /// <summary> - /// Constructor used when the fileInfo and expected length are known - /// </summary> - /// <param name="fileName"></param> - /// <param name="fileLength"></param> - public RollFileEntry(string fileName, long fileLength) - { - m_fileName = fileName; - m_fileLength = fileLength; - } - - /// <summary> - /// Stores the name of the file - /// </summary> - public string FileName - { - get { return m_fileName; } - } - - /// <summary> - /// The expected length of the file - /// </summary> - public long FileLength - { - get { return m_fileLength; } - } - } - - /// <summary> - /// Used for table-driven testing. This class holds information that can be used - /// for testing of file rolling. - /// </summary> - public class RollConditions - { - /// <summary> - /// A table of entries showing files that should exist and their expected sizes - /// before logging is called - /// </summary> - private RollFileEntry[] m_preLogFileEntries; - - /// <summary> - /// A table of entries showing files that should exist and their expected sizes - /// after a message is logged - /// </summary> - private RollFileEntry[] m_postLogFileEntries; - - /// <summary> - /// Constructor, taking all required parameters - /// </summary> - /// <param name="preLogFileEntries"></param> - /// <param name="postLogFileEntries"></param> - public RollConditions(RollFileEntry[] preLogFileEntries, RollFileEntry[] postLogFileEntries) - { - m_preLogFileEntries = preLogFileEntries; - m_postLogFileEntries = postLogFileEntries; - } - - /// <summary> - /// A table of entries showing files that should exist and their expected sizes - /// before logging is called - /// </summary> - public RollFileEntry[] GetPreLogFileEntries() - { - return m_preLogFileEntries; - } - - /// <summary> - /// A table of entries showing files that should exist and their expected sizes - /// after a message is logged - /// </summary> - public RollFileEntry[] GetPostLogFileEntries() - { - return m_postLogFileEntries; - } - } - - private static void VerifyExistenceAndRemoveFromList(ArrayList alExisting, string sFileName, FileInfo file, RollFileEntry entry) - { - Assert.IsTrue(alExisting.Contains(sFileName), "filename {0} not found in test directory", sFileName); - Assert.AreEqual(entry.FileLength, file.Length, "file length mismatch"); - // Remove this file from the list - alExisting.Remove(sFileName); - } - - /// <summary> - /// Checks that all the expected files exist, and only the expected files. Also - /// verifies the length of all files against the expected length - /// </summary> - /// <param name="sBaseFileName"></param> - /// <param name="fileEntries"></param> - private static void VerifyFileConditions(string sBaseFileName, RollFileEntry[] fileEntries) - { - ArrayList alExisting = GetExistingFiles(sBaseFileName); - if (null != fileEntries) - { - // AssertEquals( "File count mismatch", alExisting.Count, fileEntries.Length ); - foreach(RollFileEntry rollFile in fileEntries) - { - string sFileName = rollFile.FileName; - FileInfo file = new FileInfo(sFileName); - - if (rollFile.FileLength > 0) - { - Assert.IsTrue(file.Exists, "filename {0} does not exist", sFileName); - VerifyExistenceAndRemoveFromList(alExisting, sFileName, file, rollFile); - } - else - { - // If length is 0, file may not exist yet. If file exists, make sure length - // is zero. If file doesn't exist, this is OK - - if (file.Exists) - { - VerifyExistenceAndRemoveFromList(alExisting, sFileName, file, rollFile); - } - } - } - } - else - { - Assert.AreEqual(0, alExisting.Count); - } - - // This check ensures no extra files matching the wildcard pattern exist. - // We only want the files we expect, and no others - Assert.AreEqual(0, alExisting.Count); - } - - /// <summary> - /// Called before logging a message to check that all the expected files exist, - /// and only the expected files. Also verifies the length of all files against - /// the expected length - /// </summary> - /// <param name="sBaseFileName"></param> - /// <param name="entry"></param> - private static void VerifyPreConditions(string sBaseFileName, RollConditions entry) - { - VerifyFileConditions(sBaseFileName, entry.GetPreLogFileEntries()); - } - - /// <summary> - /// Called after logging a message to check that all the expected files exist, - /// and only the expected files. Also verifies the length of all files against - /// the expected length - /// </summary> - /// <param name="sBaseFileName"></param> - /// <param name="entry"></param> - private static void VerifyPostConditions(string sBaseFileName, RollConditions entry) - { - VerifyFileConditions(sBaseFileName, entry.GetPostLogFileEntries()); - } - - /// <summary> - /// Logs a message, verifying the expected message counts against the - /// current running totals. - /// </summary> - /// <param name="entry"></param> - /// <param name="sMessageToLog"></param> - private void LogMessage(RollConditions entry, string sMessageToLog) - { - Assert.AreEqual(_caRoot.Counter, _iMessagesLogged++); - _root.Log(Level.Debug, sMessageToLog, null); - Assert.AreEqual(_caRoot.Counter, _iMessagesLogged); - _iMessagesLoggedThisFile++; - } - - //private void DumpFileEntry( RollFileEntry entry ) - //{ - // System.Diagnostics.Debug.WriteLine( "\tfile name: " + entry.FileName ); - // System.Diagnostics.Debug.WriteLine( "\tfile length: " + entry.FileLength ); - //} - - //private void DumpTableEntry( RollConditions entry ) - //{ - // System.Diagnostics.Debug.WriteLine( "Pre-Conditions" ); - // foreach( RollFileEntry file in entry.GetPreLogFileEntries() ) - // { - // DumpFileEntry( file ); - // } - // System.Diagnostics.Debug.WriteLine( "Post-Conditions" ); - // foreach( RollFileEntry file in entry.GetPostLogFileEntries() ) - // { - // DumpFileEntry( file ); - // } - // // System.Diagnostics.Debug.WriteLine(""); - //} - - /// <summary> - /// Runs through all table entries, logging messages. Before each message is logged, - /// pre-conditions are checked to ensure the expected files exist and they are the - /// expected size. After logging, verifies the same. - /// </summary> - /// <param name="sBaseFileName"></param> - /// <param name="entries"></param> - /// <param name="sMessageToLog"></param> - private void RollFromTableEntries(string sBaseFileName, RollConditions[] entries, string sMessageToLog) - { - for(int i = 0; i < entries.Length; i++) - { - RollConditions entry = entries[i]; - - // System.Diagnostics.Debug.WriteLine( i + ": Entry " + i + " pre/post conditions"); - // DumpTableEntry( entry ); - // System.Diagnostics.Debug.WriteLine( i + ": Testing entry pre-conditions"); - VerifyPreConditions(sBaseFileName, entry); - // System.Diagnostics.Debug.WriteLine( i + ": Logging message"); - LogMessage(entry, sMessageToLog); - // System.Diagnostics.Debug.WriteLine( i + ": Testing entry post-conditions"); - VerifyPostConditions(sBaseFileName, entry); - // System.Diagnostics.Debug.WriteLine( i + ": Finished validating entry\n"); - } - } + VerifyFileCount(2, true); + } - private static readonly int s_Newline_Length = Environment.NewLine.Length; + /// <summary> + /// Removes all test files that exist + /// </summary> + private static void DeleteTestFiles() + { + ArrayList alFiles = GetExistingFiles(c_fileName); + alFiles.AddRange(GetExistingFiles(c_fileName, true)); + foreach (string sFile in alFiles) + { + try + { + Debug.WriteLine("Deleting test file " + sFile); + File.Delete(sFile); + } + catch (Exception ex) + { + Debug.WriteLine("Exception while deleting test file " + ex); + } + } + } - /// <summary> - /// Returns the number of bytes logged per message, including - /// any newline characters in addition to the message length. - /// </summary> - /// <param name="sMessage"></param> - /// <returns></returns> - private static int TotalMessageLength(string sMessage) - { - return sMessage.Length + s_Newline_Length; - } - - /// <summary> - /// Determines how many messages of a fixed length can be logged - /// to a single file before the file rolls. - /// </summary> - /// <param name="iMessageLength"></param> - /// <returns></returns> - private static int MessagesPerFile(int iMessageLength) - { - int iMessagesPerFile = c_iMaximumFileSize / iMessageLength; - - // - // RollingFileAppender checks for wrap BEFORE logging, - // so we will actually get one more message per file than - // we would otherwise. - // - if (iMessagesPerFile * iMessageLength < c_iMaximumFileSize) - { - iMessagesPerFile++; - } - - return iMessagesPerFile; - } - - /// <summary> - /// Determines the name of the current file - /// </summary> - /// <returns></returns> - private static string GetCurrentFile() - { - // Current file name is always the base file name when - // counting. Dates will need a different approach - return c_fileName; - } - - /// <summary> - /// Turns a group of file names into an array of file entries that include the name - /// and a size. This is useful for assigning the properties of backup files, when - /// the length of the files are all the same size due to a fixed message length. - /// </summary> - /// <param name="sBackupGroup"></param> - /// <param name="iBackupFileLength"></param> - /// <returns></returns> - private static RollFileEntry[] MakeBackupFileEntriesFromBackupGroup(string sBackupGroup, int iBackupFileLength) - { - string[] sFiles = sBackupGroup.Split(' '); - - ArrayList alEntries = new ArrayList(); - - for(int i = 0; i < sFiles.Length; i++) - { - // Weed out any whitespace entries from the array - if (sFiles[i].Trim().Length > 0) - { - alEntries.Add(new RollFileEntry(sFiles[i], iBackupFileLength)); - } - } - - return (RollFileEntry[])alEntries.ToArray(typeof(RollFileEntry)); - } - - /// <summary> - /// Finds the iGroup group in the string (comma separated groups) - /// </summary> - /// <param name="sBackupGroups"></param> - /// <param name="iGroup"></param> - /// <returns></returns> - private static string GetBackupGroup(string sBackupGroups, int iGroup) - { - string[] sGroups = sBackupGroups.Split(','); - return sGroups[iGroup]; - } - - ///// <summary> - ///// Builds a collection of file entries based on the file names - ///// specified in a groups string and the max file size from the - ///// stats object - ///// </summary> - ///// <param name="sBackupGroups"></param> - ///// <param name="stats"></param> - ///// <returns></returns> - //private RollFileEntry[] MakeBackupFileEntriesForPreCondition( string sBackupGroups, RollingStats stats ) - //{ - // if (0 == stats.NumberOfFileRolls ) - // { - // return null; // first round has no previous backups - // } - // string sGroup; - // if (0 == stats.MessagesThisFile ) - // { - // // first file has special pattern...since rolling doesn't occur when message - // // is logged, rather before next message is logged. - // if (stats.NumberOfFileRolls <= 1 ) - // { - // return null; - // } - // // Use backup files from previous round. The minus 2 is because we have already - // // rolled, and the first round uses null instead of the string - // sGroup = GetBackupGroup( sBackupGroups, stats.NumberOfFileRolls-2 ); - // } - // else - // { - // sGroup = GetBackupGroup( sBackupGroups, stats.NumberOfFileRolls-1 ); - // } - // return MakeBackupFileEntriesFromBackupGroup( sGroup, stats.MaximumFileSize ); - //} - - /// <summary> - /// Builds a collection of file entries based on the file names - /// specified in a groups string and the max file size from the - /// stats object - /// </summary> - /// <param name="sBackupGroups"></param> - /// <param name="stats"></param> - /// <returns></returns> - private static RollFileEntry[] MakeBackupFileEntriesForPostCondition(string sBackupGroups, RollingStats stats) - { - if (0 == stats.NumberOfFileRolls) - { - return null; // first round has no previous backups - } - string sGroup = GetBackupGroup(sBackupGroups, stats.NumberOfFileRolls - 1); - return MakeBackupFileEntriesFromBackupGroup(sGroup, stats.MaximumFileSize); - } - - - /// <summary> - /// This class holds information that is used while we are generating - /// test data sets - /// </summary> - public class RollingStats - { - private int iTotalMessageLength; - private int iMessagesPerFile; - private int iMessagesThisFile; - private int iNumberOfFileRolls; - - /// <summary> - /// Number of total bytes a log file can reach. - /// </summary> - public int MaximumFileSize - { - get { return TotalMessageLength * MessagesPerFile; } - } - - /// <summary> - /// The length of a message, including any CR/LF characters. - /// This length assumes all messages are a fixed length for - /// test purposes. - /// </summary> - public int TotalMessageLength - { - get { return iTotalMessageLength; } - set { iTotalMessageLength = value; } - } - - /// <summary> - /// A count of the number of messages that are logged to each - /// file. - /// </summary> - public int MessagesPerFile - { - get { return iMessagesPerFile; } - set { iMessagesPerFile = value; } - } - - /// <summary> - /// Counts how many messages have been logged to the current file - /// </summary> - public int MessagesThisFile - { - get { return iMessagesThisFile; } - set { iMessagesThisFile = value; } - } - - /// <summary> - /// Counts how many times a file roll has occurred - /// </summary> - public int NumberOfFileRolls - { - get { return iNumberOfFileRolls; } - set { iNumberOfFileRolls = value; } - } - } - - /// <summary> - /// The stats are used to keep track of progress while we are algorithmically - /// generating a table of pre/post condition tests for file rolling. - /// </summary> - /// <param name="sTestMessage"></param> - /// <returns></returns> - private static RollingStats InitializeStats(string sTestMessage) - { - RollingStats rollingStats = new RollingStats(); - - rollingStats.TotalMessageLength = TotalMessageLength(sTestMessage); - rollingStats.MessagesPerFile = MessagesPerFile(rollingStats.TotalMessageLength); - rollingStats.MessagesThisFile = 0; - rollingStats.NumberOfFileRolls = 0; - - return rollingStats; - } - - /// <summary> - /// Takes an existing array of RollFileEntry objects, creates a new array one element - /// bigger, and appends the final element to the end. If the existing entries are - /// null (no entries), then a one-element array is returned with the final element - /// as the only entry. - /// </summary> - /// <param name="existing"></param> - /// <param name="final"></param> - /// <returns></returns> - private static RollFileEntry[] AddFinalElement(RollFileEntry[] existing, RollFileEntry final) - { - int iLength = 1; - if (null != existing) - { - iLength += existing.Length; - } - RollFileEntry[] combined = new RollFileEntry[iLength]; - if (null != existing) - { - Array.Copy(existing, 0, combined, 0, existing.Length); - } - combined[iLength - 1] = final; - return combined; - } - - /// <summary> - /// Generates the pre and post condition arrays from an array of backup files and the - /// current file / next file. - /// </summary> - /// <param name="sBackupFiles"></param> - /// <param name="preCondition"></param> - /// <param name="current"></param> - /// <param name="currentNext"></param> - /// <param name="rollingStats"></param> - /// <returns></returns> - private static RollConditions BuildTableEntry(string sBackupFiles, RollConditions preCondition, RollFileEntry current, RollFileEntry currentNext, RollingStats rollingStats) - { - RollFileEntry[] backupsPost = MakeBackupFileEntriesForPostCondition(sBackupFiles, rollingStats); - RollFileEntry[] post = AddFinalElement(backupsPost, currentNext); - if (null == preCondition) - { - return new RollConditions(AddFinalElement(null, current), post); - } - return new RollConditions(preCondition.GetPostLogFileEntries(), post); - } - - /// <summary> - /// Returns a RollFileEntry that represents the next state of the current file, - /// based on the current state. When the current state would roll, the next - /// entry is the current file wrapped to 0 bytes. Otherwise, the next state - /// is the post-condition passed in as the currentNext parameter - /// </summary> - /// <param name="rollingStats"></param> - /// <param name="currentNext"></param> - /// <returns></returns> - private static RollFileEntry MoveNextEntry(RollingStats rollingStats, RollFileEntry currentNext) - { - rollingStats.MessagesThisFile = rollingStats.MessagesThisFile + 1; - if (rollingStats.MessagesThisFile >= rollingStats.MessagesPerFile) - { - rollingStats.MessagesThisFile = 0; - rollingStats.NumberOfFileRolls = rollingStats.NumberOfFileRolls + 1; - - return new RollFileEntry(GetCurrentFile(), 0); - } - else - { - return currentNext; - } - } - - /// <summary> - /// Callback point for the regular expression parser. Turns - /// the number into a file name. - /// </summary> - /// <param name="match"></param> - /// <returns></returns> - private static string NumberedNameMaker(Match match) - { - Int32 iValue = Int32.Parse(match.Value); - return MakeFileName(c_fileName, iValue); - } - - /// <summary> - /// Parses a numeric list of files, turning them into file names. - /// Calls back to a method that does the actual replacement, turning - /// the numeric value into a filename. - /// </summary> - /// <param name="sBackupInfo"></param> - /// <param name="evaluator"></param> - /// <returns></returns> - private static string ConvertToFiles(string sBackupInfo, MatchEvaluator evaluator) - { - Regex regex = new Regex(@"\d+"); - return regex.Replace(sBackupInfo, evaluator); - } - - /// <summary> - /// Makes test entries used for verifying counted file names - /// </summary> - /// <param name="sTestMessage">A message to log repeatedly</param> - /// <param name="sBackupInfo">Filename groups used to indicate backup file name progression - /// that results after each message is logged</param> - /// <param name="iMessagesToLog">How many times the test message will be repeatedly logged</param> - /// <returns></returns> - private static RollConditions[] MakeNumericTestEntries(string sTestMessage, string sBackupInfo, int iMessagesToLog) - { - return MakeTestEntries( - sTestMessage, - sBackupInfo, - iMessagesToLog, - new MatchEvaluator(NumberedNameMaker)); - } - - /// <summary> - /// This routine takes a list of backup file names and a message that will be logged - /// repeatedly, and generates a collection of objects containing pre-condition and - /// post-condition information. This pre/post information shows the names and expected - /// file sizes for all files just before and just after a message is logged. - /// </summary> - /// <param name="sTestMessage">A message to log repeatedly</param> - /// <param name="sBackupInfo">Filename groups used to indicate backup file name progression - /// that results after each message is logged</param> - /// <param name="iMessagesToLog">How many times the test message will be repeatedly logged</param> - /// <param name="evaluator">Function that can turn a number into a filename</param> - /// <returns></returns> - private static RollConditions[] MakeTestEntries(string sTestMessage, string sBackupInfo, int iMessagesToLog, MatchEvaluator evaluator) - { - string sBackupFiles = ConvertToFiles(sBackupInfo, evaluator); - - RollConditions[] table = new RollConditions[iMessagesToLog]; - - RollingStats rollingStats = InitializeStats(sTestMessage); - - RollConditions preCondition = null; - rollingStats.MessagesThisFile = 0; - - RollFileEntry currentFile = new RollFileEntry(GetCurrentFile(), 0); - for(int i = 0; i < iMessagesToLog; i++) - { - RollFileEntry currentNext = new RollFileEntry( - GetCurrentFile(), - (1 + rollingStats.MessagesThisFile) * rollingStats.TotalMessageLength); - - table[i] = BuildTableEntry(sBackupFiles, preCondition, currentFile, currentNext, rollingStats); - preCondition = table[i]; - - //System.Diagnostics.Debug.WriteLine( "Message " + i ); - //DumpTableEntry( table[i] ); - - currentFile = MoveNextEntry(rollingStats, currentNext); - } - - return table; - } - - /// <summary> - /// Uses the externally defined rolling table to verify rolling names/sizes - /// </summary> - /// <remarks> - /// Pattern is: check pre-conditions. Log messages, checking size of current file. - /// when size exceeds limit, check post conditions. Can determine from message the - /// number of messages N that will cause a roll to occur. Challenge is to verify the - /// expected files, their sizes, and the names. For a message of length L, the backups - /// will be of size (N * L), and the current file will be of size (K * L), where K is - /// the number of messages that have been logged to this file. - /// - /// File sizes can be checked algorithmically. - /// - /// File names are generated using a table driven algorithm, where a number is turned into - /// the actual filename. - /// - /// The entries are comma-separated, with spaces between the names. Each comma indicates - /// a 'roll', and the group between commas indicates the numbers for all backup files that - /// occur as a result of the roll. It is assumed that no backup files exist before a roll - /// occurs - /// </remarks> - /// <param name="table"></param> - private void VerifyRolling(RollConditions[] table) - { - ConfigureRootAppender(); - RollFromTableEntries(c_fileName, table, GetTestMessage()); - } - - /// <summary> - /// Validates rolling using a fixed number of backup files, with - /// count direction set to up, so that newer files have higher counts. - /// Newest = N, Oldest = N-K, where K is the number of backups to allow - /// and N is the number of times rolling has occurred. - /// </summary> - [Test] - public void TestRollingCountUpFixedBackups() - { - // - // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the - // oldest, and 3 is the newest - // - string sBackupInfo = "1, 1 2, 1 2 3, 2 3 4, 3 4 5"; - - // - // Count Up - // - _iCountDirection = +1; - - // - // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 - // bytes for current file as messages are logged. - // - int iMessagesToLog = 30; - - VerifyRolling(MakeNumericTestEntries(GetTestMessage(), sBackupInfo, iMessagesToLog)); - } - - /// <summary> - /// Validates rolling using an infinite number of backup files, with - /// count direction set to up, so that newer files have higher counts. - /// Newest = N, Oldest = 1, where N is the number of times rolling has - /// occurred. - /// </summary> - [Test] - public void TestRollingCountUpInfiniteBackups() - { - // - // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the - // oldest, and 3 is the newest - // - string sBackupInfo = "1, 1 2, 1 2 3, 1 2 3 4, 1 2 3 4 5"; - - // - // Count Up - // - _iCountDirection = +1; - - // - // Infinite backups - // - _MaxSizeRollBackups = -1; - - // - // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 - // bytes for current file as messages are logged. - // - int iMessagesToLog = 30; - - VerifyRolling(MakeNumericTestEntries(GetTestMessage(), sBackupInfo, iMessagesToLog)); - } - - /// <summary> - /// Validates rolling with no backup files, with count direction set to up. - /// Only the current file should be present, wrapping to 0 bytes once the - /// previous file fills up. - /// </summary> - [Test] - public void TestRollingCountUpZeroBackups() - { - // - // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the - // oldest, and 3 is the newest - // - string sBackupInfo = ", , , , "; - - // - // Count Up - // - _iCountDirection = +1; - - // - // No backups - // - _MaxSizeRollBackups = 0; - - // - // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 - // bytes for current file as messages are logged. - // - int iMessagesToLog = 30; - - VerifyRolling(MakeNumericTestEntries(GetTestMessage(), sBackupInfo, iMessagesToLog)); - } - - - /// <summary> - /// Validates rolling using a fixed number of backup files, with - /// count direction set to down, so that older files have higher counts. - /// Newest = 1, Oldest = N, where N is the number of backups to allow - /// </summary> - [Test] - public void TestRollingCountDownFixedBackups() - { - // - // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the - // oldest, and 3 is the newest - // - string sBackupInfo = "1, 1 2, 1 2 3, 1 2 3, 1 2 3"; - - // - // Count Up - // - _iCountDirection = -1; - - // - // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 - // bytes for current file as messages are logged. - // - int iMessagesToLog = 30; - - VerifyRolling(MakeNumericTestEntries(GetTestMessage(), sBackupInfo, iMessagesToLog)); - } - - /// <summary> - /// Validates rolling using an infinite number of backup files, with - /// count direction set to down, so that older files have higher counts. - /// Newest = 1, Oldest = N, where N is the number of times rolling has - /// occurred - /// </summary> - [Test] - public void TestRollingCountDownInfiniteBackups() - { - // - // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the - // oldest, and 3 is the newest - // - string sBackupInfo = "1, 1 2, 1 2 3, 1 2 3 4, 1 2 3 4 5"; - - // - // Count Down - // - _iCountDirection = -1; - - // - // Infinite backups - // - _MaxSizeRollBackups = -1; - - // - // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 - // bytes for current file as messages are logged. - // - int iMessagesToLog = 30; - - VerifyRolling(MakeNumericTestEntries(GetTestMessage(), sBackupInfo, iMessagesToLog)); - } - - /// <summary> - /// Validates rolling with no backup files, with count direction set to down. - /// Only the current file should be present, wrapping to 0 bytes once the - /// previous file fills up. - /// </summary> - [Test] - public void TestRollingCountDownZeroBackups() - { - // - // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the - // oldest, and 3 is the newest - // - string sBackupInfo = ", , , , "; - - // - // Count Up - // - _iCountDirection = -1; - - // - // No backups - // - _MaxSizeRollBackups = 0; - - // - // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 - // bytes for current file as messages are logged. - // - int iMessagesToLog = 30; - - VerifyRolling(MakeNumericTestEntries(GetTestMessage(), sBackupInfo, iMessagesToLog)); - } - - /// <summary> - /// Configures the root appender for counting and rolling - /// </summary> - private void ConfigureRootAppender() - { - _root = ((Repository.Hierarchy.Hierarchy)Utils.GetRepository()).Root; - _root.Level = Level.Debug; - _caRoot = new CountingAppender(); - _root.AddAppender(_caRoot); - Assert.AreEqual(_caRoot.Counter, 0); - - // - // Set the root appender with a RollingFileAppender - // - _root.AddAppender(CreateAppender()); - - _root.Repository.Configured = true; - } - - /// <summary> - /// Verifies that the current backup index is detected correctly when initializing - /// </summary> - /// <param name="sBaseFile"></param> - /// <param name="alFiles"></param> - /// <param name="iExpectedCurSizeRollBackups"></param> - private static void VerifyInitializeRollBackupsFromBaseFile(string sBaseFile, ArrayList alFiles, int iExpectedCurSizeRollBackups) - { - InitializeAndVerifyExpectedValue(alFiles, sBaseFile, CreateRollingFileAppender("5,0,1"), iExpectedCurSizeRollBackups); - } - - /// <summary> - /// Tests that the current backup index is 0 when no - /// existing files are seen - /// </summary> - [Test] - public void TestInitializeRollBackups1() - { - string sBaseFile = "LogFile.log"; - ArrayList arrFiles = new ArrayList(); - arrFiles.Add("junk1"); - arrFiles.Add("junk1.log"); - arrFiles.Add("junk2.log"); - arrFiles.Add("junk.log.1"); - arrFiles.Add("junk.log.2"); - - int iExpectedCurSizeRollBackups = 0; - VerifyInitializeRollBackupsFromBaseFile(sBaseFile, arrFiles, iExpectedCurSizeRollBackups); - } - - /// <summary> - /// Verifies that files are detected when the base file is specified - /// </summary> - /// <param name="sBaseFile"></param> - private static void VerifyInitializeRollBackupsFromBaseFile(string sBaseFile) - { - ArrayList alFiles = MakeTestDataFromString(sBaseFile, "0,1,2"); - - int iExpectedCurSizeRollBackups = 2; - VerifyInitializeRollBackupsFromBaseFile(sBaseFile, alFiles, iExpectedCurSizeRollBackups); - } - - /// <summary> - /// Verifies that count goes to the highest when counting up - /// </summary> - [Test] - public void TestInitializeCountUpFixed() - { - ArrayList alFiles = MakeTestDataFromString("3,4,5"); - int iExpectedValue = 5; - InitializeAndVerifyExpectedValue(alFiles, c_fileName, CreateRollingFileAppender("3,0,1"), iExpectedValue); - } - - /// <summary> - /// Verifies that count goes to the highest when counting up - /// </summary> - [Test] - public void TestInitializeCountUpFixed2() - { - ArrayList alFiles = MakeTestDataFromString("0,3"); - int iExpectedValue = 3; - InitializeAndVerifyExpectedValue(alFiles, c_fileName, CreateRollingFileAppender("3,0,1"), iExpectedValue); - } - - /// <summary> - /// Verifies that count stays at 0 for the zero backups case - /// when counting up - /// </summary> - [Test] - public void TestInitializeCountUpZeroBackups() - { - ArrayList alFiles = MakeTestDataFromString("0,3"); - int iExpectedValue = 0; - InitializeAndVerifyExpectedValue(alFiles, c_fileName, CreateRollingFileAppender("0,0,1"), iExpectedValue); - } - - /// <summary> - /// Verifies that count stays at 0 for the zero backups case - /// when counting down - /// </summary> - [Test] - public void TestInitializeCountDownZeroBackups() - { - ArrayList alFiles = MakeTestDataFromString("0,3"); - int iExpectedValue = 0; - InitializeAndVerifyExpectedValue(alFiles, c_fileName, CreateRollingFileAppender("0,0,-1"), iExpectedValue); - } - - - /// <summary> - /// Verifies that count goes to the highest when counting up - /// </summary> - [Test] - public void TestInitializeCountDownFixed() - { - ArrayList alFiles = MakeTestDataFromString("4,5,6"); - VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 0); - } - - /// <summary> - /// Verifies that count goes to the highest when counting up - /// </summary> - [Test] - public void TestInitializeCountDownFixed2() - { - ArrayList alFiles = MakeTestDataFromString("1,5,6"); - VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 1); - } - - /// <summary> - /// Verifies that count goes to the highest when counting up - /// </summary> - [Test] - public void TestInitializeCountDownFixed3() - { - ArrayList alFiles = MakeTestDataFromString("2,5,6"); - VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 2); - } - - /// <summary> - /// Verifies that count goes to the highest when counting up - /// </summary> - [Test] - public void TestInitializeCountDownFixed4() - { - ArrayList alFiles = MakeTestDataFromString("3,5,6"); - VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 3); - } - - /// <summary> - /// Verifies that count goes to the highest when counting up - /// </summary> - [Test] - public void TestInitializeCountDownFixed5() - { - ArrayList alFiles = MakeTestDataFromString("1,2,3"); - VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 3); - } - - /// <summary> - /// Verifies that count goes to the highest when counting up - /// </summary> - [Test] - public void TestInitializeCountDownFixed6() - { - ArrayList alFiles = MakeTestDataFromString("1,2"); - VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 2); - } - - /// <summary> - /// Verifies that count goes to the highest when counting up - /// </summary> - [Test] - public void TestInitializeCountDownFixed7() - { - ArrayList alFiles = MakeTestDataFromString("2,3"); - VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 3); - } - - private static void InitializeAndVerifyExpectedValue(ArrayList alFiles, string sBaseFile, RollingFileAppender rfa, int iExpectedValue) - { - InitializeRollBackups(rfa, sBaseFile, alFiles); - Assert.AreEqual(iExpectedValue, GetFieldCurSizeRollBackups(rfa)); - } - - /// <summary> - /// Tests the count down case, with infinite max backups, to see that - /// initialization of the rolling file appender results in the expected value - /// </summary> - /// <param name="alFiles"></param> - /// <param name="sBaseFile"></param> - /// <param name="iExpectedValue"></param> - private static void VerifyInitializeDownInfiniteExpectedValue(ArrayList alFiles, string sBaseFile, int iExpectedValue) - { - InitializeAndVerifyExpectedValue(alFiles, sBaseFile, CreateRollingFileAppender("-1,0,-1"), iExpectedValue); - } - - /// <summary> - /// Creates a RollingFileAppender with the desired values, where the - /// values are passed as a comma separated string, with 3 parameters, - /// m_maxSizeRollBackups, m_curSizeRollBackups, CountDirection - /// </summary> - /// <param name="sParams"></param> - /// <returns></returns> - private static RollingFileAppender CreateRollingFileAppender(string sParams) - { - string[] asParams = sParams.Split(','); - if (null == asParams || asParams.Length != 3) - { - throw new ArgumentOutOfRangeException(sParams, sParams, "Must have 3 comma separated params: MaxSizeRollBackups, CurSizeRollBackups, CountDirection"); - } - - RollingFileAppender rfa = new RollingFileAppender(); - rfa.RollingStyle = RollingFileAppender.RollingMode.Size; - SetFieldMaxSizeRollBackups(rfa, Int32.Parse(asParams[0].Trim())); - SetFieldCurSizeRollBackups(rfa, Int32.Parse(asParams[1].Trim())); - rfa.CountDirection = Int32.Parse(asParams[2].Trim()); - - return rfa; - } - - /// <summary> - /// Verifies that count goes to the highest when counting down - /// and infinite backups are selected - /// </summary> - [Test] - public void TestInitializeCountDownInfinite() - { - ArrayList alFiles = MakeTestDataFromString("2,3"); - VerifyInitializeDownInfiniteExpectedValue(alFiles, c_fileName, 3); - } - - /// <summary> - /// Verifies that count goes to the highest when counting down - /// and infinite backups are selected - /// </summary> - [Test] - public void TestInitializeCountDownInfinite2() - { - ArrayList alFiles = MakeTestDataFromString("2,3,4,5,6,7,8,9,10"); - VerifyInitializeDownInfiniteExpectedValue(alFiles, c_fileName, 10); - } - - /// <summary> - /// Verifies that count goes to the highest when counting down - /// and infinite backups are selected - /// </summary> - [Test] - public void TestInitializeCountDownInfinite3() - { - ArrayList alFiles = MakeTestDataFromString("9,10,3,4,5,7,9,6,1,2,8"); - VerifyInitializeDownInfiniteExpectedValue(alFiles, c_fileName, 10); - } - - /// <summary> - /// Verifies that count goes to the highest when counting up - /// and infinite backups are selected - /// </summary> - [Test] - public void TestInitializeCountUpInfinite() - { - ArrayList alFiles = MakeTestDataFromString("2,3"); - VerifyInitializeUpInfiniteExpectedValue(alFiles, c_fileName, 3); - } - - /// <summary> - /// Verifies that count goes to the highest when counting up - /// and infinite backups are selected - /// </summary> - [Test] - public void TestInitializeCountUpInfinite2() - { - ArrayList alFiles = MakeTestDataFromString("2,3,4,5,6,7,8,9,10"); - VerifyInitializeUpInfiniteExpectedValue(alFiles, c_fileName, 10); - } - - /// <summary> - /// Verifies that count goes to the highest when counting up - /// and infinite backups are selected - /// </summary> - [Test] - public void TestInitializeCountUpInfinite3() - { - ArrayList alFiles = MakeTestDataFromString("9,10,3,4,5,7,9,6,1,2,8"); - VerifyInitializeUpInfiniteExpectedValue(alFiles, c_fileName, 10); - } - - /// <summary> - /// Creates a logger hierarchy, configures a rolling file appender and returns an ILogger - /// </summary> - /// <param name="filename">The filename to log to</param> - /// <param name="lockModel">The locking model to use.</param> - /// <param name="handler">The error handler to use.</param> - /// <returns>A configured ILogger</returns> - private static ILogger CreateLogger(string filename, FileAppender.LockingModelBase lockModel, IErrorHandler handler) - { - return CreateLogger(filename, lockModel, handler, 100000, 0); - } - - /// <summary> - /// Creates a logger hierarchy, configures a rolling file appender and returns an ILogger - /// </summary> - /// <param name="filename">The filename to log to</param> - /// <param name="lockModel">The locking model to use.</param> - /// <param name="handler">The error handler to use.</param> - /// <param name="maxFileSize">Maximum file size for roll</param> - /// <param name="maxSizeRollBackups">Maximum number of roll backups</param> - /// <returns>A configured ILogger</returns> - private static ILogger CreateLogger(string filename, FileAppender.LockingModelBase lockModel, IErrorHandler handler, int maxFileSize, int maxSizeRollBackups) - { - Repository.Hierarchy.Hierarchy h = (Repository.Hierarchy.Hierarchy)LogManager.CreateRepository("TestRepository"); - - RollingFileAppender appender = new RollingFileAppender(); - appender.File = filename; - appender.AppendToFile = false; - appender.CountDirection = 0; - appender.RollingStyle = RollingFileAppender.RollingMode.Size; - appender.MaxFileSize = maxFileSize; - appender.Encoding = Encoding.ASCII; - appender.ErrorHandler = handler; - appender.MaxSizeRollBackups = maxSizeRollBackups; - if (lockModel != null) - { - appender.LockingModel = lockModel; - } - - PatternLayout layout = new PatternLayout(); - layout.ConversionPattern = "%m%n"; - layout.ActivateOptions(); - - appender.Layout = layout; - appender.ActivateOptions(); - - h.Root.AddAppender(appender); - h.Configured = true; - - ILogger log = h.GetLogger("Logger"); - return log; - } - - /// <summary> - /// Destroys the logger hierarchy created by <see cref="RollingFileAppenderTest.CreateLogger"/> - /// </summary> - private static void DestroyLogger() - { - Repository.Hierarchy.Hierarchy h = (Repository.Hierarchy.Hierarchy)LogManager.GetRepository("TestRepository"); - h.ResetConfiguration(); - //Replace the repository selector so that we can recreate the hierarchy with the same name if necessary - LoggerManager.RepositorySelector = new DefaultRepositorySelector(typeof(log4net.Repository.Hierarchy.Hierarchy)); - } - - private static void AssertFileEquals(string filename, string contents) + ///// <summary> + ///// Generates a file name associated with the count. + ///// </summary> + ///// <param name="iFileCount"></param> + ///// <returns></returns> + //private string MakeFileName(int iFileCount) + //{ + // return MakeFileName(_fileName, iFileCount); + //} + + /// <summary> + /// Generates a file name associated with the count, using + /// the base file name. + /// </summary> + /// <param name="sBaseFile"></param> + /// <param name="iFileCount"></param> + /// <returns></returns> + private static string MakeFileName(string sBaseFile, int iFileCount) { - FileInfo fileinfo = new FileInfo(filename); - StreamReader sr = new StreamReader(fileinfo.OpenRead()); - string logcont = sr.ReadToEnd(); - sr.Close(); - - Assert.AreEqual(contents, logcont, "Log contents is not what is expected"); - - File.Delete(filename); - } - - /// <summary> - /// Verifies that logging a message actually produces output - /// </summary> - [Test] - public void TestLogOutput() - { - string filename ="test_simple.log"; - SilentErrorHandler sh = new SilentErrorHandler(); - ILogger log = CreateLogger(filename, new FileAppender.ExclusiveLock(), sh); - log.Log(GetType(), Level.Info, "This is a message", null); - log.Log(GetType(), Level.Info, "This is a message 2", null); - DestroyLogger(); - - AssertFileEquals(filename, "This is a message" + Environment.NewLine + "This is a message 2" + Environment.NewLine); - Assert.AreEqual("", sh.Message, "Unexpected error message"); - } - - /// <summary> - /// Verifies that attempting to log to a locked file fails gracefully - /// </summary> - [Test] - public void TestExclusiveLockFails() - { - string filename ="test_exclusive_lock_fails.log"; - - FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); - fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); - - SilentErrorHandler sh = new SilentErrorHandler(); - ILogger log = CreateLogger(filename, new FileAppender.ExclusiveLock(), sh); - log.Log(GetType(), Level.Info, "This is a message", null); - log.Log(GetType(), Level.Info, "This is a message 2", null); - DestroyLogger(); - fs.Close(); - - AssertFileEquals(filename, "Test"); - Assert.AreEqual(sh.Message.Substring(0, 30), "Unable to acquire lock on file", "Expecting an error message"); - } - - /// <summary> - /// Verifies that attempting to log to a locked file recovers if the lock is released - /// </summary> - [Test] - public void TestExclusiveLockRecovers() - { - string filename ="test_exclusive_lock_recovers.log"; - - FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); - fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); - - SilentErrorHandler sh = new SilentErrorHandler(); - ILogger log = CreateLogger(filename, new FileAppender.ExclusiveLock(), sh); - log.Log(GetType(), Level.Info, "This is a message", null); - fs.Close(); - log.Log(GetType(), Level.Info, "This is a message 2", null); - DestroyLogger(); - - AssertFileEquals(filename, "This is a message 2" + Environment.NewLine); - Assert.AreEqual("Unable to acquire lock on file", sh.Message.Substring(0, 30), "Expecting an error message"); - } - - /// <summary> - /// Verifies that attempting to log to a file with ExclusiveLock really locks the file - /// </summary> - [Test] - public void TestExclusiveLockLocks() - { - string filename ="test_exclusive_lock_locks.log"; - bool locked = false; - - SilentErrorHandler sh = new SilentErrorHandler(); - ILogger log = CreateLogger(filename, new FileAppender.ExclusiveLock(), sh); - log.Log(GetType(), Level.Info, "This is a message", null); - - try - { - FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); - fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); - fs.Close(); - } - catch(IOException e1) - { -#if MONO - Assert.AreEqual("Sharing violation on path ", e1.Message.Substring(0, 26), "Unexpected exception"); -#else - Assert.AreEqual("The process cannot access the file ", e1.Message.Substring(0, 35), "Unexpected exception"); -#endif - locked = true; - } + if (0 == iFileCount) + { + return sBaseFile; + } - log.Log(GetType(), Level.Info, "This is a message 2", null); - DestroyLogger(); + return sBaseFile + "." + iFileCount; + } - Assert.IsTrue(locked, "File was not locked"); -#if !MONO || MONO_3_5 || MONO_4_0 // at least on Linux with Mono 2.4 exclusive locking doesn't work as one would expect - AssertFileEquals(filename, "This is a message" + Environment.NewLine + "This is a message 2" + Environment.NewLine); -#endif - Assert.AreEqual("", sh.Message, "Unexpected error message"); - } - - - /// <summary> - /// Verifies that attempting to log to a locked file fails gracefully - /// </summary> - [Test] - public void TestMinimalLockFails() - { - string filename ="test_minimal_lock_fails.log"; - - FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); - fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); - - SilentErrorHandler sh = new SilentErrorHandler(); - ILogger log = CreateLogger(filename, new FileAppender.MinimalLock(), sh); - log.Log(GetType(), Level.Info, "This is a message", null); - log.Log(GetType(), Level.Info, "This is a message 2", null); - DestroyLogger(); - fs.Close(); - - AssertFileEquals(filename, "Test"); - Assert.AreEqual("Unable to acquire lock on file", sh.Message.Substring(0, 30), "Expecting an error message"); - } - - /// <summary> - /// Verifies that attempting to log to a locked file recovers if the lock is released - /// </summary> - [Test] - public void TestMinimalLockRecovers() - { - string filename ="test_minimal_lock_recovers.log"; - - FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); - fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); - - SilentErrorHandler sh = new SilentErrorHandler(); - ILogger log = CreateLogger(filename, new FileAppender.MinimalLock(), sh); - log.Log(GetType(), Level.Info, "This is a message", null); - fs.Close(); - log.Log(GetType(), Level.Info, "This is a message 2", null); - DestroyLogger(); - - AssertFileEquals(filename, "This is a message 2" + Environment.NewLine); - Assert.AreEqual("Unable to acquire lock on file", sh.Message.Substring(0, 30), "Expecting an error message"); - } - - /// <summary> - /// Verifies that attempting to log to a file with MinimalLock doesn't lock the file - /// </summary> - [Test] - public void TestMinimalLockUnlocks() - { - string filename ="test_minimal_lock_unlocks.log"; - bool locked; - - SilentErrorHandler sh = new SilentErrorHandler(); - ILogger log = CreateLogger(filename, new FileAppender.MinimalLock(), sh); - log.Log(GetType(), Level.Info, "This is a message", null); - - locked = true; - FileStream fs = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.None); - fs.Write(Encoding.ASCII.GetBytes("Test" + Environment.NewLine), 0, 4 + Environment.NewLine.Length); - fs.Close(); - - log.Log(GetType(), Level.Info, "This is a message 2", null); - DestroyLogger(); - - Assert.IsTrue(locked, "File was not locked"); - AssertFileEquals(filename, "This is a message" + Environment.NewLine + "Test" + Environment.NewLine + "This is a message 2" + Environment.NewLine); - Assert.AreEqual("", sh.Message, "Unexpected error message"); - } + /// <summary> + /// Returns a RollingFileAppender using all the internal settings for maximum + /// file size and number of backups + /// </summary> + /// <returns></returns> + private RollingFileAppender CreateAppender() + { + return CreateAppender(new FileAppender.ExclusiveLock()); + } -#if !NETCF /// <summary> - /// Verifies that attempting to log to a locked file fails gracefully + /// Returns a RollingFileAppender using all the internal settings for maximum + /// file size and number of backups /// </summary> - [Test] - public void TestInterProcessLockFails() { - string filename ="test_interprocess_lock_fails.log"; + /// <param name="lockModel">The locking model to test</param> + /// <returns></returns> + private RollingFileAppender CreateAppender(FileAppender.LockingModelBase lockModel) + { + // + // Use a basic pattern that + // includes just the message and a CR/LF. + // + PatternLayout layout = new PatternLayout("%m%n"); + + // + // Create the new appender + // + RollingFileAppender appender = new RollingFileAppender(); + appender.Layout = layout; + appender.File = c_fileName; + appender.Encoding = Encoding.ASCII; + appender.MaximumFileSize = c_iMaximumFileSize.ToString(); + appender.MaxSizeRollBackups = _MaxSizeRollBackups; + appender.CountDirection = _iCountDirection; + appender.RollingStyle = RollingFileAppender.RollingMode.Size; + appender.LockingModel = lockModel; + + appender.ActivateOptions(); + + return appender; + } - FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); - fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); + /// <summary> + /// Used for test purposes, a table of these objects can be used to identify + /// any existing files and their expected length. + /// </summary> + public class RollFileEntry + { + /// <summary> + /// Stores the name of the file + /// </summary> + private string m_fileName; + + /// <summary> + /// The expected length of the file + /// </summary> + private long m_fileLength; + + /// <summary> + /// Default constructor + /// </summary> + public RollFileEntry() + { + } - SilentErrorHandler sh = new SilentErrorHandler(); - ILogger log = CreateLogger(filename, new FileAppender.InterProcessLock(), sh); - log.Log(GetType(), Level.Info, "This is a message", null); - log.Log(GetType(), Level.Info, "This is a message 2", null); - DestroyLogger(); - fs.Close(); + /// <summary> + /// Constructor used when the fileInfo and expected length are known + /// </summary> + /// <param name="fileName"></param> + /// <param name="fileLength"></param> + public RollFileEntry(string fileName, long fileLength) + { + m_fileName = fileName; + m_fileLength = fileLength; + } - AssertFileEquals(filename, "Test"); - Assert.AreEqual("Unable to acquire lock on file", sh.Message.Substring(0, 30), "Expecting an error message"); + /// <summary> + /// Stores the name of the file + /// </summary> + public string FileName + { + get { return m_fileName; } + } + + /// <summary> + /// The expected length of the file + /// </summary> + public long FileLength + { + get { return m_fileLength; } + } } /// <summary> - /// Verifies that attempting to log to a locked file recovers if the lock is released + /// Used for table-driven testing. This class holds information that can be used + /// for testing of file rolling. /// </summary> - [Test] - public void TestInterProcessLockRecovers() { - string filename ="test_interprocess_lock_recovers.log"; + public class RollConditions + { + /// <summary> + /// A table of entries showing files that should exist and their expected sizes + /// before logging is called + /// </summary> + private RollFileEntry[] m_preLogFileEntries; + + /// <summary> + /// A table of entries showing files that should exist and their expected sizes + /// after a message is logged + /// </summary> + private RollFileEntry[] m_postLogFileEntries; + + /// <summary> + /// Constructor, taking all required parameters + /// </summary> + /// <param name="preLogFileEntries"></param> + /// <param name="postLogFileEntries"></param> + public RollConditions(RollFileEntry[] preLogFileEntries, RollFileEntry[] postLogFileEntries) + { + m_preLogFileEntries = preLogFileEntries; + m_postLogFileEntries = postLogFileEntries; + } - FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); - fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); + /// <summary> + /// A table of entries showing files that should exist and their expected sizes + /// before logging is called + /// </summary> + public RollFileEntry[] GetPreLogFileEntries() + { + return m_preLogFileEntries; + } - SilentErrorHandler sh = new SilentErrorHandler(); - ILogger log = CreateLogger(filename, new FileAppender.InterProcessLock(), sh); - log.Log(GetType(), Level.Info, "This is a message", null); - fs.Close(); - log.Log(GetType(), Level.Info, "This is a message 2", null); - DestroyLogger(); + /// <summary> + /// A table of entries showing files that should exist and their expected sizes + /// after a message is logged + /// </summary> + public RollFileEntry[] GetPostLogFileEntries() + { + return m_postLogFileEntries; + } + } - AssertFileEquals(filename, "This is a message 2" + Environment.NewLine); - Assert.AreEqual("Unable to acquire lock on file", sh.Message.Substring(0, 30), "Expecting an error message"); + private static void VerifyExistenceAndRemoveFromList(ArrayList alExisting, + string sFileName, + FileInfo file, + RollFileEntry entry) + { + Assert.IsTrue(alExisting.Contains(sFileName), "filename {0} not found in test directory", sFileName); + Assert.AreEqual(entry.FileLength, file.Length, "file length mismatch"); + // Remove this file from the list + alExisting.Remove(sFileName); } /// <summary> - /// Verifies that attempting to log to a file with InterProcessLock really locks the file + /// Checks that all the expected files exist, and only the expected files. Also + /// verifies the length of all files against the expected length /// </summary> - [Test] - public void TestInterProcessLockUnlocks() { - string filename ="test_interprocess_lock_unlocks.log"; - bool locked; + /// <param name="sBaseFileName"></param> + /// <param name="fileEntries"></param> + private static void VerifyFileConditions(string sBaseFileName, RollFileEntry[] fileEntries) + { + ArrayList alExisting = GetExistingFiles(sBaseFileName); + if (null != fileEntries) + { + // AssertEquals( "File count mismatch", alExisting.Count, fileEntries.Length ); + foreach (RollFileEntry rollFile in fileEntries) + { + string sFileName = rollFile.FileName; + FileInfo file = new FileInfo(sFileName); - SilentErrorHandler sh = new SilentErrorHandler(); - ILogger log = CreateLogger(filename, new FileAppender.InterProcessLock(), sh); - log.Log(GetType(), Level.Info, "This is a message", null); + if (rollFile.FileLength > 0) + { + Assert.IsTrue(file.Exists, "filename {0} does not exist", sFileName); + VerifyExistenceAndRemoveFromList(alExisting, sFileName, file, rollFile); + } + else + { + // If length is 0, file may not exist yet. If file exists, make sure length + // is zero. If file doesn't exist, this is OK - locked = true; - FileStream fs = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); - fs.Write(Encoding.ASCII.GetBytes("Test" + Environment.NewLine), 0, 4 + Environment.NewLine.Length); - fs.Close(); + if (file.Exists) + { + VerifyExistenceAndRemoveFromList(alExisting, sFileName, file, rollFile); + } + } + } + } + else + { + Assert.AreEqual(0, alExisting.Count); + } - log.Log(GetType(), Level.Info, "This is a message 2", null); - DestroyLogger(); + // This check ensures no extra files matching the wildcard pattern exist. + // We only want the files we expect, and no others + Assert.AreEqual(0, alExisting.Count); + } - Assert.IsTrue(locked, "File was not locked"); - AssertFileEquals(filename, "This is a message" + Environment.NewLine + "Test" + Environment.NewLine + "This is a message 2" + Environment.NewLine); - Assert.AreEqual("", sh.Message, "Unexpected error message"); + /// <summary> + /// Called before logging a message to check that all the expected files exist, + /// and only the expected files. Also verifies the length of all files against + /// the expected length + /// </summary> + /// <param name="sBaseFileName"></param> + /// <param name="entry"></param> + private static void VerifyPreConditions(string sBaseFileName, RollConditions entry) + { + VerifyFileConditions(sBaseFileName, entry.GetPreLogFileEntries()); } - /// <summary> - /// Verifies that rolling file works - /// </summary> - [Test] - public void TestInterProcessLockRoll() - { - string filename ="test_interprocess_lock_roll.log"; + /// <summary> + /// Called after logging a message to check that all the expected files exist, + /// and only the expected files. Also verifies the length of all files against + /// the expected length + /// </summary> + /// <param name="sBaseFileName"></param> + /// <param name="entry"></param> + private static void VerifyPostConditions(string sBaseFileName, RollConditions entry) + { + VerifyFileConditions(sBaseFileName, entry.GetPostLogFileEntries()); + } - SilentErrorHandler sh = new SilentErrorHandler(); - ILogger log = CreateLogger(filename, new FileAppender.InterProcessLock(), sh, 1, 2); + /// <summary> + /// Logs a message, verifying the expected message counts against the + /// current running totals. + /// </summary> + /// <param name="entry"></param> + /// <param name="sMessageToLog"></param> + private void LogMessage(RollConditions entry, string sMessageToLog) + { + Assert.AreEqual(_caRoot.Counter, _iMessagesLogged++); + _root.Log(Level.Debug, sMessageToLog, null); + Assert.AreEqual(_caRoot.Counter, _iMessagesLogged); + _iMessagesLoggedThisFile++; + } - Assert.DoesNotThrow(delegate { log.Log(GetType(), Level.Info, "A", null); }); - Assert.DoesNotThrow(delegate { log.Log(GetType(), Level.Info, "A", null); }); - - DestroyLogger(); + //private void DumpFileEntry( RollFileEntry entry ) + //{ + // System.Diagnostics.Debug.WriteLine( "\tfile name: " + entry.FileName ); + // System.Diagnostics.Debug.WriteLine( "\tfile length: " + entry.FileLength ); + //} + + //private void DumpTableEntry( RollConditions entry ) + //{ + // System.Diagnostics.Debug.WriteLine( "Pre-Conditions" ); + // foreach( RollFileEntry file in entry.GetPreLogFileEntries() ) + // { + // DumpFileEntry( file ); + // } + // System.Diagnostics.Debug.WriteLine( "Post-Conditions" ); + // foreach( RollFileEntry file in entry.GetPostLogFileEntries() ) + // { + // DumpFileEntry( file ); + // } + // // System.Diagnostics.Debug.WriteLine(""); + //} - AssertFileEquals(filename, "A" + Environment.NewLine); - AssertFileEquals(filename + ".1", "A" + Environment.NewLine); - Assert.IsEmpty(sh.Message); - } -#endif + /// <summary> + /// Runs through all table entries, logging messages. Before each message is logged, + /// pre-conditions are checked to ensure the expected files exist and they are the + /// expected size. After logging, verifies the same. + /// </summary> + /// <param name="sBaseFileName"></param> + /// <param name="entries"></param> + /// <param name="sMessageToLog"></param> + private void RollFromTableEntries(string sBaseFileName, RollConditions[] entries, string sMessageToLog) + { + for (int i = 0; i < entries.Length; i++) + { + RollConditions entry = entries[i]; + + // System.Diagnostics.Debug.WriteLine( i + ": Entry " + i + " pre/post conditions"); + // DumpTableEntry( entry ); + // System.Diagnostics.Debug.WriteLine( i + ": Testing entry pre-conditions"); + VerifyPreConditions(sBaseFileName, entry); + // System.Diagnostics.Debug.WriteLine( i + ": Logging message"); + LogMessage(entry, sMessageToLog); + // System.Diagnostics.Debug.WriteLine( i + ": Testing entry post-conditions"); + VerifyPostConditions(sBaseFileName, entry); + // System.Diagnostics.Debug.WriteLine( i + ": Finished validating entry\n"); + } + } + + private static readonly int s_Newline_Length = Environment.NewLine.Length; + + /// <summary> + /// Returns the number of bytes logged per message, including + /// any newline characters in addition to the message length. + /// </summary> + /// <param name="sMessage"></param> + /// <returns></returns> + private static int TotalMessageLength(string sMessage) + { + return sMessage.Length + s_Newline_Length; + } + + /// <summary> + /// Determines how many messages of a fixed length can be logged + /// to a single file before the file rolls. + /// </summary> + /// <param name="iMessageLength"></param> + /// <returns></returns> + private static int MessagesPerFile(int iMessageLength) + { + int iMessagesPerFile = c_iMaximumFileSize / iMessageLength; + + // + // RollingFileAppender checks for wrap BEFORE logging, + // so we will actually get one more message per file than + // we would otherwise. + // + if (iMessagesPerFile * iMessageLength < c_iMaximumFileSize) + { + iMessagesPerFile++; + } + + return iMessagesPerFile; + } + + /// <summary> + /// Determines the name of the current file + /// </summary> + /// <returns></returns> + private static string GetCurrentFile() + { + // Current file name is always the base file name when + // counting. Dates will need a different approach + return c_fileName; + } /// <summary> - /// Verify that the default LockModel is ExclusiveLock, to maintain backwards compatibility with previous behaviour - /// </summary> - [Test] - public void TestDefaultLockingModel() - { - string filename ="test_default.log"; - - SilentErrorHandler sh = new SilentErrorHandler(); - ILogger log = CreateLogger(filename, null, sh); - - IAppender[] appenders = log.Repository.GetAppenders(); - Assert.AreEqual(1, appenders.Length, "The wrong number of appenders are configured"); - - RollingFileAppender rfa = (RollingFileAppender)(appenders[0]); - Assert.AreEqual(typeof(log4net.Appender.FileAppender.ExclusiveLock), rfa.LockingModel.GetType(), "The LockingModel is of an unexpected type"); - - DestroyLogger(); - } - - /// <summary> - /// Tests the count up case, with infinite max backups , to see that - /// initialization of the rolling file appender results in the expected value - /// </summary> - /// <param name="alFiles"></param> - /// <param name="sBaseFile"></param> - /// <param name="iExpectedValue"></param> - private static void VerifyInitializeUpInfiniteExpectedValue(ArrayList alFiles, string sBaseFile, int iExpectedValue) - { - InitializeAndVerifyExpectedValue(alFiles, sBaseFile, CreateRollingFileAppender("-1,0,1"), iExpectedValue); - } - - - /// <summary> - /// Tests the count down case, with max backups limited to 3, to see that - /// initialization of the rolling file appender results in the expected value - /// </summary> - /// <param name="alFiles"></param> - /// <param name="sBaseFile"></param> - /// <param name="iExpectedValue"></param> - private static void VerifyInitializeDownFixedExpectedValue(ArrayList alFiles, string sBaseFile, int iExpectedValue) - { - InitializeAndVerifyExpectedValue(alFiles, sBaseFile, CreateRollingFileAppender("3,0,-1"), iExpectedValue); - } - - /// <summary> - /// Turns a string of comma separated numbers into a collection of filenames - /// generated from the numbers. - /// - /// Defaults to filename in _fileName variable. - /// - /// </summary> - /// <param name="sFileNumbers">Comma separated list of numbers for counted file names</param> - /// <returns></returns> - private static ArrayList MakeTestDataFromString(string sFileNumbers) - { - return MakeTestDataFromString(c_fileName, sFileNumbers); - } - - /// <summary> - /// Turns a string of comma separated numbers into a collection of filenames - /// generated from the numbers - /// - /// Uses the input filename. - /// </summary> - /// <param name="sFileName">Name of file to combine with numbers when generating counted file names</param> - /// <param name="sFileNumbers">Comma separated list of numbers for counted file names</param> - /// <returns></returns> - private static ArrayList MakeTestDataFromString(string sFileName, string sFileNumbers) - { - ArrayList alFiles = new ArrayList(); - - string[] sNumbers = sFileNumbers.Split(','); - foreach(string sNumber in sNumbers) - { - Int32 iValue = Int32.Parse(sNumber.Trim()); - alFiles.Add(MakeFileName(sFileName, iValue)); - } - - return alFiles; - } - - /// <summary> - /// Tests that the current backup index is correctly detected - /// for a file with no extension - /// </summary> - [Test] - public void TestInitializeRollBackups2() - { - VerifyInitializeRollBackupsFromBaseFile("LogFile"); - } - - /// <summary> - /// Tests that the current backup index is correctly detected - /// for a file with a .log extension - /// </summary> - [Test] - public void TestInitializeRollBackups3() - { - VerifyInitializeRollBackupsFromBaseFile("LogFile.log"); - } - - /// <summary> - /// Makes sure that the initialization can detect the backup - /// number correctly. - /// </summary> - /// <param name="iBackups"></param> - /// <param name="iMaxSizeRollBackups"></param> - public void VerifyInitializeRollBackups(int iBackups, int iMaxSizeRollBackups) - { - string sBaseFile = "LogFile.log"; - ArrayList arrFiles = new ArrayList(); - arrFiles.Add("junk1"); - for(int i = 0; i < iBackups; i++) - { - arrFiles.Add(MakeFileName(sBaseFile, i)); - } - RollingFileAppender rfa = new RollingFileAppender(); - rfa.RollingStyle = RollingFileAppender.RollingMode.Size; - SetFieldMaxSizeRollBackups(rfa, iMaxSizeRollBackups); - SetFieldCurSizeRollBackups(rfa, 0); - InitializeRollBackups(rfa, sBaseFile, arrFiles); - - // iBackups / Meaning - // 0 = none - // 1 = file.log - // 2 = file.log.1 - // 3 = file.log.2 - if (0 == iBackups || - 1 == iBackups) - { - Assert.AreEqual(0, GetFieldCurSizeRollBackups(rfa)); - } - else - { - Assert.AreEqual(Math.Min(iBackups - 1, iMaxSizeRollBackups), GetFieldCurSizeRollBackups(rfa)); - } - } - - /// <summary> - /// Tests that the current backup index is correctly detected, - /// and gets no bigger than the max backups setting - /// </summary> - [Test] - public void TestInitializeRollBackups4() - { - const int iMaxRollBackups = 5; - VerifyInitializeRollBackups(0, iMaxRollBackups); - VerifyInitializeRollBackups(1, iMaxRollBackups); - VerifyInitializeRollBackups(2, iMaxRollBackups); - VerifyInitializeRollBackups(3, iMaxRollBackups); - VerifyInitializeRollBackups(4, iMaxRollBackups); - VerifyInitializeRollBackups(5, iMaxRollBackups); - VerifyInitializeRollBackups(6, iMaxRollBackups); - // Final we cap out at the max value - VerifyInitializeRollBackups(7, iMaxRollBackups); - VerifyInitializeRollBackups(8, iMaxRollBackups); - } - - /// <summary> - /// - /// </summary> - [Test, Ignore("Not Implemented: Want to test counted files limited up, to see that others are ?? ignored? deleted?")] - public void TestInitialization3() - { - } - - /// <summary> - /// - /// </summary> - [Test, Ignore("Not Implemented: Want to test counted files limited down, to see that others are ?? ignored? deleted?")] - public void TestInitialization4() - { - } - - /// <summary> - /// - /// </summary> - [Test, Ignore("Not Implemented: Want to test dated files with a limit, to see that others are ?? ignored? deleted?")] - public void TestInitialization5() - { - } - - /// <summary> - /// - /// </summary> - [Test, Ignore("Not Implemented: Want to test dated files with no limit, to see that others are ?? ignored? deleted?")] - public void TestInitialization6() - { - } - - /// <summary> - /// - /// </summary> - [Test, Ignore("Not Implemented: Want to test dated files with mixed dates existing, to see that other dates do not matter")] - public void TestInitialization7() - { - } - - - // - // Helper functions to dig into the appender - // - - private static ArrayList GetExistingFiles(string baseFilePath) - { - return GetExistingFiles(baseFilePath, false); + /// Turns a group of file names into an array of file entries that include the name + /// and a size. This is useful for assigning the properties of backup files, when + /// the length of the files are all the same size due to a fixed message length. + /// </summary> + /// <param name="sBackupGroup"></param> + /// <param name="iBackupFileLength"></param> + /// <returns></returns> + private static RollFileEntry[] MakeBackupFileEntriesFromBackupGroup(string sBackupGroup, int iBackupFileLength) + { + string[] sFiles = sBackupGroup.Split(' '); + + ArrayList alEntries = new ArrayList(); + + for (int i = 0; i < sFiles.Length; i++) + { + // Weed out any whitespace entries from the array + if (sFiles[i].Trim().Length > 0) + { + alEntries.Add(new RollFileEntry(sFiles[i], iBackupFileLength)); } - private static ArrayList GetExistingFiles(string baseFilePath, bool preserveLogFileNameExtension) - { - RollingFileAppender appender = new RollingFileAppender(); - appender.PreserveLogFileNameExtension = preserveLogFileNameExtension; - appender.SecurityContext = NullSecurityContext.Instance; - - return (ArrayList)Utils.InvokeMethod(appender, "GetExistingFiles", baseFilePath); - } - - private static void InitializeRollBackups(RollingFileAppender appender, string baseFile, ArrayList arrayFiles) - { - Utils.InvokeMethod(appender, "InitializeRollBackups", baseFile, arrayFiles); - } - - private static int GetFieldCurSizeRollBackups(RollingFileAppender appender) - { - return (int)Utils.GetField(appender, "m_curSizeRollBackups"); - } - - private static void SetFieldCurSizeRollBackups(RollingFileAppender appender, int val) - { - Utils.SetField(appender, "m_curSizeRollBackups", val); - } - - private static void SetFieldMaxSizeRollBackups(RollingFileAppender appender, int val) - { - Utils.SetField(appender, "m_maxSizeRollBackups", val); - } - - private static string GetTestMessage() - { - switch (Environment.NewLine.Length) - { - case 2: - return c_testMessage98Chars; - - case 1: - return c_testMessage99Chars; - - default: - throw new Exception("Unexpected Environment.NewLine.Length"); - } - } - } - - [TestFixture] - public class RollingFileAppenderSubClassTest : RollingFileAppender - { - [Test] - public void TestComputeCheckPeriod() - { - RollingFileAppender rfa = new RollingFileAppender(); - - Assert.AreEqual(RollPoint.TopOfMinute, InvokeComputeCheckPeriod(rfa, ".yyyy-MM-dd HH:mm"), "TopOfMinute pattern"); - Assert.AreEqual(RollPoint.TopOfHour, InvokeComputeCheckPeriod(rfa, ".yyyy-MM-dd HH"), "TopOfHour pattern"); - Assert.AreEqual(RollPoint.HalfDay, InvokeComputeCheckPeriod(rfa, ".yyyy-MM-dd tt"), "HalfDay pattern"); - Assert.AreEqual(RollPoint.TopOfDay, InvokeComputeCheckPeriod(rfa, ".yyyy-MM-dd"), "TopOfDay pattern"); - Assert.AreEqual(RollPoint.TopOfMonth, InvokeComputeCheckPeriod(rfa, ".yyyy-MM"), "TopOfMonth pattern"); - - // Test invalid roll point - Assert.AreEqual(RollPoint.InvalidRollPoint, InvokeComputeCheckPeriod(rfa, "..."), "TopOfMonth pattern"); - } - - private static RollPoint InvokeComputeCheckPeriod(RollingFileAppender rollingFileAppender, string datePattern) - { - return (RollPoint)Utils.InvokeMethod(rollingFileAppender, "ComputeCheckPeriod", datePattern); - } - } -} + } + + return (RollFileEntry[])alEntries.ToArray(typeof(RollFileEntry)); + } + + /// <summary> + /// Finds the iGroup group in the string (comma separated groups) + /// </summary> + /// <param name="sBackupGroups"></param> + /// <param name="iGroup"></param> + /// <returns></returns> + private static string GetBackupGroup(string sBackupGroups, int iGroup) + { + string[] sGroups = sBackupGroups.Split(','); + return sGroups[iGroup]; + } + + ///// <summary> + ///// Builds a collection of file entries based on the file names + ///// specified in a groups string and the max file size from the + ///// stats object + ///// </summary> + ///// <param name="sBackupGroups"></param> + ///// <param name="stats"></param> + ///// <returns></returns> + //private RollFileEntry[] MakeBackupFileEntriesForPreCondition( string sBackupGroups, RollingStats stats ) + //{ + // if (0 == stats.NumberOfFileRolls ) + // { + // return null; // first round has no previous backups + // } + // string sGroup; + // if (0 == stats.MessagesThisFile ) + // { + // // first file has special pattern...since rolling doesn't occur when message + // // is logged, rather before next message is logged. + // if (stats.NumberOfFileRolls <= 1 ) + // { + // return null; + // } + // // Use backup files from previous round. The minus 2 is because we have already + // // rolled, and the first round uses null instead of the string + // sGroup = GetBackupGroup( sBackupGroups, stats.NumberOfFileRolls-2 ); + // } + // else + // { + // sGroup = GetBackupGroup( sBackupGroups, stats.NumberOfFileRolls-1 ); + // } + // return MakeBackupFileEntriesFromBackupGroup( sGroup, stats.MaximumFileSize ); + //} + + /// <summary> + /// Builds a collection of file entries based on the file names + /// specified in a groups string and the max file size from the + /// stats object + /// </summary> + /// <param name="sBackupGroups"></param> + /// <param name="stats"></param> + /// <returns></returns> + private static RollFileEntry[] MakeBackupFileEntriesForPostCondition(string sBackupGroups, RollingStats stats) + { + if (0 == stats.NumberOfFileRolls) + { + return null; // first round has no previous backups + } + + string sGroup = GetBackupGroup(sBackupGroups, stats.NumberOfFileRolls - 1); + return MakeBackupFileEntriesFromBackupGroup(sGroup, stats.MaximumFileSize); + } + + + /// <summary> + /// This class holds information that is used while we are generating + /// test data sets + /// </summary> + public class RollingStats + { + private int iTotalMessageLength; + private int iMessagesPerFile; + private int iMessagesThisFile; + private int iNumberOfFileRolls; + + /// <summary> + /// Number of total bytes a log file can reach. + /// </summary> + public int MaximumFileSize + { + get { return TotalMessageLength * MessagesPerFile; } + } + + /// <summary> + /// The length of a message, including any CR/LF characters. + /// This length assumes all messages are a fixed length for + /// test purposes. + /// </summary> + public int TotalMessageLength + { + get { return iTotalMessageLength; } + set { iTotalMessageLength = value; } + } + + /// <summary> + /// A count of the number of messages that are logged to each + /// file. + /// </summary> + public int MessagesPerFile + { + get { return iMessagesPerFile; } + set { iMessagesPerFile = value; } + } + + /// <summary> + /// Counts how many messages have been logged to the current file + /// </summary> + public int MessagesThisFile + { + get { return iMessagesThisFile; } + set { iMessagesThisFile = value; } + } + + /// <summary> + /// Counts how many times a file roll has occurred + /// </summary> + public int NumberOfFileRolls + { + get { return iNumberOfFileRolls; } + set { iNumberOfFileRolls = value; } + } + } + + /// <summary> + /// The stats are used to keep track of progress while we are algorithmically + /// generating a table of pre/post condition tests for file rolling. + /// </summary> + /// <param name="sTestMessage"></param> + /// <returns></returns> + private static RollingStats InitializeStats(string sTestMessage) + { + RollingStats rollingStats = new RollingStats(); + + rollingStats.TotalMessageLength = TotalMessageLength(sTestMessage); + rollingStats.MessagesPerFile = MessagesPerFile(rollingStats.TotalMessageLength); + rollingStats.MessagesThisFile = 0; + rollingStats.NumberOfFileRolls = 0; + + return rollingStats; + } + + /// <summary> + /// Takes an existing array of RollFileEntry objects, creates a new array one element + /// bigger, and appends the final element to the end. If the existing entries are + /// null (no entries), then a one-element array is returned with the final element + /// as the only entry. + /// </summary> + /// <param name="existing"></param> + /// <param name="final"></param> + /// <returns></returns> + private static RollFileEntry[] AddFinalElement(RollFileEntry[] existing, RollFileEntry final) + { + int iLength = 1; + if (null != existing) + { + iLength += existing.Length; + } + + RollFileEntry[] combined = new RollFileEntry[iLength]; + if (null != existing) + { + Array.Copy(existing, 0, combined, 0, existing.Length); + } + + combined[iLength - 1] = final; + return combined; + } + + /// <summary> + /// Generates the pre and post condition arrays from an array of backup files and the + /// current file / next file. + /// </summary> + /// <param name="sBackupFiles"></param> + /// <param name="preCondition"></param> + /// <param name="current"></param> + /// <param name="currentNext"></param> + /// <param name="rollingStats"></param> + /// <returns></returns> + private static RollConditions BuildTableEntry(string sBackupFiles, + RollConditions preCondition, + RollFileEntry current, + RollFileEntry currentNext, + RollingStats rollingStats) + { + RollFileEntry[] backupsPost = MakeBackupFileEntriesForPostCondition(sBackupFiles, rollingStats); + RollFileEntry[] post = AddFinalElement(backupsPost, currentNext); + if (null == preCondition) + { + return new RollConditions(AddFinalElement(null, current), post); + } + + return new RollConditions(preCondition.GetPostLogFileEntries(), post); + } + + /// <summary> + /// Returns a RollFileEntry that represents the next state of the current file, + /// based on the current state. When the current state would roll, the next + /// entry is the current file wrapped to 0 bytes. Otherwise, the next state + /// is the post-condition passed in as the currentNext parameter + /// </summary> + /// <param name="rollingStats"></param> + /// <param name="currentNext"></param> + /// <returns></returns> + private static RollFileEntry MoveNextEntry(RollingStats rollingStats, RollFileEntry currentNext) + { + rollingStats.MessagesThisFile = rollingStats.MessagesThisFile + 1; + if (rollingStats.MessagesThisFile >= rollingStats.MessagesPerFile) + { + rollingStats.MessagesThisFile = 0; + rollingStats.NumberOfFileRolls = rollingStats.NumberOfFileRolls + 1; + + return new RollFileEntry(GetCurrentFile(), 0); + } + else + { + return currentNext; + } + } + + /// <summary> + /// Callback point for the regular expression parser. Turns + /// the number into a file name. + /// </summary> + /// <param name="match"></param> + /// <returns></returns> + private static string NumberedNameMaker(Match match) + { + Int32 iValue = Int32.Parse(match.Value); + return MakeFileName(c_fileName, iValue); + } + + /// <summary> + /// Parses a numeric list of files, turning them into file names. + /// Calls back to a method that does the actual replacement, turning + /// the numeric value into a filename. + /// </summary> + /// <param name="sBackupInfo"></param> + /// <param name="evaluator"></param> + /// <returns></returns> + private static string ConvertToFiles(string sBackupInfo, MatchEvaluator evaluator) + { + Regex regex = new Regex(@"\d+"); + return regex.Replace(sBackupInfo, evaluator); + } + + /// <summary> + /// Makes test entries used for verifying counted file names + /// </summary> + /// <param name="sTestMessage">A message to log repeatedly</param> + /// <param name="sBackupInfo">Filename groups used to indicate backup file name progression + /// that results after each message is logged</param> + /// <param name="iMessagesToLog">How many times the test message will be repeatedly logged</param> + /// <returns></returns> + private static RollConditions[] MakeNumericTestEntries(string sTestMessage, + string sBackupInfo, + int iMessagesToLog) + { + return MakeTestEntries( + sTestMessage, + sBackupInfo, + iMessagesToLog, + new MatchEvaluator(NumberedNameMaker)); + } + + /// <summary> + /// This routine takes a list of backup file names and a message that will be logged + /// repeatedly, and generates a collection of objects containing pre-condition and + /// post-condition information. This pre/post information shows the names and expected + /// file sizes for all files just before and just after a message is logged. + /// </summary> + /// <param name="sTestMessage">A message to log repeatedly</param> + /// <param name="sBackupInfo">Filename groups used to indicate backup file name progression + /// that results after each message is logged</param> + /// <param name="iMessagesToLog">How many times the test message will be repeatedly logged</param> + /// <param name="evaluator">Function that can turn a number into a filename</param> + /// <returns></returns> + private static RollConditions[] MakeTestEntries(string sTestMessage, + string sBackupInfo, + int iMessagesToLog, + MatchEvaluator evaluator) + { + string sBackupFiles = ConvertToFiles(sBackupInfo, evaluator); + + RollConditions[] table = new RollConditions[iMessagesToLog]; + + RollingStats rollingStats = InitializeStats(sTestMessage); + + RollConditions preCondition = null; + rollingStats.MessagesThisFile = 0; + + RollFileEntry currentFile = new RollFileEntry(GetCurrentFile(), 0); + for (int i = 0; i < iMessagesToLog; i++) + { + RollFileEntry currentNext = new RollFileEntry( + GetCurrentFile(), + (1 + rollingStats.MessagesThisFile) * rollingStats.TotalMessageLength); + + table[i] = BuildTableEntry(sBackupFiles, preCondition, currentFile, currentNext, rollingStats); + preCondition = table[i]; + + //System.Diagnostics.Debug.WriteLine( "Message " + i ); + //DumpTableEntry( table[i] ); + + currentFile = MoveNextEntry(rollingStats, currentNext); + } + + return table; + } + + /// <summary> + /// Uses the externally defined rolling table to verify rolling names/sizes + /// </summary> + /// <remarks> + /// Pattern is: check pre-conditions. Log messages, checking size of current file. + /// when size exceeds limit, check post conditions. Can determine from message the + /// number of messages N that will cause a roll to occur. Challenge is to verify the + /// expected files, their sizes, and the names. For a message of length L, the backups + /// will be of size (N * L), and the current file will be of size (K * L), where K is + /// the number of messages that have been logged to this file. + /// + /// File sizes can be checked algorithmically. + /// + /// File names are generated using a table driven algorithm, where a number is turned into + /// the actual filename. + /// + /// The entries are comma-separated, with spaces between the names. Each comma indicates + /// a 'roll', and the group between commas indicates the numbers for all backup files that + /// occur as a result of the roll. It is assumed that no backup files exist before a roll + /// occurs + /// </remarks> + /// <param name="table"></param> + private void VerifyRolling(RollConditions[] table) + { + ConfigureRootAppender(); + RollFromTableEntries(c_fileName, table, GetTestMessage()); + } + + /// <summary> + /// Validates rolling using a fixed number of backup files, with + /// count direction set to up, so that newer files have higher counts. + /// Newest = N, Oldest = N-K, where K is the number of backups to allow + /// and N is the number of times rolling has occurred. + /// </summary> + [Test] + public void TestRollingCountUpFixedBackups() + { + // + // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the + // oldest, and 3 is the newest + // + string sBackupInfo = "1, 1 2, 1 2 3, 2 3 4, 3 4 5"; + + // + // Count Up + // + _iCountDirection = +1; + + // + // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 + // bytes for current file as messages are logged. + // + int iMessagesToLog = 30; + + VerifyRolling(MakeNumericTestEntries(GetTestMessage(), sBackupInfo, iMessagesToLog)); + } + + /// <summary> + /// Validates rolling using an infinite number of backup files, with + /// count direction set to up, so that newer files have higher counts. + /// Newest = N, Oldest = 1, where N is the number of times rolling has + /// occurred. + /// </summary> + [Test] + public void TestRollingCountUpInfiniteBackups() + { + // + // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the + // oldest, and 3 is the newest + // + string sBackupInfo = "1, 1 2, 1 2 3, 1 2 3 4, 1 2 3 4 5"; + + // + // Count Up + // + _iCountDirection = +1; + + // + // Infinite backups + // + _MaxSizeRollBackups = -1; + + // + // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 + // bytes for current file as messages are logged. + // + int iMessagesToLog = 30; + + VerifyRolling(MakeNumericTestEntries(GetTestMessage(), sBackupInfo, iMessagesToLog)); + } + + /// <summary> + /// Validates rolling with no backup files, with count direction set to up. + /// Only the current file should be present, wrapping to 0 bytes once the + /// previous file fills up. + /// </summary> + [Test] + public void TestRollingCountUpZeroBackups() + { + // + // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the + // oldest, and 3 is the newest + // + string sBackupInfo = ", , , , "; + + // + // Count Up + // + _iCountDirection = +1; + + // + // No backups + // + _MaxSizeRollBackups = 0; + + // + // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 + // bytes for current file as messages are logged. + // + int iMessagesToLog = 30; + + VerifyRolling(MakeNumericTestEntries(GetTestMessage(), sBackupInfo, iMessagesToLog)); + } + + + /// <summary> + /// Validates rolling using a fixed number of backup files, with + /// count direction set to down, so that older files have higher counts. + /// Newest = 1, Oldest = N, where N is the number of backups to allow + /// </summary> + [Test] + public void TestRollingCountDownFixedBackups() + { + // + // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the + // oldest, and 3 is the newest + // + string sBackupInfo = "1, 1 2, 1 2 3, 1 2 3, 1 2 3"; + + // + // Count Up + // + _iCountDirection = -1; + + // + // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 + // bytes for current file as messages are logged. + // + int iMessagesToLog = 30; + + VerifyRolling(MakeNumericTestEntries(GetTestMessage(), sBackupInfo, iMessagesToLog)); + } + + /// <summary> + /// Validates rolling using an infinite number of backup files, with + /// count direction set to down, so that older files have higher counts. + /// Newest = 1, Oldest = N, where N is the number of times rolling has + /// occurred + /// </summary> + [Test] + public void TestRollingCountDownInfiniteBackups() + { + // + // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the + // oldest, and 3 is the newest + // + string sBackupInfo = "1, 1 2, 1 2 3, 1 2 3 4, 1 2 3 4 5"; + + // + // Count Down + // + _iCountDirection = -1; + + // + // Infinite backups + // + _MaxSizeRollBackups = -1; + + // + // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 + // bytes for current file as messages are logged. + // + int iMessagesToLog = 30; + + VerifyRolling(MakeNumericTestEntries(GetTestMessage(), sBackupInfo, iMessagesToLog)); + } + + /// <summary> + /// Validates rolling with no backup files, with count direction set to down. + /// Only the current file should be present, wrapping to 0 bytes once the + /// previous file fills up. + /// </summary> + [Test] + public void TestRollingCountDownZeroBackups() + { + // + // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the + // oldest, and 3 is the newest + // + string sBackupInfo = ", , , , "; + + // + // Count Up + // + _iCountDirection = -1; + + // + // No backups + // + _MaxSizeRollBackups = 0; + + // + // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 + // bytes for current file as messages are logged. + // + int iMessagesToLog = 30; + + VerifyRolling(MakeNumericTestEntries(GetTestMessage(), sBackupInfo, iMessagesToLog)); + } + + /// <summary> + /// Configures the root appender for counting and rolling + /// </summary> + private void ConfigureRootAppender() + { + _root = ((Repository.Hierarchy.Hierarchy)Utils.GetRepository()).Root; + _root.Level = Level.Debug; + _caRoot = new CountingAppender(); + _root.AddAppender(_caRoot); + Assert.AreEqual(_caRoot.Counter, 0); + + // + // Set the root appender with a RollingFileAppender + // + _root.AddAppender(CreateAppender()); + + _root.Repository.Configured = true; + } + + /// <summary> + /// Verifies that the current backup index is detected correctly when initializing + /// </summary> + /// <param name="sBaseFile"></param> + /// <param name="alFiles"></param> + /// <param name="iExpectedCurSizeRollBackups"></param> + private static void VerifyInitializeRollBackupsFromBaseFile(string sBaseFile, + ArrayList alFiles, + int iExpectedCurSizeRollBackups) + { + InitializeAndVerifyExpectedValue(alFiles, sBaseFile, CreateRollingFileAppender("5,0,1"), + iExpectedCurSizeRollBackups); + } + + /// <summary> + /// Tests that the current backup index is 0 when no + /// existing files are seen + /// </summary> + [Test] + public void TestInitializeRollBackups1() + { + string sBaseFile = "LogFile.log"; + ArrayList arrFiles = new ArrayList(); + arrFiles.Add("junk1"); + arrFiles.Add("junk1.log"); + arrFiles.Add("junk2.log"); + arrFiles.Add("junk.log.1"); + arrFiles.Add("junk.log.2"); + + int iExpectedCurSizeRollBackups = 0; + VerifyInitializeRollBackupsFromBaseFile(sBaseFile, arrFiles, iExpectedCurSizeRollBackups); + } + + /// <summary> + /// Verifies that files are detected when the base file is specified + /// </summary> + /// <param name="sBaseFile"></param> + private static void VerifyInitializeRollBackupsFromBaseFile(string sBaseFile) + { + ArrayList alFiles = MakeTestDataFromString(sBaseFile, "0,1,2"); + + int iExpectedCurSizeRollBackups = 2; + VerifyInitializeRollBackupsFromBaseFile(sBaseFile, alFiles, iExpectedCurSizeRollBackups); + } + + /// <summary> + /// Verifies that count goes to the highest when counting up + /// </summary> + [Test] + public void TestInitializeCountUpFixed() + { + ArrayList alFiles = MakeTestDataFromString("3,4,5"); + int iExpectedValue = 5; + InitializeAndVerifyExpectedValue(alFiles, c_fileName, CreateRollingFileAppender("3,0,1"), iExpectedValue); + } + + /// <summary> + /// Verifies that count goes to the highest when counting up + /// </summary> + [Test] + public void TestInitializeCountUpFixed2() + { + ArrayList alFiles = MakeTestDataFromString("0,3"); + int iExpectedValue = 3; + InitializeAndVerifyExpectedValue(alFiles, c_fileName, CreateRollingFileAppender("3,0,1"), iExpectedValue); + } + + /// <summary> + /// Verifies that count stays at 0 for the zero backups case + /// when counting up + /// </summary> + [Test] + public void TestInitializeCountUpZeroBackups() + { + ArrayList alFiles = MakeTestDataFromString("0,3"); + int iExpectedValue = 0; + InitializeAndVerifyExpectedValue(alFiles, c_fileName, CreateRollingFileAppender("0,0,1"), iExpectedValue); + } + + /// <summary> + /// Verifies that count stays at 0 for the zero backups case + /// when counting down + /// </summary> + [Test] + public void TestInitializeCountDownZeroBackups() + { + ArrayList alFiles = MakeTestDataFromString("0,3"); + int iExpectedValue = 0; + InitializeAndVerifyExpectedValue(alFiles, c_fileName, CreateRollingFileAppender("0,0,-1"), iExpectedValue); + } + + + /// <summary> + /// Verifies that count goes to the highest when counting up + /// </summary> + [Test] + public void TestInitializeCountDownFixed() + { + ArrayList alFiles = MakeTestDataFromString("4,5,6"); + VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 0); + } + + /// <summary> + /// Verifies that count goes to the highest when counting up + /// </summary> + [Test] + public void TestInitializeCountDownFixed2() + { + ArrayList alFiles = MakeTestDataFromString("1,5,6"); + VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 1); + } + + /// <summary> + /// Verifies that count goes to the highest when counting up + /// </summary> + [Test] + public void TestInitializeCountDownFixed3() + { + ArrayList alFiles = MakeTestDataFromString("2,5,6"); + VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 2); + } + + /// <summary> + /// Verifies that count goes to the highest when counting up + /// </summary> + [Test] + public void TestInitializeCountDownFixed4() + { + ArrayList alFiles = MakeTestDataFromString("3,5,6"); + VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 3); + } + + /// <summary> + /// Verifies that count goes to the highest when counting up + /// </summary> + [Test] + public void TestInitializeCountDownFixed5() + { + ArrayList alFiles = MakeTestDataFromString("1,2,3"); + VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 3); + } + + /// <summary> + /// Verifies that count goes to the highest when counting up + /// </summary> + [Test] + public void TestInitializeCountDownFixed6() + { + ArrayList alFiles = MakeTestDataFromString("1,2"); + VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 2); + } + + /// <summary> + /// Verifies that count goes to the highest when counting up + /// </summary> + [Test] + public void TestInitializeCountDownFixed7() + { + ArrayList alFiles = MakeTestDataFromString("2,3"); + VerifyInitializeDownFixedExpectedValue(alFiles, c_fileName, 3); + } + + private static void InitializeAndVerifyExpectedValue(ArrayList alFiles, + string sBaseFile, + RollingFileAppender rfa, + int iExpectedValue) + { + InitializeRollBackups(rfa, sBaseFile, alFiles); + Assert.AreEqual(iExpectedValue, GetFieldCurSizeRollBackups(rfa)); + } + + /// <summary> + /// Tests the count down case, with infinite max backups, to see that + /// initialization of the rolling file appender results in the expected value + /// </summary> + /// <param name="alFiles"></param> + /// <param name="sBaseFile"></param> + /// <param name="iExpectedValue"></param> + private static void VerifyInitializeDownInfiniteExpectedValue(ArrayList alFiles, + string sBaseFile, + int iExpectedValue) + { + InitializeAndVerifyExpectedValue(alFiles, sBaseFile, CreateRollingFileAppender("-1,0,-1"), iExpectedValue); + } + + /// <summary> + /// Creates a RollingFileAppender with the desired values, where the + /// values are passed as a comma separated string, with 3 parameters, + /// m_maxSizeRollBackups, m_curSizeRollBackups, CountDirection + /// </summary> + /// <param name="sParams"></param> + /// <returns></returns> + private static RollingFileAppender CreateRollingFileAppender(string sParams) + { + string[] asParams = sParams.Split(','); + if (null == asParams || asParams.Length != 3) + { + throw new ArgumentOutOfRangeException(sParams, sParams, + "Must have 3 comma separated params: MaxSizeRollBackups, CurSizeRollBackups, CountDirection"); + } + + RollingFileAppender rfa = new RollingFileAppender(); + rfa.RollingStyle = RollingFileAppender.RollingMode.Size; + SetFieldMaxSizeRollBackups(rfa, Int32.Parse(asParams[0].Trim())); + SetFieldCurSizeRollBackups(rfa, Int32.Parse(asParams[1].Trim())); + rfa.CountDirection = Int32.Parse(asParams[2].Trim()); + + return rfa; + } + + /// <summary> + /// Verifies that count goes to the highest when counting down + /// and infinite backups are selected + /// </summary> + [Test] + public void TestInitializeCountDownInfinite() + { + ArrayList alFiles = MakeTestDataFromString("2,3"); + VerifyInitializeDownInfiniteExpectedValue(alFiles, c_fileName, 3); + } + + /// <summary> + /// Verifies that count goes to the highest when counting down + /// and infinite backups are selected + /// </summary> + [Test] + public void TestInitializeCountDownInfinite2() + { + ArrayList alFiles = MakeTestDataFromString("2,3,4,5,6,7,8,9,10"); + VerifyInitializeDownInfiniteExpectedValue(alFiles, c_fileName, 10); + } + + /// <summary> + /// Verifies that count goes to the highest when counting down + /// and infinite backups are selected + /// </summary> + [Test] + public void TestInitializeCountDownInfinite3() + { + ArrayList alFiles = MakeTestDataFromString("9,10,3,4,5,7,9,6,1,2,8"); + VerifyInitializeDownInfiniteExpectedValue(alFiles, c_fileName, 10); + } + + /// <summary> + /// Verifies that count goes to the highest when counting up + /// and infinite backups are selected + /// </summary> + [Test] + public void TestInitializeCountUpInfinite() + { + ArrayList alFiles = MakeTestDataFromString("2,3"); + VerifyInitializeUpInfiniteExpectedValue(alFiles, c_fileName, 3); + } + + /// <summary> + /// Verifies that count goes to the highest when counting up + /// and infinite backups are selected + /// </summary> + [Test] + public void TestInitializeCountUpInfinite2() + { + ArrayList alFiles = MakeTestDataFromString("2,3,4,5,6,7,8,9,10"); + VerifyInitializeUpInfiniteExpectedValue(alFiles, c_fileName, 10); + } + + /// <summary> + /// Verifies that count goes to the highest when counting up + /// and infinite backups are selected + /// </summary> + [Test] + public void TestInitializeCountUpInfinite3() + { + ArrayList alFiles = MakeTestDataFromString("9,10,3,4,5,7,9,6,1,2,8"); + VerifyInitializeUpInfiniteExpectedValue(alFiles, c_fileName, 10); + } + + /// <summary> + /// Creates a logger hierarchy, configures a rolling file appender and returns an ILogger + /// </summary> + /// <param name="filename">The filename to log to</param> + /// <param name="lockModel">The locking model to use.</param> + /// <param name="handler">The error handler to use.</param> + /// <returns>A configured ILogger</returns> + private static ILogger CreateLogger(string filename, + FileAppender.LockingModelBase lockModel, + IErrorHandler handler) + { + return CreateLogger(filename, lockModel, handler, 100000, 0); + } + + /// <summary> + /// Creates a logger hierarchy, configures a rolling file appender and returns an ILogger + /// </summary> + /// <param name="filename">The filename to log to</param> + /// <param name="lockModel">The locking model to use.</param> + /// <param name="handler">The error handler to use.</param> + /// <param name="maxFileSize">Maximum file size for roll</param> + /// <param name="maxSizeRollBackups">Maximum number of roll backups</param> + /// <returns>A configured ILogger</returns> + private static ILogger CreateLogger(string filename, + FileAppender.LockingModelBase lockModel, + IErrorHandler handler, + int maxFileSize, + int maxSizeRollBackups) + { + Repository.Hierarchy.Hierarchy h = + (Repository.Hierarchy.Hierarchy)LogManager.CreateRepository("TestRepository"); + + RollingFileAppender appender = new RollingFileAppender(); + appender.File = filename; + appender.AppendToFile = false; + appender.CountDirection = 0; + appender.RollingStyle = RollingFileAppender.RollingMode.Size; + appender.MaxFileSize = maxFileSize; + appender.Encoding = Encoding.ASCII; + appender.ErrorHandler = handler; + appender.MaxSizeRollBackups = maxSizeRollBackups; + if (lockModel != null) + { + appender.LockingModel = lockModel; + } + + PatternLayout layout = new PatternLayout(); + layout.ConversionPattern = "%m%n"; + layout.ActivateOptions(); + + appender.Layout = layout; + appender.ActivateOptions(); + + h.Root.AddAppender(appender); + h.Configured = true; + + ILogger log = h.GetLogger("Logger"); + return log; + } + + /// <summary> + /// Destroys the logger hierarchy created by <see cref="RollingFileAppenderTest.CreateLogger"/> + /// </summary> + private static void DestroyLogger() + { + Repository.Hierarchy.Hierarchy h = + (Repository.Hierarchy.Hierarchy)LogManager.GetRepository("TestRepository"); + h.ResetConfiguration(); + //Replace the repository selector so that we can recreate the hierarchy with the same name if necessary + LoggerManager.RepositorySelector = + new DefaultRepositorySelector(typeof(log4net.Repository.Hierarchy.Hierarchy)); + } + + private static void AssertFileEquals(string filename, string contents) + { + FileInfo fileinfo = new FileInfo(filename); + StreamReader sr = new StreamReader(fileinfo.OpenRead()); + string logcont = sr.ReadToEnd(); + sr.Close(); + + Assert.AreEqual(contents, logcont, "Log contents is not what is expected"); + + File.Delete(filename); + } + + /// <summary> + /// Verifies that logging a message actually produces output + /// </summary> + [Test] + public void TestLogOutput() + { + string filename = "test_simple.log"; + SilentErrorHandler sh = new SilentErrorHandler(); + ILogger log = CreateLogger(filename, new FileAppender.ExclusiveLock(), sh); + log.Log(GetType(), Level.Info, "This is a message", null); + log.Log(GetType(), Level.Info, "This is a message 2", null); + DestroyLogger(); + + AssertFileEquals(filename, + "This is a message" + Environment.NewLine + "This is a message 2" + Environment.NewLine); + Assert.AreEqual("", sh.Message, "Unexpected error message"); + } + + /// <summary> + /// Verifies that attempting to log to a locked file fails gracefully + /// </summary> + [Test] + public void TestExclusiveLockFails() + { + string filename = "test_exclusive_lock_fails.log"; + + FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); + fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); + + SilentErrorHandler sh = new SilentErrorHandler(); + ILogger log = CreateLogger(filename, new FileAppender.ExclusiveLock(), sh); + log.Log(GetType(), Level.Info, "This is a message", null); + log.Log(GetType(), Level.Info, "This is a message 2", null); + DestroyLogger(); + fs.Close(); + + AssertFileEquals(filename, "Test"); + Assert.AreEqual(sh.Message.Substring(0, 30), "Unable to acquire lock on file", + "Expecting an error message"); + } + + /// <summary> + /// Verifies that attempting to log to a locked file recovers if the lock is released + /// </summary> + [Test] + public void TestExclusiveLockRecovers() + { + string filename = "test_exclusive_lock_recovers.log"; + + FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); + fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); + + SilentErrorHandler sh = new SilentErrorHandler(); + ILogger log = CreateLogger(filename, new FileAppender.ExclusiveLock(), sh); + log.Log(GetType(), Level.Info, "This is a message", null); + fs.Close(); + log.Log(GetType(), Level.Info, "This is a message 2", null); + DestroyLogger(); + + AssertFileEquals(filename, "This is a message 2" + Environment.NewLine); + Assert.AreEqual("Unable to acquire lock on file", sh.Message.Substring(0, 30), + "Expecting an error message"); + } + + /// <summary> + /// Verifies that attempting to log to a file with ExclusiveLock really locks the file + /// </summary> + [Test] + public void TestExclusiveLockLocks() + { + string filename = "test_exclusive_lock_locks.log"; + bool locked = false; + + SilentErrorHandler sh = new SilentErrorHandler(); + ILogger log = CreateLogger(filename, new FileAppender.ExclusiveLock(), sh); + log.Log(GetType(), Level.Info, "This is a message", null); + + try + { + FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); + fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); + fs.Close(); + } + catch (IOException e1) + { +#if MONO + Assert.AreEqual("Sharing violation on path ", e1.Message.Substring(0, 26), "Unexpected exception"); +#else + Assert.AreEqual("The process cannot access the file ", e1.Message.Substring(0, 35), + "Unexpected exception"); +#endif + locked = true; + } + + log.Log(GetType(), Level.Info, "This is a message 2", null); + DestroyLogger(); + + Assert.IsTrue(locked, "File was not locked"); +#if !MONO || MONO_3_5 || MONO_4_0 // at least on Linux with Mono 2.4 exclusive locking doesn't work as one would expect + AssertFileEquals(filename, + "This is a message" + Environment.NewLine + "This is a message 2" + Environment.NewLine); +#endif + Assert.AreEqual("", sh.Message, "Unexpected error message"); + } + + + /// <summary> + /// Verifies that attempting to log to a locked file fails gracefully + /// </summary> + [Test] + public void TestMinimalLockFails() + { + string filename = "test_minimal_lock_fails.log"; + + FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); + fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); + + SilentErrorHandler sh = new SilentErrorHandler(); + ILogger log = CreateLogger(filename, new FileAppender.MinimalLock(), sh); + log.Log(GetType(), Level.Info, "This is a message", null); + log.Log(GetType(), Level.Info, "This is a message 2", null); + DestroyLogger(); + fs.Close(); + + AssertFileEquals(filename, "Test"); + Assert.AreEqual("Unable to acquire lock on file", sh.Message.Substring(0, 30), + "Expecting an error message"); + } + + /// <summary> + /// Verifies that attempting to log to a locked file recovers if the lock is released + /// </summary> + [Test] + public void TestMinimalLockRecovers() + { + string filename = "test_minimal_lock_recovers.log"; + + FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); + fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); + + SilentErrorHandler sh = new SilentErrorHandler(); + ILogger log = CreateLogger(filename, new FileAppender.MinimalLock(), sh); + log.Log(GetType(), Level.Info, "This is a message", null); + fs.Close(); + log.Log(GetType(), Level.Info, "This is a message 2", null); + DestroyLogger(); + + AssertFileEquals(filename, "This is a message 2" + Environment.NewLine); + Assert.AreEqual("Unable to acquire lock on file", sh.Message.Substring(0, 30), + "Expecting an error message"); + } + + /// <summary> + /// Verifies that attempting to log to a file with MinimalLock doesn't lock the file + /// </summary> + [Test] + public void TestMinimalLockUnlocks() + { + string filename = "test_minimal_lock_unlocks.log"; + bool locked; + + SilentErrorHandler sh = new SilentErrorHandler(); + ILogger log = CreateLogger(filename, new FileAppender.MinimalLock(), sh); + log.Log(GetType(), Level.Info, "This is a message", null); + + locked = true; + FileStream fs = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.None); + fs.Write(Encoding.ASCII.GetBytes("Test" + Environment.NewLine), 0, 4 + Environment.NewLine.Length); + fs.Close(); + + log.Log(GetType(), Level.Info, "This is a message 2", null); + DestroyLogger(); + + Assert.IsTrue(locked, "File was not locked"); + AssertFileEquals(filename, + "This is a message" + Environment.NewLine + "Test" + Environment.NewLine + "This is a message 2" + + Environment.NewLine); + Assert.AreEqual("", sh.Message, "Unexpected error message"); + } + +#if !NETCF + /// <summary> + /// Verifies that attempting to log to a locked file fails gracefully + /// </summary> + [Test] + public void TestInterProcessLockFails() + { + string filename = "test_interprocess_lock_fails.log"; + + FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); + fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); + + SilentErrorHandler sh = new SilentErrorHandler(); + ILogger log = CreateLogger(filename, new FileAppender.InterProcessLock(), sh); + log.Log(GetType(), Level.Info, "This is a message", null); + log.Log(GetType(), Level.Info, "This is a message 2", null); + DestroyLogger(); + fs.Close(); + + AssertFileEquals(filename, "Test"); + Assert.AreEqual("Unable to acquire lock on file", sh.Message.Substring(0, 30), + "Expecting an error message"); + } + + /// <summary> + /// Verifies that attempting to log to a locked file recovers if the lock is released + /// </summary> + [Test] + public void TestInterProcessLockRecovers() + { + string filename = "test_interprocess_lock_recovers.log"; + + FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); + fs.Write(Encoding.ASCII.GetBytes("Test"), 0, 4); + + SilentErrorHandler sh = new SilentErrorHandler(); + ILogger log = CreateLogger(filename, new FileAppender.InterProcessLock(), sh); + log.Log(GetType(), Level.Info, "This is a message", null); + fs.Close(); + log.Log(GetType(), Level.Info, "This is a message 2", null); + DestroyLogger(); + + AssertFileEquals(filename, "This is a message 2" + Environment.NewLine); + Assert.AreEqual("Unable to acquire lock on file", sh.Message.Substring(0, 30), + "Expecting an error message"); + } + + /// <summary> + /// Verifies that attempting to log to a file with InterProcessLock really locks the file + /// </summary> + [Test] + public void TestInterProcessLockUnlocks() + { + string filename = "test_interprocess_lock_unlocks.log"; + bool locked; + + SilentErrorHandler sh = new SilentErrorHandler(); + ILogger log = CreateLogger(filename, new FileAppender.InterProcessLock(), sh); + log.Log(GetType(), Level.Info, "This is a message", null); + + locked = true; + FileStream fs = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); + fs.Write(Encoding.ASCII.GetBytes("Test" + Environment.NewLine), 0, 4 + Environment.NewLine.Length); + fs.Close(); + + log.Log(GetType(), Level.Info, "This is a message 2", null); + DestroyLogger(); + + Assert.IsTrue(locked, "File was not locked"); + AssertFileEquals(filename, + "This is a message" + Environment.NewLine + "Test" + Environment.NewLine + "This is a message 2" + + Environment.NewLine); + Assert.AreEqual("", sh.Message, "Unexpected error message"); + } + + /// <summary> + /// Verifies that rolling file works + /// </summary> + [Test] + public void TestInterProcessLockRoll() + { + string filename = "test_interprocess_lock_roll.log"; + + SilentErrorHandler sh = new SilentErrorHandler(); + ILogger log = CreateLogger(filename, new FileAppender.InterProcessLock(), sh, 1, 2); + + Assert.DoesNotThrow(delegate + { + log.Log(GetType(), Level.Info, "A", null); + }); + Assert.DoesNotThrow(delegate + { + log.Log(GetType(), Level.Info, "A", null); + }); + + DestroyLogger(); + + AssertFileEquals(filename, "A" + Environment.NewLine); + AssertFileEquals(filename + ".1", "A" + Environment.NewLine); + Assert.IsEmpty(sh.Message); + } +#endif + + /// <summary> + /// Verify that the default LockModel is ExclusiveLock, to maintain backwards compatibility with previous behaviour + /// </summary> + [Test] + public void TestDefaultLockingModel() + { + string filename = "test_default.log"; + + SilentErrorHandler sh = new SilentErrorHandler(); + ILogger log = CreateLogger(filename, null, sh); + + IAppender[] appenders = log.Repository.GetAppenders(); + Assert.AreEqual(1, appenders.Length, "The wrong number of appenders are configured"); + + RollingFileAppender rfa = (RollingFileAppender)(appenders[0]); + Assert.AreEqual(typeof(log4net.Appender.FileAppender.ExclusiveLock), rfa.LockingModel.GetType(), + "The LockingModel is of an unexpected type"); + + DestroyLogger(); + } + + /// <summary> + /// Tests the count up case, with infinite max backups , to see that + /// initialization of the rolling file appender results in the expected value + /// </summary> + /// <param name="alFiles"></param> + /// <param name="sBaseFile"></param> + /// <param name="iExpectedValue"></param> + private static void VerifyInitializeUpInfiniteExpectedValue(ArrayList alFiles, + string sBaseFile, + int iExpectedValue) + { + InitializeAndVerifyExpectedValue(alFiles, sBaseFile, CreateRollingFileAppender("-1,0,1"), iExpectedValue); + } + + + /// <summary> + /// Tests the count down case, with max backups limited to 3, to see that + /// initialization of the rolling file appender results in the expected value + /// </summary> + /// <param name="alFiles"></param> + /// <param name="sBaseFile"></param> + /// <param name="iExpectedValue"></param> + private static void VerifyInitializeDownFixedExpectedValue(ArrayList alFiles, + string sBaseFile, + int iExpectedValue) + { + InitializeAndVerifyExpectedValue(alFiles, sBaseFile, CreateRollingFileAppender("3,0,-1"), iExpectedValue); + } + + /// <summary> + /// Turns a string of comma separated numbers into a collection of filenames + /// generated from the numbers. + /// + /// Defaults to filename in _fileName variable. + /// + /// </summary> + /// <param name="sFileNumbers">Comma separated list of numbers for counted file names</param> + /// <returns></returns> + private static ArrayList MakeTestDataFromString(string sFileNumbers) + { + return MakeTestDataFromString(c_fileName, sFileNumbers); + } + + /// <summary> + /// Turns a string of comma separated numbers into a collection of filenames + /// generated from the numbers + /// + /// Uses the input filename. + /// </summary> + /// <param name="sFileName">Name of file to combine with numbers when generating counted file names</param> + /// <param name="sFileNumbers">Comma separated list of numbers for counted file names</param> + /// <returns></returns> + private static ArrayList MakeTestDataFromString(string sFileName, string sFileNumbers) + { + ArrayList alFiles = new ArrayList(); + + string[] sNumbers = sFileNumbers.Split(','); + foreach (string sNumber in sNumbers) + { + Int32 iValue = Int32.Parse(sNumber.Trim()); + alFiles.Add(MakeFileName(sFileName, iValue)); + } + + return alFiles; + } + + /// <summary> + /// Tests that the current backup index is correctly detected + /// for a file with no extension + /// </summary> + [Test] + public void TestInitializeRollBackups2() + { + VerifyInitializeRollBackupsFromBaseFile("LogFile"); + } + + /// <summary> + /// Tests that the current backup index is correctly detected + /// for a file with a .log extension + /// </summary> + [Test] + public void TestInitializeRollBackups3() + { + VerifyInitializeRollBackupsFromBaseFile("LogFile.log"); + } + + /// <summary> + /// Makes sure that the initialization can detect the backup + /// number correctly. + /// </summary> + /// <param name="iBackups"></param> + /// <param name="iMaxSizeRollBackups"></param> + public void VerifyInitializeRollBackups(int iBackups, int iMaxSizeRollBackups) + { + string sBaseFile = "LogFile.log"; + ArrayList arrFiles = new ArrayList(); + arrFiles.Add("junk1"); + for (int i = 0; i < iBackups; i++) + { + arrFiles.Add(MakeFileName(sBaseFile, i)); + } + + RollingFileAppender rfa = new RollingFileAppender(); + rfa.RollingStyle = RollingFileAppender.RollingMode.Size; + SetFieldMaxSizeRollBackups(rfa, iMaxSizeRollBackups); + SetFieldCurSizeRollBackups(rfa, 0); + InitializeRollBackups(rfa, sBaseFile, arrFiles); + + // iBackups / Meaning + // 0 = none + // 1 = file.log + // 2 = file.log.1 + // 3 = file.log.2 + if (0 == iBackups || + 1 == iBackups) + { + Assert.AreEqual(0, GetFieldCurSizeRollBackups(rfa)); + } + else + { + Assert.AreEqual(Math.Min(iBackups - 1, iMaxSizeRollBackups), GetFieldCurSizeRollBackups(rfa)); + } + } + + /// <summary> + /// Tests that the current backup index is correctly detected, + /// and gets no bigger than the max backups setting + /// </summary> + [Test] + public void TestInitializeRollBackups4() + { + const int iMaxRollBackups = 5; + VerifyInitializeRollBackups(0, iMaxRollBackups); + VerifyInitializeRollBackups(1, iMaxRollBackups); + VerifyInitializeRollBackups(2, iMaxRollBackups); + VerifyInitializeRollBackups(3, iMaxRollBackups); + VerifyInitializeRollBackups(4, iMaxRollBackups); + VerifyInitializeRollBackups(5, iMaxRollBackups); + VerifyInitializeRollBackups(6, iMaxRollBackups); + // Final we cap out at the max value + VerifyInitializeRollBackups(7, iMaxRollBackups); + VerifyInitializeRollBackups(8, iMaxRollBackups); + } + + /// <summary> + /// + /// </summary> + [Test, + Ignore("Not Implemented: Want to test counted files limited up, to see that others are ?? ignored? deleted?")] + public void TestInitialization3() + { + } + + /// <summary> + /// + /// </summary> + [Test, + Ignore( + "Not Implemented: Want to test counted files limited down, to see that others are ?? ignored? deleted?")] + public void TestInitialization4() + { + } + + /// <summary> + /// + /// </summary> + [Test, + Ignore("Not Implemented: Want to test dated files with a limit, to see that others are ?? ignored? deleted?")] + public void TestInitialization5() + { + } + + /// <summary> + /// + /// </summary> + [Test, + Ignore("Not Implemented: Want to test dated files with no limit, to see that others are ?? ignored? deleted?")] + public void TestInitialization6() + { + } + + /// <summary> + /// + /// </summary> + [Test, + Ignore( + "Not Implemented: Want to test dated files with mixed dates existing, to see that other dates do not matter")] + public void TestInitialization7() + { + } + + + // + // Helper functions to dig into the appender + // + + private static ArrayList GetExistingFiles(string baseFilePath) + { + return GetExistingFiles(baseFilePath, false); + } + + private static ArrayList GetExistingFiles(string baseFilePath, bool preserveLogFileNameExtension) + { + RollingFileAppender appender = new RollingFileAppender(); + appender.PreserveLogFileNameExtension = preserveLogFileNameExtension; + appender.SecurityContext = NullSecurityContext.Instance; + + return (ArrayList)Utils.InvokeMethod(appender, "GetExistingFiles", baseFilePath); + } + + private static void InitializeRollBackups(RollingFileAppender appender, string baseFile, ArrayList arrayFiles) + { + Utils.InvokeMethod(appender, "InitializeRollBackups", baseFile, arrayFiles); + } + + private static int GetFieldCurSizeRollBackups(RollingFileAppender appender) + { + return (int)Utils.GetField(appender, "m_curSizeRollBackups"); + } + + private static void SetFieldCurSizeRollBackups(RollingFileAppender appender, int val) + { + Utils.SetField(appender, "m_curSizeRollBackups", val); + } + + private static void SetFieldMaxSizeRollBackups(RollingFileAppender appender, int val) + { + Utils.SetField(appender, "m_maxSizeRollBackups", val); + } + + private static string GetTestMessage() + { + switch (Environment.NewLine.Length) + { + case 2: + return c_testMessage98Chars; + + case 1: + return c_testMessage99Chars; + + default: + throw new Exception("Unexpected Environment.NewLine.Length"); + } + } + } + + [TestFixture] + public class RollingFileAppenderSubClassTest : RollingFileAppender + { + [Test] + public void TestComputeCheckPeriod() + { + RollingFileAppender rfa = new RollingFileAppender(); + + Assert.AreEqual(RollPoint.TopOfMinute, InvokeComputeCheckPeriod(rfa, ".yyyy-MM-dd HH:mm"), + "TopOfMinute pattern"); + Assert.AreEqual(RollPoint.TopOfHour, InvokeComputeCheckPeriod(rfa, ".yyyy-MM-dd HH"), "TopOfHour pattern"); + Assert.AreEqual(RollPoint.HalfDay, InvokeComputeCheckPeriod(rfa, ".yyyy-MM-dd tt"), "HalfDay pattern"); + Assert.AreEqual(RollPoint.TopOfDay, InvokeComputeCheckPeriod(rfa, ".yyyy-MM-dd"), "TopOfDay pattern"); + Assert.AreEqual(RollPoint.TopOfMonth, InvokeComputeCheckPeriod(rfa, ".yyyy-MM"), "TopOfMonth pattern"); + + // Test invalid roll point + Assert.AreEqual(RollPoint.InvalidRollPoint, InvokeComputeCheckPeriod(rfa, "..."), "TopOfMonth pattern"); + } + + private static RollPoint InvokeComputeCheckPeriod(RollingFileAppender rollingFileAppender, string datePattern) + { + return (RollPoint)Utils.InvokeMethod(rollingFileAppender, "ComputeCheckPeriod", datePattern); + } + } +} \ No newline at end of file diff --git a/src/log4net/Appender/RollingFileAppender.cs b/src/log4net/Appender/RollingFileAppender.cs index f53dd788..d6aef101 100644 --- a/src/log4net/Appender/RollingFileAppender.cs +++ b/src/log4net/Appender/RollingFileAppender.cs @@ -470,7 +470,7 @@ public RollingMode RollingStyle m_rollDate = false; m_rollSize = false; - this.AppendToFile = false; + AppendToFile = false; break; case RollingMode.Size: @@ -623,7 +623,7 @@ protected virtual void AdjustFileBeforeAppend() #endif if (m_rollDate) { - DateTime n = m_dateTime.Now; + var n = m_dateTime.Now; if (n >= m_nextCheck) { m_now = n; @@ -729,7 +729,7 @@ protected string GetNextOutputFileName(string fileName) if (m_rollDate) { - fileName = CombinePath(fileName, m_now.ToString(m_datePattern, System.Globalization.DateTimeFormatInfo.InvariantInfo)); + fileName = CombinePath(fileName, m_now.ToString(m_datePattern, DateTimeFormatInfo.InvariantInfo)); } if (m_countDirection >= 0) @@ -757,11 +757,11 @@ private void DetermineCurSizeRollBackups() using(SecurityContext.Impersonate(this)) { - fullPath = System.IO.Path.GetFullPath(m_baseFileName); - fileName = System.IO.Path.GetFileName(fullPath); + fullPath = Path.GetFullPath(m_baseFileName); + fileName = Path.GetFileName(fullPath); } - ArrayList arrayFiles = GetExistingFiles(fullPath); + var arrayFiles = GetExistingFiles(fullPath); InitializeRollBackups(fileName, arrayFiles); LogLog.Debug(declaringType, "curSizeRollBackups starts at ["+m_curSizeRollBackups+"]"); @@ -793,26 +793,26 @@ private string GetWildcardPatternForFile(string baseFileName) /// <returns></returns> private ArrayList GetExistingFiles(string baseFilePath) { - ArrayList alFiles = new ArrayList(); + var alFiles = new ArrayList(); string directory = null; using(SecurityContext.Impersonate(this)) { - string fullPath = Path.GetFullPath(baseFilePath); + var fullPath = Path.GetFullPath(baseFilePath); directory = Path.GetDirectoryName(fullPath); if (Directory.Exists(directory)) { - string baseFileName = Path.GetFileName(fullPath); + var baseFileName = Path.GetFileName(fullPath); - string[] files = Directory.GetFiles(directory, GetWildcardPatternForFile(baseFileName)); + var files = Directory.GetFiles(directory, GetWildcardPatternForFile(baseFileName)); if (files != null) { - for (int i = 0; i < files.Length; i++) + for (var i = 0; i < files.Length; i++) { - string curFileName = Path.GetFileName(files[i]); + var curFileName = Path.GetFileName(files[i]); if (curFileName.StartsWith(Path.GetFileNameWithoutExtension(baseFileName))) { alFiles.Add(curFileName); @@ -849,11 +849,11 @@ private void RollOverIfDateBoundaryCrossing() } #endif } - LogLog.Debug(declaringType, "["+last.ToString(m_datePattern,System.Globalization.DateTimeFormatInfo.InvariantInfo)+"] vs. ["+m_now.ToString(m_datePattern,System.Globalization.DateTimeFormatInfo.InvariantInfo)+"]"); + LogLog.Debug(declaringType, "["+last.ToString(m_datePattern,DateTimeFormatInfo.InvariantInfo)+"] vs. ["+m_now.ToString(m_datePattern,DateTimeFormatInfo.InvariantInfo)+"]"); - if (!(last.ToString(m_datePattern,System.Globalization.DateTimeFormatInfo.InvariantInfo).Equals(m_now.ToString(m_datePattern, System.Globalization.DateTimeFormatInfo.InvariantInfo)))) + if (!(last.ToString(m_datePattern,DateTimeFormatInfo.InvariantInfo).Equals(m_now.ToString(m_datePattern, DateTimeFormatInfo.InvariantInfo)))) { - m_scheduledFilename = CombinePath(m_baseFileName, last.ToString(m_datePattern, System.Globalization.DateTimeFormatInfo.InvariantInfo)); + m_scheduledFilename = CombinePath(m_baseFileName, last.ToString(m_datePattern, DateTimeFormatInfo.InvariantInfo)); LogLog.Debug(declaringType, "Initial roll over to ["+m_scheduledFilename+"]"); RollOverTime(false); LogLog.Debug(declaringType, "curSizeRollBackups after rollOver at ["+m_curSizeRollBackups+"]"); @@ -881,29 +881,33 @@ protected void ExistingInit() RollOverIfDateBoundaryCrossing(); // If file exists and we are not appending then roll it out of the way - if (AppendToFile == false) + if (AppendToFile != false) { - bool fileExists = false; - string fileName = GetNextOutputFileName(m_baseFileName); + return; + } - using(SecurityContext.Impersonate(this)) - { - fileExists = System.IO.File.Exists(fileName); - } + bool fileExists; + var fileName = GetNextOutputFileName(m_baseFileName); - if (fileExists) - { - if (m_maxSizeRollBackups == 0) - { - LogLog.Debug(declaringType, "Output file ["+fileName+"] already exists. MaxSizeRollBackups is 0; cannot roll. Overwriting existing file."); - } - else - { - LogLog.Debug(declaringType, "Output file ["+fileName+"] already exists. Not appending to file. Rolling existing file out of the way."); + using(SecurityContext.Impersonate(this)) + { + fileExists = System.IO.File.Exists(fileName); + } - RollOverRenameFiles(fileName); - } - } + if (!fileExists) + { + return; + } + + if (m_maxSizeRollBackups == 0) + { + LogLog.Debug(declaringType, "Output file ["+fileName+"] already exists. MaxSizeRollBackups is 0; cannot roll. Overwriting existing file."); + } + else + { + LogLog.Debug(declaringType, "Output file ["+fileName+"] already exists. Not appending to file. Rolling existing file out of the way."); + + RollOverRenameFiles(fileName); } } @@ -918,6 +922,8 @@ protected void ExistingInit() /// <param name="curFileName"></param> private void InitializeFromOneFile(string baseFile, string curFileName) { + curFileName = curFileName.ToLower(); + baseFile = baseFile.ToLower(); if (curFileName.StartsWith(Path.GetFileNameWithoutExtension(baseFile)) == false) { // This is not a log file, so ignore @@ -929,24 +935,16 @@ private void InitializeFromOneFile(string baseFile, string curFileName) return; } - /* - if (m_staticLogFileName) - { - int endLength = curFileName.Length - index; - if (baseFile.Length + endLength != curFileName.Length) - { - // file is probably scheduledFilename + .x so I don't care - return; - } - } - */ - // Only look for files in the current roll point if (m_rollDate && !m_staticLogFileName) { - string date = m_dateTime.Now.ToString(m_datePattern, System.Globalization.DateTimeFormatInfo.InvariantInfo); - string prefix = m_preserveLogFileNameExtension ? Path.GetFileNameWithoutExtension(baseFile) + date : baseFile + date; - string suffix = m_preserveLogFileNameExtension ? Path.GetExtension(baseFile) : ""; + var date = m_dateTime.Now.ToString(m_datePattern, DateTimeFormatInfo.InvariantInfo).ToLower(); + var prefix = (m_preserveLogFileNameExtension + ? Path.GetFileNameWithoutExtension(baseFile) + date + : baseFile + date).ToLower(); + var suffix = m_preserveLogFileNameExtension + ? Path.GetExtension(baseFile).ToLower() + : ""; if (!curFileName.StartsWith(prefix) || !curFileName.EndsWith(suffix)) { LogLog.Debug(declaringType, "Ignoring file ["+curFileName+"] because it is from a different date period"); @@ -957,7 +955,7 @@ private void InitializeFromOneFile(string baseFile, string curFileName) try { // Bump the counter up to the highest count seen so far - int backup = GetBackUpIndex(curFileName); + var backup = GetBackUpIndex(curFileName); // caution: we might get a false positive when certain // date patterns such as yyyyMMdd are used...those are @@ -1013,15 +1011,15 @@ private void InitializeFromOneFile(string baseFile, string curFileName) /// <returns></returns> private int GetBackUpIndex(string curFileName) { - int backUpIndex = -1; - string fileName = curFileName; + var backUpIndex = -1; + var fileName = curFileName; if (m_preserveLogFileNameExtension) { fileName = Path.GetFileNameWithoutExtension(fileName); } - int index = fileName.LastIndexOf("."); + var index = fileName.LastIndexOf("."); if (index > 0) { // if the "yyyy-MM-dd" component of file.log.yyyy-MM-dd is passed to TryParse @@ -1043,7 +1041,7 @@ private void InitializeRollBackups(string baseFile, ArrayList arrayFiles) { if (null != arrayFiles) { - string baseFileLower = baseFile.ToLowerInvariant(); + var baseFileLower = baseFile.ToLowerInvariant(); foreach(string curFileName in arrayFiles) { @@ -1072,13 +1070,13 @@ private RollPoint ComputeCheckPeriod(string datePattern) // purposes to calculate the resolution of the date pattern. // Get string representation of base line date - string r0 = s_date1970.ToString(datePattern, System.Globalization.DateTimeFormatInfo.InvariantInfo); + var r0 = s_date1970.ToString(datePattern, DateTimeFormatInfo.InvariantInfo); // Check each type of rolling mode starting with the smallest increment. - for(int i = (int)RollPoint.TopOfMinute; i <= (int)RollPoint.TopOfMonth; i++) + for(var 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); + var r1 = NextCheckDate(s_date1970, (RollPoint)i).ToString(datePattern, DateTimeFormatInfo.InvariantInfo); LogLog.Debug(declaringType, "Type = ["+i+"], r0 = ["+r0+"], r1 = ["+r1+"]"); @@ -1169,7 +1167,7 @@ public override void ActivateOptions() if (m_rollDate && File != null && m_scheduledFilename == null) { - m_scheduledFilename = CombinePath(File, m_now.ToString(m_datePattern, System.Globalization.DateTimeFormatInfo.InvariantInfo)); + m_scheduledFilename = CombinePath(File, m_now.ToString(m_datePattern, DateTimeFormatInfo.InvariantInfo)); } ExistingInit(); @@ -1189,7 +1187,7 @@ public override void ActivateOptions() /// <returns></returns> private string CombinePath(string path1, string path2) { - string extension = Path.GetExtension(path1); + var extension = Path.GetExtension(path1); if (m_preserveLogFileNameExtension && extension.Length > 0) { return Path.Combine(Path.GetDirectoryName(path1), Path.GetFileNameWithoutExtension(path1) + path2 + extension); @@ -1225,7 +1223,7 @@ protected void RollOverTime(bool fileIsOpen) //is the new file name equivalent to the 'current' one //something has gone wrong if we hit this -- we should only //roll over if the new file will be different from the old - string dateFormat = m_now.ToString(m_datePattern, System.Globalization.DateTimeFormatInfo.InvariantInfo); + var dateFormat = m_now.ToString(m_datePattern, DateTimeFormatInfo.InvariantInfo); if (m_scheduledFilename.Equals(CombinePath(File, dateFormat))) { ErrorHandler.Error("Compare " + m_scheduledFilename + " : " + CombinePath(File, dateFormat)); @@ -1235,14 +1233,14 @@ protected void RollOverTime(bool fileIsOpen) if (fileIsOpen) { // close current file, and rename it to datedFilename - this.CloseFile(); + CloseFile(); } //we may have to roll over a large number of backups here - for (int i = 1; i <= m_curSizeRollBackups; i++) + for (var i = 1; i <= m_curSizeRollBackups; i++) { - string from = CombinePath(File, "." + i); - string to = CombinePath(m_scheduledFilename, "." + i); + var from = CombinePath(File, "." + i); + var to = CombinePath(m_scheduledFilename, "." + i); RollFile(from, to); } @@ -1253,7 +1251,7 @@ protected void RollOverTime(bool fileIsOpen) m_curSizeRollBackups = 0; //new scheduled name - m_scheduledFilename = CombinePath(File, m_now.ToString(m_datePattern, System.Globalization.DateTimeFormatInfo.InvariantInfo)); + m_scheduledFilename = CombinePath(File, m_now.ToString(m_datePattern, DateTimeFormatInfo.InvariantInfo)); if (fileIsOpen) { @@ -1336,11 +1334,11 @@ protected void DeleteFile(string fileName) { // We may not have permission to delete the file, or the file may be locked - string fileToDelete = fileName; + var fileToDelete = fileName; // Try to move the file to temp name. // If the file is locked we may still be able to move it - string tempFileName = fileName + "." + Environment.TickCount + ".DeletePending"; + var tempFileName = fileName + "." + Environment.TickCount + ".DeletePending"; try { using(SecurityContext.Impersonate(this)) @@ -1408,7 +1406,7 @@ protected void DeleteFile(string fileName) /// </remarks> protected void RollOverSize() { - this.CloseFile(); // keep windows happy. + CloseFile(); // keep windows happy. LogLog.Debug(declaringType, "rolling over count ["+((CountingQuietTextWriter)QuietWriter).Count+"]"); LogLog.Debug(declaringType, "maxSizeRollBackups ["+m_maxSizeRollBackups+"]"); @@ -1467,7 +1465,7 @@ protected void RollOverRenameFiles(string baseFileName) } // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2} - for (int i = m_curSizeRollBackups; i >= 1; i--) + for (var i = m_curSizeRollBackups; i >= 1; i--) { RollFile((CombinePath(baseFileName, "." + i)), (CombinePath(baseFileName, "." + (i + 1)))); } @@ -1483,7 +1481,7 @@ protected void RollOverRenameFiles(string baseFileName) if (m_curSizeRollBackups >= m_maxSizeRollBackups && m_maxSizeRollBackups > 0) { //delete the first and keep counting up. - int oldestFileIndex = m_curSizeRollBackups - m_maxSizeRollBackups; + var oldestFileIndex = m_curSizeRollBackups - m_maxSizeRollBackups; // If static then there is 1 file without a number, therefore 1 less archive if (m_staticLogFileName) @@ -1494,14 +1492,14 @@ protected void RollOverRenameFiles(string baseFileName) // If using a static log file then the base for the numbered sequence is the baseFileName passed in // If not using a static log file then the baseFileName will already have a numbered postfix which // we must remove, however it may have a date postfix which we must keep! - string archiveFileBaseName = baseFileName; + var archiveFileBaseName = baseFileName; if (!m_staticLogFileName) { if (m_preserveLogFileNameExtension) { - string extension = Path.GetExtension(archiveFileBaseName); - string baseName = Path.GetFileNameWithoutExtension(archiveFileBaseName); - int lastDotIndex = baseName.LastIndexOf("."); + var extension = Path.GetExtension(archiveFileBaseName); + var baseName = Path.GetFileNameWithoutExtension(archiveFileBaseName); + var lastDotIndex = baseName.LastIndexOf("."); if (lastDotIndex >= 0) { archiveFileBaseName = baseName.Substring(0, lastDotIndex) + extension; @@ -1509,7 +1507,7 @@ protected void RollOverRenameFiles(string baseFileName) } else { - int lastDotIndex = archiveFileBaseName.LastIndexOf("."); + var lastDotIndex = archiveFileBaseName.LastIndexOf("."); if (lastDotIndex >= 0) { archiveFileBaseName = archiveFileBaseName.Substring(0, lastDotIndex); @@ -1554,7 +1552,7 @@ protected void RollOverRenameFiles(string baseFileName) protected DateTime NextCheckDate(DateTime currentDateTime, RollPoint rollPoint) { // Local variable to work on (this does not look very efficient) - DateTime current = currentDateTime; + var current = currentDateTime; // Do slightly different things depending on what the type of roll point we want. switch(rollPoint) diff --git a/src/log4net/Core/LoggingEvent.cs b/src/log4net/Core/LoggingEvent.cs index 26b76ba5..8abb555e 100644 --- a/src/log4net/Core/LoggingEvent.cs +++ b/src/log4net/Core/LoggingEvent.cs @@ -922,7 +922,7 @@ public string ThreadName public string UserName => m_data.UserName ??= TryGetCurrentUserName() ?? SystemInfo.NotAvailableText; - private static string TryGetCurrentUserName() + private string TryGetCurrentUserName() { #if (NETCF || SSCLI || NETSTANDARD1_3) // On compact framework there's no notion of current Windows user @@ -937,10 +937,8 @@ private static string TryGetCurrentUserName() try { - using (WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent()) - { - return windowsIdentity?.Name ?? ""; - } + return _cachedWindowsIdentityUserName ??= + TryReadWindowsIdentityUserName(); } catch (PlatformNotSupportedException) { @@ -963,6 +961,16 @@ private static string TryGetCurrentUserName() } #endif } + +#if (NETCF || SSCLI || NETSTANDARD1_3) +#else + private string _cachedWindowsIdentityUserName; + private static string TryReadWindowsIdentityUserName() + { + using var identity = WindowsIdentity.GetCurrent(); + return identity?.Name ?? ""; + } +#endif #if (NETCF || SSCLI || NETSTANDARD1_3) #else @@ -1464,10 +1472,23 @@ private void CreateCompositeProperties() // TODO: Add Repository Properties // event properties - var eventProperties = new PropertiesDictionary(); - eventProperties[UserNameProperty] = UserName; - eventProperties[IdentityProperty] = Identity; - compositeProperties.Add(eventProperties); + var shouldFixUserName = (m_fixFlags & FixFlags.UserName) != 0; + var shouldFixIdentity = (m_fixFlags & FixFlags.Identity) != 0; + if (shouldFixIdentity || shouldFixUserName) + { + var eventProperties = new PropertiesDictionary(); + if (shouldFixUserName) + { + eventProperties[UserNameProperty] = UserName; + } + + if (shouldFixIdentity) + { + eventProperties[IdentityProperty] = Identity; + } + + compositeProperties.Add(eventProperties); + } compositeProperties.Add(GlobalContext.Properties.GetReadOnlyProperties()); m_compositeProperties = compositeProperties;