#region Copyright & License // // Copyright 2001-2006 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #endregion using System; using System.IO; using System.Text; using log4net.Util; using log4net.Layout; using log4net.Core; namespace log4net.Appender { /// /// Appends logging events to a file. /// /// /// /// Logging events are sent to the file specified by /// the property. /// /// /// The file can be opened in either append or overwrite mode /// by specifying the property. /// If the file path is relative it is taken as relative from /// the application base directory. The file encoding can be /// specified by setting the property. /// /// /// The layout's and /// values will be written each time the file is opened and closed /// respectively. If the property is /// then the file may contain multiple copies of the header and footer. /// /// /// This appender will first try to open the file for writing when /// is called. This will typically be during configuration. /// If the file cannot be opened for writing the appender will attempt /// to open the file again each time a message is logged to the appender. /// If the file cannot be opened for writing when a message is logged then /// the message will be discarded by this appender. /// /// /// The supports pluggable file locking models via /// the property. /// The default behavior, implemented by /// is to obtain an exclusive write lock on the file until this appender is closed. /// The alternative model, , only holds a /// write lock while the appender is writing a logging event. /// /// /// Nicko Cadell /// Gert Driesen /// Rodrigo B. de Oliveira /// Douglas de la Torre /// Niall Daley public class FileAppender : TextWriterAppender { #region LockingStream Inner Class /// /// Write only that uses the /// to manage access to an underlying resource. /// private sealed class LockingStream : Stream, IDisposable { public sealed class LockStateException : LogException { public LockStateException(string message): base(message) { } } private Stream m_realStream=null; private LockingModelBase m_lockingModel=null; private int m_readTotal=-1; private int m_lockLevel=0; public LockingStream(LockingModelBase locking) : base() { if (locking==null) { throw new ArgumentException("Locking model may not be null","locking"); } m_lockingModel=locking; } #region Override Implementation of Stream // Methods 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); m_readTotal=EndRead(ret); return ret; } /// /// True asynchronous writes are not supported, the implementation forces a synchronous write. /// 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_readTotal; } public override void EndWrite(IAsyncResult asyncResult) { //No-op, it has already been handled } public override void Flush() { AssertLocked(); m_realStream.Flush(); } public override int Read(byte[] buffer, int offset, int count) { return m_realStream.Read(buffer,offset,count); } public override int ReadByte() { 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 { return false; } } 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 Override Implementation of Stream #region Locking Methods private void AssertLocked() { if (m_realStream == null) { throw new LockStateException("The file is not currently locked"); } } public bool AcquireLock() { bool ret=false; lock(this) { if (m_lockLevel==0) { // If lock is already acquired, nop m_realStream=m_lockingModel.AcquireLock(); } if (m_realStream!=null) { m_lockLevel++; ret=true; } } return ret; } public void ReleaseLock() { lock(this) { m_lockLevel--; if (m_lockLevel==0) { // If already unlocked, nop m_lockingModel.ReleaseLock(); m_realStream=null; } } } #endregion Locking Methods } #endregion LockingStream Inner Class #region Locking Models /// /// Locking model base class /// /// /// /// Base class for the locking models available to the derived loggers. /// /// public abstract class LockingModelBase { private FileAppender m_appender=null; /// /// Open the output file /// /// The filename to use /// Whether to append to the file, or overwrite /// The encoding to use /// /// /// Open the file specified and prepare for logging. /// No writes will be made until is called. /// Must be called before any calls to , /// and . /// /// public abstract void OpenFile(string filename, bool append,Encoding encoding); /// /// Close the file /// /// /// /// Close the file. No further writes will be made. /// /// public abstract void CloseFile(); /// /// Acquire the lock on the file /// /// A stream that is ready to be written to. /// /// /// Acquire the lock on the file in preparation for writing to it. /// Return a stream pointing to the file. /// must be called to release the lock on the output file. /// /// public abstract Stream AcquireLock(); /// /// Release the lock on the file /// /// /// /// Release the lock on the file. No further writes will be made to the /// stream until is called again. /// /// public abstract void ReleaseLock(); /// /// Gets or sets the for this LockingModel /// /// /// The for this LockingModel /// /// /// /// The file appender this locking model is attached to and working on /// behalf of. /// /// /// The file appender is used to locate the security context and the error handler to use. /// /// /// The value of this property will be set before is /// called. /// /// public FileAppender CurrentAppender { get { return m_appender; } set { m_appender = value; } } } /// /// Hold an exclusive lock on the output file /// /// /// /// Open the file once for writing and hold it open until is called. /// Maintains an exclusive lock on the file during this time. /// /// public class ExclusiveLock : LockingModelBase { private Stream m_stream = null; /// /// Open the file specified and prepare for logging. /// /// The filename to use /// Whether to append to the file, or overwrite /// The encoding to use /// /// /// Open the file specified and prepare for logging. /// No writes will be made until is called. /// Must be called before any calls to , /// and . /// /// public override void OpenFile(string filename, bool append,Encoding encoding) { try { using(CurrentAppender.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; m_stream = new FileStream(filename, fileOpenMode, FileAccess.Write, FileShare.Read); } } catch (Exception e1) { CurrentAppender.ErrorHandler.Error("Unable to acquire lock on file "+filename+". "+e1.Message); } } /// /// Close the file /// /// /// /// Close the file. No further writes will be made. /// /// public override void CloseFile() { using(CurrentAppender.SecurityContext.Impersonate(this)) { m_stream.Close(); } } /// /// Acquire the lock on the file /// /// A stream that is ready to be written to. /// /// /// Does nothing. The lock is already taken /// /// public override Stream AcquireLock() { return m_stream; } /// /// Release the lock on the file /// /// /// /// Does nothing. The lock will be released when the file is closed. /// /// public override void ReleaseLock() { //NOP } } /// /// Acquires the file lock for each write /// /// /// /// Opens the file once for each / cycle, /// thus holding the lock for the minimal amount of time. This method of locking /// is considerably slower than but allows /// other processes to move/delete the log file whilst logging continues. /// /// public class MinimalLock : LockingModelBase { private string m_filename; private bool m_append; private Stream m_stream=null; /// /// Prepares to open the file when the first message is logged. /// /// The filename to use /// Whether to append to the file, or overwrite /// The encoding to use /// /// /// Open the file specified and prepare for logging. /// No writes will be made until is called. /// Must be called before any calls to , /// and . /// /// public override void OpenFile(string filename, bool append, Encoding encoding) { m_filename=filename; m_append=append; } /// /// Close the file /// /// /// /// Close the file. No further writes will be made. /// /// public override void CloseFile() { // NOP } /// /// Acquire the lock on the file /// /// A stream that is ready to be written to. /// /// /// Acquire the lock on the file in preparation for writing to it. /// Return a stream pointing to the file. /// must be called to release the lock on the output file. /// /// public override Stream AcquireLock() { if (m_stream==null) { try { using(CurrentAppender.SecurityContext.Impersonate(this)) { // 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) { CurrentAppender.ErrorHandler.Error("Unable to acquire lock on file "+m_filename+". "+e1.Message); } } return m_stream; } /// /// Release the lock on the file /// /// /// /// Release the lock on the file. No further writes will be made to the /// stream until is called again. /// /// public override void ReleaseLock() { using(CurrentAppender.SecurityContext.Impersonate(this)) { m_stream.Close(); m_stream=null; } } } #endregion Locking Models #region Public Instance Constructors /// /// Default constructor /// /// /// /// Default constructor /// /// public FileAppender() { } /// /// Construct a new appender using the layout, file and append mode. /// /// the layout to use with this appender /// the full path to the file to write to /// flag to indicate if the file should be appended to /// /// /// Obsolete constructor. /// /// [Obsolete("Instead use the default constructor and set the Layout, File & AppendToFile properties")] public FileAppender(ILayout layout, string filename, bool append) { Layout = layout; File = filename; AppendToFile = append; ActivateOptions(); } /// /// Construct a new appender using the layout and file specified. /// The file will be appended to. /// /// the layout to use with this appender /// the full path to the file to write to /// /// /// Obsolete constructor. /// /// [Obsolete("Instead use the default constructor and set the Layout & File properties")] public FileAppender(ILayout layout, string filename) : this(layout, filename, true) { } #endregion Public Instance Constructors #region Public Instance Properties /// /// Gets or sets the path to the file that logging will be written to. /// /// /// The path to the file that logging will be written to. /// /// /// /// If the path is relative it is taken as relative from /// the application base directory. /// /// virtual public string File { get { return m_fileName; } set { m_fileName = value; } } /// /// Gets or sets a flag that indicates whether the file should be /// appended to or overwritten. /// /// /// Indicates whether the file should be appended to or overwritten. /// /// /// /// If the value is set to false then the file will be overwritten, if /// it is set to true then the file will be appended to. /// /// The default value is true. /// public bool AppendToFile { get { return m_appendToFile; } set { m_appendToFile = value; } } /// /// Gets or sets used to write to the file. /// /// /// The used to write to the file. /// /// /// /// The default encoding set is /// which is the encoding for the system's current ANSI code page. /// /// public Encoding Encoding { get { return m_encoding; } set { m_encoding = value; } } /// /// Gets or sets the used to write to the file. /// /// /// The used to write to the file. /// /// /// /// Unless a specified here for this appender /// the is queried for the /// security context to use. The default behavior is to use the security context /// of the current thread. /// /// public SecurityContext SecurityContext { get { return m_securityContext; } set { m_securityContext = value; } } /// /// Gets or sets the used to handle locking of the file. /// /// /// The used to lock the file. /// /// /// /// Gets or sets the used to handle locking of the file. /// /// /// There are two built in locking models, and . /// The former locks the file from the start of logging to the end and the /// later lock only for the minimal amount of time when logging each message. /// /// /// The default locking model is the . /// /// public FileAppender.LockingModelBase LockingModel { get { return m_lockingModel; } set { m_lockingModel = value; } } #endregion Public Instance Properties #region Override implementation of AppenderSkeleton /// /// Activate the options on the file appender. /// /// /// /// This is part of the delayed object /// activation scheme. The method must /// be called on this object after the configuration properties have /// been set. Until is called this /// object is in an undefined state and must not be used. /// /// /// If any of the configuration properties are modified then /// must be called again. /// /// /// This will cause the file to be opened. /// /// override public void ActivateOptions() { base.ActivateOptions(); if (m_securityContext == null) { m_securityContext = SecurityContextProvider.DefaultProvider.CreateSecurityContext(this); } if (m_lockingModel == null) { m_lockingModel = new FileAppender.ExclusiveLock(); } m_lockingModel.CurrentAppender=this; using(SecurityContext.Impersonate(this)) { m_fileName = ConvertToFullPath(m_fileName.Trim()); } if (m_fileName != null) { SafeOpenFile(m_fileName, m_appendToFile); } else { LogLog.Warn("FileAppender: File option not set for appender ["+Name+"]."); LogLog.Warn("FileAppender: Are you using FileAppender instead of ConsoleAppender?"); } } #endregion Override implementation of AppenderSkeleton #region Override implementation of TextWriterAppender /// /// Closes any previously opened file and calls the parent's . /// /// /// /// Resets the filename and the file stream. /// /// override protected void Reset() { base.Reset(); m_fileName = null; } /// /// Called to initialize the file writer /// /// /// /// Will be called for each logged message until the file is /// successfully opened. /// /// override protected void PrepareWriter() { SafeOpenFile(m_fileName, m_appendToFile); } /// /// This method is called by the /// method. /// /// The event to log. /// /// /// Writes a log statement to the output stream if the output stream exists /// and is writable. /// /// /// The format of the output will depend on the appender's layout. /// /// override protected void Append(LoggingEvent loggingEvent) { if (m_stream.AcquireLock()) { try { base.Append(loggingEvent); } finally { m_stream.ReleaseLock(); } } } /// /// This method is called by the /// method. /// /// The array of events to log. /// /// /// Acquires the output file locks once before writing all the events to /// the stream. /// /// override protected void Append(LoggingEvent[] loggingEvents) { if (m_stream.AcquireLock()) { try { base.Append(loggingEvents); } finally { m_stream.ReleaseLock(); } } } /// /// Writes a footer as produced by the embedded layout's property. /// /// /// /// Writes a footer as produced by the embedded layout's property. /// /// protected override void WriteFooter() { if (m_stream!=null) { //WriteFooter can be called even before a file is opened m_stream.AcquireLock(); try { base.WriteFooter(); } finally { m_stream.ReleaseLock(); } } } /// /// Writes a header produced by the embedded layout's property. /// /// /// /// Writes a header produced by the embedded layout's property. /// /// protected override void WriteHeader() { if (m_stream!=null) { if (m_stream.AcquireLock()) { try { base.WriteHeader(); } finally { m_stream.ReleaseLock(); } } } } /// /// Closes the underlying . /// /// /// /// Closes the underlying . /// /// protected override void CloseWriter() { if (m_stream!=null) { m_stream.AcquireLock(); try { base.CloseWriter(); } finally { m_stream.ReleaseLock(); } } } #endregion Override implementation of TextWriterAppender #region Public Instance Methods /// /// Closes the previously opened file. /// /// /// /// Writes the to the file and then /// closes the file. /// /// protected void CloseFile() { WriteFooterAndCloseWriter(); } #endregion Public Instance Methods #region Protected Instance Methods /// /// Sets and opens the file where the log output will go. The specified file must be writable. /// /// The path to the log file. Must be a fully qualified path. /// If true will append to fileName. Otherwise will truncate fileName /// /// /// Calls but guarantees not to throw an exception. /// Errors are passed to the . /// /// virtual protected void SafeOpenFile(string fileName, bool append) { try { OpenFile(fileName, append); } catch(Exception e) { ErrorHandler.Error("OpenFile("+fileName+","+append+") call failed.", e, ErrorCode.FileOpenFailure); } } /// /// Sets and opens the file where the log output will go. The specified file must be writable. /// /// The path to the log file. Must be a fully qualified path. /// If true will append to fileName. Otherwise will truncate fileName /// /// /// If there was already an opened file, then the previous file /// is closed first. /// /// /// This method will ensure that the directory structure /// for the specified exists. /// /// virtual protected void OpenFile(string fileName, bool append) { if (LogLog.IsErrorEnabled) { // Internal check that the fileName passed in is a rooted path bool isPathRooted = false; using(SecurityContext.Impersonate(this)) { isPathRooted = Path.IsPathRooted(fileName); } if (!isPathRooted) { LogLog.Error("FileAppender: INTERNAL ERROR. OpenFile("+fileName+"): File name is not fully qualified."); } } lock(this) { Reset(); LogLog.Debug("FileAppender: Opening file for writing ["+fileName+"] append ["+append+"]"); // Save these for later, allowing retries if file open fails m_fileName = fileName; m_appendToFile = append; LockingModel.CurrentAppender=this; LockingModel.OpenFile(fileName,append,m_encoding); m_stream=new LockingStream(LockingModel); if (m_stream != null) { m_stream.AcquireLock(); try { SetQWForFiles(new StreamWriter(m_stream, m_encoding)); } finally { m_stream.ReleaseLock(); } } WriteHeader(); } } /// /// Sets the quiet writer used for file output /// /// the file stream that has been opened for writing /// /// /// This implementation of creates a /// over the and passes it to the /// method. /// /// /// This method can be overridden by sub classes that want to wrap the /// in some way, for example to encrypt the output /// data using a System.Security.Cryptography.CryptoStream. /// /// virtual protected void SetQWForFiles(Stream fileStream) { SetQWForFiles(new StreamWriter(fileStream, m_encoding)); } /// /// Sets the quiet writer being used. /// /// the writer over the file stream that has been opened for writing /// /// /// This method can be overridden by sub classes that want to /// wrap the in some way. /// /// virtual protected void SetQWForFiles(TextWriter writer) { QuietWriter = new QuietTextWriter(writer, ErrorHandler); } #endregion Protected Instance Methods #region Protected Static Methods /// /// Convert a path into a fully qualified path. /// /// The path to convert. /// The fully qualified path. /// /// /// Converts the path specified to a fully /// qualified path. If the path is relative it is /// taken as relative from the application base /// directory. /// /// protected static string ConvertToFullPath(string path) { return SystemInfo.ConvertToFullPath(path); } #endregion Protected Static Methods #region Private Instance Fields /// /// Flag to indicate if we should append to the file /// or overwrite the file. The default is to append. /// private bool m_appendToFile = true; /// /// The name of the log file. /// private string m_fileName = null; /// /// The encoding to use for the file stream. /// private Encoding m_encoding = Encoding.Default; /// /// The security context to use for privileged calls /// private SecurityContext m_securityContext; /// /// The stream to log to. Has added locking semantics /// private FileAppender.LockingStream m_stream = null; /// /// The locking model to use /// private FileAppender.LockingModelBase m_lockingModel = new FileAppender.ExclusiveLock(); #endregion Private Instance Fields } }