niall 2005/03/11 10:21:56
Modified: src/Appender FileAppender.cs
tests/src log4net.Tests.csproj
tests/src/Appender RollingFileAppenderTest.cs
Log:
Implemented locking models so tat FileAppender and it's subclasses can change
their file access semantics.
Revision Changes Path
1.14 +378 -16 logging-log4net/src/Appender/FileAppender.cs
Index: FileAppender.cs
===================================================================
RCS file: /home/cvs/logging-log4net/src/Appender/FileAppender.cs,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -r1.13 -r1.14
--- FileAppender.cs 7 Mar 2005 01:34:45 -0000 1.13
+++ FileAppender.cs 11 Mar 2005 18:21:56 -0000 1.14
@@ -60,8 +60,268 @@
/// <author>Gert Driesen</author>
/// <author>Rodrigo B. de Oliveira</author>
/// <author>Douglas de la Torre</author>
- public class FileAppender : TextWriterAppender
+ /// <author>Niall Daley</author>
+ public class FileAppender : TextWriterAppender
{
+ #region Inner Classes
+ private sealed class LockingStream : Stream, IDisposable
+ {
+ public class LockStateException : Exception
+ {
+ public LockStateException(string message):
base(message){}
+ }
+
+ private Stream m_realStream=null;
+ private LockingModelBase m_lockingModel=null;
+
+ #region Stream methods
+ // Methods
+ public LockingStream(LockingModelBase locking) : base()
+ {
+ if (locking==null)
+ {
+ throw new ArgumentException("Locking
model may not be null","locking");
+ }
+ m_lockingModel=locking;
+ }
+ public override IAsyncResult BeginRead(byte[] buffer,
int offset, int count, AsyncCallback callback, object state)
+ {
+ AssertLocked();
+ IAsyncResult
ret=m_realStream.BeginRead(buffer,offset,count,callback,state);
+ EndRead(ret);
+ return ret;
+ }
+ public override IAsyncResult BeginWrite(byte[] buffer,
int offset, int count, AsyncCallback callback, object state)
+ {
+ AssertLocked();
+ IAsyncResult
ret=m_realStream.BeginWrite(buffer,offset,count,callback,state);
+ EndWrite(ret);
+ return ret;
+ }
+ public override void Close()
{m_lockingModel.CloseFile();}
+ public override int EndRead(IAsyncResult asyncResult)
{AssertLocked();return m_realStream.EndRead(asyncResult);}
+ public override void EndWrite(IAsyncResult asyncResult)
{AssertLocked();m_realStream.EndWrite(asyncResult);}
+ public override void Flush()
{AssertLocked();m_realStream.Flush();}
+ public override int Read(byte[] buffer, int offset, int
count) {AssertLocked();return m_realStream.Read(buffer,offset,count);}
+ public override int ReadByte() {AssertLocked();return
m_realStream.ReadByte();}
+ public override long Seek(long offset, SeekOrigin
origin) {AssertLocked();return m_realStream.Seek(offset,origin);}
+ public override void SetLength(long value)
{AssertLocked();m_realStream.SetLength(value);}
+ void IDisposable.Dispose() {this.Close();}
+ public override void Write(byte[] buffer, int offset,
int count) {AssertLocked();m_realStream.Write(buffer,offset,count);}
+ public override void WriteByte(byte value)
{AssertLocked();m_realStream.WriteByte(value);}
+
+ // Properties
+ public override bool CanRead { get
{AssertLocked();return m_realStream.CanRead;} }
+ public override bool CanSeek { get
{AssertLocked();return m_realStream.CanSeek;} }
+ public override bool CanWrite { get
{AssertLocked();return m_realStream.CanWrite;} }
+ public override long Length { get
{AssertLocked();return m_realStream.Length;} }
+ public override long Position { get
{AssertLocked();return m_realStream.Position;} set
{AssertLocked();m_realStream.Position=value;} }
+ #endregion
+
+ #region Locking Methods
+
+ private void AssertLocked()
+ {
+ if (m_realStream==null)
+ {
+ throw new LockStateException("The file
is not currently locked");
+ }
+ }
+
+ public void AquireLock()
+ {
+ lock(this)
+ {
+ if (m_realStream==null)
+ { // If lock is already aquired,
nop
+
m_realStream=m_lockingModel.AquireLock();
+ }
+ }
+ }
+
+ public void ReleaseLock()
+ {
+ lock(this)
+ {
+ if (m_realStream!=null)
+ { // If already unlocked, nop
+ m_lockingModel.ReleaseLock();
+ m_realStream=null;
+ }
+ }
+ }
+ #endregion
+ }
+
+ #region Locking Models
+
+ /// <summary>
+ /// Base class for the locking models available to the <see
cref="FileAppender"/> derived loggers
+ /// </summary>
+ public abstract class LockingModelBase
+ {
+ private IErrorHandler m_errorHandler=null;
+ /// <summary>
+ /// Open the file specified and prepare for logging. No
writes will be made until AquireLock is called.
+ /// </summary>
+ /// <param name="filename">The filename to use</param>
+ /// <param name="append">Whether to append to the file,
or overwrite</param>
+ /// <param name="encoding">The encoding to use</param>
+ public abstract void OpenFile(string filename, bool
append,Encoding encoding);
+
+ /// <summary>
+ /// Close the file. No further writes will be made.
+ /// </summary>
+ public abstract void CloseFile();
+
+ /// <summary>
+ /// Aquire the lock on the file in preparation for
writing to it. Return a stream pointing to the file.
+ /// </summary>
+ /// <returns>A stream that is ready to be written
to.</returns>
+ public abstract Stream AquireLock();
+
+ /// <summary>
+ /// Release the lock on the file. No further writes
will be made to the stream until AquireLock is called again.
+ /// </summary>
+ public abstract void ReleaseLock();
+
+ /// <summary>
+ /// Gets or sets the <see cref="IErrorHandler"/> for
this appender.
+ /// </summary>
+ /// <value>The <see cref="IErrorHandler"/> of the
appender</value>
+ /// <remarks>
+ /// <para>
+ /// The <see cref="AppenderSkeleton"/> provides a
default
+ /// implementation for the <see cref="ErrorHandler"/>
property.
+ /// </para>
+ /// </remarks>
+ virtual public IErrorHandler ErrorHandler
+ {
+ get { return this.m_errorHandler; }
+ set
+ {
+ lock(this)
+ {
+ if (value == null)
+ {
+ // We do not throw
exception here since the cause is probably a
+ // bad config file.
+
LogLog.Warn("AppenderSkeleton: You have tried to set a null error-handler.");
+ }
+ else
+ {
+ m_errorHandler = value;
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Open te file once for writing and hold it open until
CloseFile is called. Maintains an exclusive lock on the file during this time.
+ /// </summary>
+ public class ExclusiveLock : LockingModelBase
+ {
+ private Stream m_stream=null;
+
+ public override void OpenFile(string filename, bool
append,Encoding encoding)
+ {
+ try
+ {
+ // Ensure that the directory structure
exists
+ string directoryFullName =
Path.GetDirectoryName(filename);
+
+ // Only create the directory if it does
not exist
+ // doing this check here resolves some
permissions failures
+ if
(!Directory.Exists(directoryFullName))
+ {
+
Directory.CreateDirectory(directoryFullName);
+ }
+
+ FileMode fileOpenMode = append ?
FileMode.Append : FileMode.Create;
+ m_stream = new FileStream(filename,
fileOpenMode, FileAccess.Write, FileShare.Read);
+ }
+ catch (Exception e1)
+ {
+ ErrorHandler.Error("Unable to aquire
lock on file "+filename+". "+e1.Message);
+ }
+ }
+
+ public override void CloseFile()
+ {
+ m_stream.Close();
+ }
+
+ public override Stream AquireLock()
+ {
+ return m_stream;
+ }
+
+ public override void ReleaseLock()
+ {
+ //NOP
+ }
+ }
+
+ /// <summary>
+ /// Opens the file once for each AquireLock/ReleaseLock cycle,
thus holding the lock for the minimal amount of time. This method of locking
+ /// is considerably slower than <see
cref="FileAppender.ExclusiveLock"/> but allows other processes to move/delete
the log file whilst logging
+ /// continues.
+ /// </summary>
+ public class MinimalLock : LockingModelBase
+ {
+ private string m_filename;
+ private bool m_append;
+ private Stream m_stream=null;
+
+ public override void OpenFile(string filename, bool
append, Encoding encoding)
+ {
+ m_filename=filename;
+ m_append=append;
+ }
+
+ public override void CloseFile()
+ {
+ //NOP
+ }
+
+ public override Stream AquireLock()
+ {
+ if (m_stream==null)
+ {
+ try
+ {
+ // Ensure that the directory
structure exists
+ string directoryFullName =
Path.GetDirectoryName(m_filename);
+
+ // Only create the directory if
it does not exist
+ // doing this check here
resolves some permissions failures
+ if
(!Directory.Exists(directoryFullName))
+ {
+
Directory.CreateDirectory(directoryFullName);
+ }
+
+ FileMode fileOpenMode =
m_append ? FileMode.Append : FileMode.Create;
+ m_stream = new
FileStream(m_filename, fileOpenMode, FileAccess.Write, FileShare.Read);
+ m_append=true;
+ }
+ catch (Exception e1)
+ {
+ ErrorHandler.Error("Unable to
aquire lock on file "+m_filename+". "+e1.Message);
+ }
+ }
+ return m_stream;
+ }
+
+ public override void ReleaseLock()
+ {
+ m_stream.Close();
+ m_stream=null;
+ }
+ }
+ #endregion
+ #endregion
+
#region Public Instance Constructors
/// <summary>
@@ -192,6 +452,25 @@
set { m_securityContext = value; }
}
+ /// <summary>
+ /// Gets or sets the <see cref="FileAppender.LockingModel"/>
used to handle locking of the file. There are two
+ /// built in locking models, <see
cref="FileAppender.ExclusiveLock"/> and <see cref="FileAppender.MinimalLock"/>.
+ /// The former locks the file from the start of loggin to the
end and the later lock only for the minimal amount of time when loggin each
message.
+ /// </summary>
+ /// <value>
+ /// The <see cref="FileAppender.LockingModel"/> used to lock
the file.
+ /// </value>
+ /// <remarks>
+ /// <para>
+ /// Unless a value is specified here the <see
cref="FileAppender.ExclusiveLock"/> model is used.
+ /// </para>
+ /// </remarks>
+ public FileAppender.LockingModelBase LockingModel
+ {
+ get { return m_lockingmodel; }
+ set { m_lockingmodel = value;}
+ }
+
#endregion Public Instance Properties
#region Override implementation of AppenderSkeleton
@@ -224,6 +503,12 @@
m_securityContext =
SecurityContextProvider.DefaultProvider.CreateSecurityContext(this);
}
+ if (m_lockingmodel == null)
+ {
+ m_lockingmodel = new
FileAppender.ExclusiveLock();
+ }
+ m_lockingmodel.ErrorHandler=this.ErrorHandler;
+
using(SecurityContext.Impersonate(this))
{
m_fileName =
ConvertToFullPath(m_fileName.Trim());
@@ -272,6 +557,81 @@
SafeOpenFile(m_fileName, m_appendToFile);
}
+ /// <summary>
+ /// This method is called by the <see
cref="AppenderSkeleton.DoAppend"/>
+ /// method.
+ /// </summary>
+ /// <param name="loggingEvent">The event to log.</param>
+ /// <remarks>
+ /// <para>
+ /// Writes a log statement to the output stream if the output
stream exists
+ /// and is writable.
+ /// </para>
+ /// <para>
+ /// The format of the output will depend on the appender's
layout.
+ /// </para>
+ /// </remarks>
+ override protected void Append(LoggingEvent loggingEvent)
+ {
+ m_stream.AquireLock();
+ base.Append(loggingEvent);
+ m_stream.ReleaseLock();
+ }
+
+ /// <summary>
+ /// Writes a footer as produced by the embedded layout's <see
cref="ILayout.Footer"/> property.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Writes a footer as produced by the embedded layout's <see
cref="ILayout.Footer"/> property.
+ /// </para>
+ /// </remarks>
+ protected override void WriteFooter()
+ {
+ if (m_stream!=null)
+ { //WriteFooter can be called even before a file
is opened
+ m_stream.AquireLock();
+ base.WriteFooter();
+ m_stream.ReleaseLock();
+ }
+ }
+
+ /// <summary>
+ /// Writes a header produced by the embedded layout's <see
cref="ILayout.Header"/> property.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Writes a header produced by the embedded layout's <see
cref="ILayout.Header"/> property.
+ /// </para>
+ /// </remarks>
+ protected override void WriteHeader()
+ {
+ if (m_stream!=null)
+ {
+ m_stream.AquireLock();
+ base.WriteHeader();
+ m_stream.ReleaseLock();
+ }
+ }
+
+ /// <summary>
+ /// Closes the underlying <see cref="TextWriter"/>.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Closes the underlying <see cref="TextWriter"/>.
+ /// </para>
+ /// </remarks>
+ protected override void CloseWriter()
+ {
+ if (m_stream!=null)
+ {
+ m_stream.AquireLock();
+ base.CloseWriter();
+ m_stream.ReleaseLock();
+ }
+ }
+
#endregion Override implementation of TextWriterAppender
#region Public Instance Methods
@@ -357,27 +717,19 @@
// Save these for later, allowing retries if
file open fails
m_fileName = fileName;
m_appendToFile = append;
- FileStream fileStream = null;
using(SecurityContext.Impersonate(this))
{
- // Ensure that the directory structure
exists
- string directoryFullName =
Path.GetDirectoryName(fileName);
-
- // Only create the directory if it does
not exist
- // doing this check here resolves some
permissions failures
- if
(!Directory.Exists(directoryFullName))
- {
-
Directory.CreateDirectory(directoryFullName);
- }
-
- FileMode fileOpenMode = append ?
FileMode.Append : FileMode.Create;
- fileStream = new FileStream(fileName,
fileOpenMode, FileAccess.Write, FileShare.Read);
+
LockingModel.ErrorHandler=this.ErrorHandler;
+
LockingModel.OpenFile(fileName,append,m_encoding);
+ m_stream=new
LockingStream(LockingModel);
}
- if (fileStream != null)
+ if (m_stream != null)
{
- SetQWForFiles(new
StreamWriter(fileStream, m_encoding));
+ m_stream.AquireLock();
+ SetQWForFiles(new
StreamWriter(m_stream, m_encoding));
+ m_stream.ReleaseLock();
}
WriteHeader();
@@ -467,6 +819,16 @@
/// </summary>
private SecurityContext m_securityContext;
+ /// <summary>
+ /// The stream to log to. Has added locking semantics
+ /// </summary>
+ private FileAppender.LockingStream m_stream=null;
+
+ /// <summary>
+ /// The locking model to use
+ /// </summary>
+ private FileAppender.LockingModelBase m_lockingmodel=new
FileAppender.ExclusiveLock();
+
#endregion Private Instance Fields
}
}
1.8 +5 -0 logging-log4net/tests/src/log4net.Tests.csproj
Index: log4net.Tests.csproj
===================================================================
RCS file: /home/cvs/logging-log4net/tests/src/log4net.Tests.csproj,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -r1.7 -r1.8
--- log4net.Tests.csproj 19 Aug 2004 21:39:29 -0000 1.7
+++ log4net.Tests.csproj 11 Mar 2005 18:21:56 -0000 1.8
@@ -87,6 +87,11 @@
AssemblyName = "System.Runtime.Remoting"
HintPath =
"..\..\..\..\..\..\WINDOWS\Microsoft.NET\Framework\v1.0.3705\System.Runtime.Remoting.dll"
/>
+ <Reference
+ Name = "nunit.tests"
+ AssemblyName = "nunit.tests"
+ HintPath = "..\..\..\..\..\..\net\NUnit
2.0\bin\nunit.tests.dll"
+ />
</References>
</Build>
<Files>
1.6 +256 -0
logging-log4net/tests/src/Appender/RollingFileAppenderTest.cs
Index: RollingFileAppenderTest.cs
===================================================================
RCS file:
/home/cvs/logging-log4net/tests/src/Appender/RollingFileAppenderTest.cs,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -r1.5 -r1.6
--- RollingFileAppenderTest.cs 17 Jan 2005 21:40:52 -0000 1.5
+++ RollingFileAppenderTest.cs 11 Mar 2005 18:21:56 -0000 1.6
@@ -25,6 +25,7 @@
using log4net.Util;
using log4net.Layout;
using log4net.Core;
+using log4net.Config;
using NUnit.Framework;
using log4net.Repository;
@@ -48,6 +49,19 @@
CountingAppender _caRoot;
Logger _root;
+ private class SilentErrorHandler : IErrorHandler
+ {
+ System.Text.StringBuilder m_buffer=new
System.Text.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>
@@ -196,6 +210,17 @@
/// <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.
@@ -212,6 +237,8 @@
appender.MaxSizeRollBackups = _MaxSizeRollBackups;
appender.CountDirection = _iCountDirection;
appender.RollingStyle =
RollingFileAppender.RollingMode.Size;
+ appender.LockingModel =lockmodel;
+
appender.ActivateOptions();
return appender;
@@ -1307,8 +1334,237 @@
VerifyInitializeUpInfiniteExpectedValue( alFiles,
_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 ILogger CreateLogger(string
filename,FileAppender.LockingModelBase lockmodel, IErrorHandler handler)
+ {
+ log4net.Repository.Hierarchy.Hierarchy h =
(log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.CreateRepository("TestRepository");
+
+ log4net.Appender.RollingFileAppender appender = new
log4net.Appender.RollingFileAppender();
+ appender.File = filename;
+ appender.AppendToFile = false;
+ appender.CountDirection=0;
+
appender.RollingStyle=RollingFileAppender.RollingMode.Size;
+ appender.MaxFileSize=100000;
+ appender.Encoding=System.Text.Encoding.ASCII;
+ appender.ErrorHandler=handler;
+ if (lockmodel!=null) {appender.LockingModel=lockmodel;}
+
+ log4net.Layout.PatternLayout layout = new
log4net.Layout.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="RollingFileAppenderTests.CreateLogger"/>
+ /// </summary>
+ private void DestroyLogger()
+ {
+ log4net.Repository.Hierarchy.Hierarchy h =
(log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository("TestRepository");
+ h.ResetConfiguration();
+ //Replace the repository selector so that we can
recreate the hierarchy with the same name if necesary
+ LoggerManager.RepositorySelector=new
DefaultRepositorySelector(log4net.Util.SystemInfo.GetTypeFromString("log4net.Repository.Hierarchy.Hierarchy",true,true));
+ }
+
+ private void AssertFileEquals(string filename, string contents)
+ {
+ StreamReader sr=new StreamReader(filename);
+ string logcont=sr.ReadToEnd();
+ sr.Close();
+
+ Assertion.AssertEquals("Log contents is not what is
expected",contents,logcont);
+
+ System.IO.File.Delete(filename);
+ }
+
+ /// <summary>
+ /// Verifies that logging a messsage actually produces output
+ /// </summary>
+ [Test] public void TestLogOutput()
+ {
+ String filename="test.log";
+ SilentErrorHandler sh=new SilentErrorHandler();
+ ILogger log=CreateLogger(filename,new
FileAppender.ExclusiveLock(), sh);
+ log.Log(this.GetType(),Level.Info,"This is a
message",null);
+ log.Log(this.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);
+ Assertion.AssertEquals("Unexpeced error
message","",sh.Message);
+ }
+
+ /// <summary>
+ /// Verifies that attempting to log to a locked file fails
gracefully
+ /// </summary>
+ [Test] public void TestExclusiveLockFails()
+ {
+ String filename="test.log";
+
+ FileStream fs=new
FileStream(filename,FileMode.Create,FileAccess.Write,FileShare.None);
+
fs.Write(System.Text.Encoding.ASCII.GetBytes("Test"),0,4);
+
+ SilentErrorHandler sh=new SilentErrorHandler();
+ ILogger log=CreateLogger(filename,new
FileAppender.ExclusiveLock(),sh);
+ log.Log(this.GetType(),Level.Info,"This is a
message",null);
+ log.Log(this.GetType(),Level.Info,"This is a message
2",null);
+ DestroyLogger();
+ fs.Close();
+
+ AssertFileEquals(filename,"Test");
+ Assertion.AssertEquals("Expecting an error
message","Unable to aquire lock on file",sh.Message.Substring(0,29));
+ }
+
+ /// <summary>
+ /// Verifies that attempting to log to a locked file recovers
if the lock is released
+ /// </summary>
+ [Test] public void TestExclusiveLockRecovers()
+ {
+ String filename="test.log";
+ FileStream fs=new
FileStream(filename,FileMode.Create,FileAccess.Write,FileShare.None);
+
fs.Write(System.Text.Encoding.ASCII.GetBytes("Test"),0,4);
+ SilentErrorHandler sh=new SilentErrorHandler();
+ ILogger log=CreateLogger(filename,new
FileAppender.ExclusiveLock(),sh);
+ log.Log(this.GetType(),Level.Info,"This is a
message",null);
+ fs.Close();
+ log.Log(this.GetType(),Level.Info,"This is a message
2",null);
+ DestroyLogger();
+
+ AssertFileEquals(filename,"This is a message
2"+Environment.NewLine);
+ Assertion.AssertEquals("Expecting an error
message","Unable to aquire lock on file",sh.Message.Substring(0,29));
+ }
+
+ /// <summary>
+ /// Verifies that attempting to log to a file with
ExclusiveLock really locks the file
+ /// </summary>
+ [Test] public void TestExclusiveLockLocks()
+ {
+ String filename="test.log";
+ bool locked=false;
+
+ SilentErrorHandler sh=new SilentErrorHandler();
+ ILogger log=CreateLogger(filename,new
FileAppender.ExclusiveLock(),sh);
+ log.Log(this.GetType(),Level.Info,"This is a
message",null);
+
+ try
+ {
+ FileStream fs=new
FileStream(filename,FileMode.Create,FileAccess.Write,FileShare.None);
+
fs.Write(System.Text.Encoding.ASCII.GetBytes("Test"),0,4);
+ fs.Close();
+ }
+ catch (System.IO.IOException e1)
+ {
+ Assertion.AssertEquals("Unexpected
exception","The process cannot access the file ",e1.Message.Substring(0,35));
+ locked=true;
+ }
+
+ log.Log(this.GetType(),Level.Info,"This is a message
2",null);
+ DestroyLogger();
+
+ Assertion.Assert("File was not locked",locked);
+ AssertFileEquals(filename,"This is a
message"+Environment.NewLine+"This is a message 2"+Environment.NewLine);
+ Assertion.AssertEquals("Unexpected error
message","",sh.Message);
+ }
+
+
+ /// <summary>
+ /// Verifies that attempting to log to a locked file fails
gracefully
+ /// </summary>
+ [Test] public void TestMinimalLockFails()
+ {
+ String filename="test.log";
+
+ FileStream fs=new
FileStream(filename,FileMode.Create,FileAccess.Write,FileShare.None);
+
fs.Write(System.Text.Encoding.ASCII.GetBytes("Test"),0,4);
+
+ SilentErrorHandler sh=new SilentErrorHandler();
+ ILogger log=CreateLogger(filename,new
FileAppender.MinimalLock(),sh);
+ log.Log(this.GetType(),Level.Info,"This is a
message",null);
+ log.Log(this.GetType(),Level.Info,"This is a message
2",null);
+ DestroyLogger();
+ fs.Close();
+
+ AssertFileEquals(filename,"Test");
+ Assertion.AssertEquals("Expecting an error
message","Unable to aquire lock on file",sh.Message.Substring(0,29));
+ }
+
+ /// <summary>
+ /// Verifies that attempting to log to a locked file recovers
if the lock is released
+ /// </summary>
+ [Test] public void TestMinimalLockRecovers()
+ {
+ String filename="test.log";
+
+ FileStream fs=new
FileStream(filename,FileMode.Create,FileAccess.Write,FileShare.None);
+
fs.Write(System.Text.Encoding.ASCII.GetBytes("Test"),0,4);
+
+ SilentErrorHandler sh=new SilentErrorHandler();
+ ILogger log=CreateLogger(filename,new
FileAppender.MinimalLock(),sh);
+ log.Log(this.GetType(),Level.Info,"This is a
message",null);
+ fs.Close();
+ log.Log(this.GetType(),Level.Info,"This is a message
2",null);
+ DestroyLogger();
+
+ AssertFileEquals(filename,"This is a message
2"+Environment.NewLine);
+ Assertion.AssertEquals("Expecting an error
message","Unable to aquire lock on file",sh.Message.Substring(0,29));
+ }
+
+ /// <summary>
+ /// Verifies that attempting to log to a file with
ExclusiveLock really locks the file
+ /// </summary>
+ [Test] public void TestMinimalLockUnlocks()
+ {
+ String filename="test.log";
+ bool locked=false;
+
+ SilentErrorHandler sh=new SilentErrorHandler();
+ ILogger log=CreateLogger(filename,new
FileAppender.MinimalLock(),sh);
+ log.Log(this.GetType(),Level.Info,"This is a
message",null);
+
+ locked=true;
+ FileStream fs=new
FileStream(filename,FileMode.Append,FileAccess.Write,FileShare.None);
+
fs.Write(System.Text.Encoding.ASCII.GetBytes("Test"+Environment.NewLine),0,4+Environment.NewLine.Length);
+ fs.Close();
+
+ log.Log(this.GetType(),Level.Info,"This is a message
2",null);
+ DestroyLogger();
+
+ Assertion.Assert("File was not locked",locked);
+ AssertFileEquals(filename,"This is a
message"+Environment.NewLine+"Test"+Environment.NewLine+"This is a message
2"+Environment.NewLine);
+ Assertion.AssertEquals("Unexpected error
message","",sh.Message);
+ }
+
+ /// <summary>
+ /// Verify that the default LockModel is ExclusiveLock, to
maintain backwards compatability with previous behaviour
+ /// </summary>
+ [Test] public void TestDefaultLockingModel()
+ {
+ String filename="test.log";
+ SilentErrorHandler sh=new SilentErrorHandler();
+ ILogger log=CreateLogger(filename,null,sh);
+
+ IAppender[] appenders=log.Repository.GetAppenders();
+ Assertion.AssertEquals("The wrong number of appenders
are configured",1,appenders.Length);
+
+ RollingFileAppender
rfa=(RollingFileAppender)(appenders[0]);
+ Assertion.AssertEquals("The LockingModel is of an
unexpected
type",log4net.Util.SystemInfo.GetTypeFromString("log4net.Appender.FileAppender+ExclusiveLock",true,true),rfa.LockingModel.GetType());
+ }
+
/// <summary>
/// Tests the count up case, with infinite max backups , to see
that
/// initialization of the rolling file appender results in the
expected value