#region Apache License // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to you 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 System.Threading; using log4net.Util; using log4net.Layout; using log4net.Core; namespace log4net.Appender { #if !NETCF /// /// 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 models only hold a /// write lock while the appender is writing a logging event () /// or synchronize by using a named system wide Mutex (). /// /// /// All locking strategies have issues and you should seriously consider using a different strategy that /// avoids having multiple processes logging to the same file. /// /// /// Nicko Cadell /// Gert Driesen /// Rodrigo B. de Oliveira /// Douglas de la Torre /// Niall Daley #else /// /// 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 (). /// /// /// All locking strategies have issues and you should seriously consider using a different strategy that /// avoids having multiple processes logging to the same file. /// /// /// Nicko Cadell /// Gert Driesen /// Rodrigo B. de Oliveira /// Douglas de la Torre /// Niall Daley #endif 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() { 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; } } /// /// Helper method that creates a FileStream under CurrentAppender's SecurityContext. /// /// /// /// Typically called during OpenFile or AcquireLock. /// /// /// If the directory portion of the does not exist, it is created /// via Directory.CreateDirecctory. /// /// /// /// /// /// protected Stream CreateStream(string filename, bool append, FileShare fileShare) { 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; return new FileStream(filename, fileOpenMode, FileAccess.Write, fileShare); } } /// /// Helper method to close under CurrentAppender's SecurityContext. /// /// /// Does not set to null. /// /// protected void CloseStream(Stream stream) { using (CurrentAppender.SecurityContext.Impersonate(this)) { stream.Close(); } } } /// /// 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 { m_stream = CreateStream(filename, append, 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() { CloseStream(m_stream); m_stream = null; } /// /// 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 { m_stream = CreateStream(m_filename, m_append, 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() { CloseStream(m_stream); m_stream = null; } } #if !NETCF /// /// Provides cross-process file locking. /// /// Ron Grabowski /// Steve Wranovsky public class InterProcessLock : LockingModelBase { private Mutex m_mutex = null; private bool m_mutexClosed = false; 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 . /// /// #if NET_4_0 [System.Security.SecuritySafeCritical] #endif public override void OpenFile(string filename, bool append, Encoding encoding) { try { m_stream = CreateStream(filename, append, FileShare.ReadWrite); string mutextFriendlyFilename = filename .Replace("\\", "_") .Replace(":", "_") .Replace("/", "_"); m_mutex = new Mutex(false, mutextFriendlyFilename); } 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() { try { CloseStream(m_stream); m_stream = null; } finally { m_mutex.ReleaseMutex(); m_mutex.Close(); m_mutexClosed = true; } } /// /// 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() { if (m_mutex != null) { // TODO: add timeout? m_mutex.WaitOne(); // should always be true (and fast) for FileStream if (m_stream.CanSeek) { m_stream.Seek(0, SeekOrigin.End); } } return m_stream; } /// /// /// public override void ReleaseLock() { if (m_mutexClosed == false && m_mutex != null) { m_mutex.ReleaseMutex(); } } } #endif #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; } } #if NETCF /// /// 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 first locks the file from the start of logging to the end, the /// second locks only for the minimal amount of time when logging each message /// and the last synchronizes processes using a named system wide Mutex. /// /// /// The default locking model is the . /// /// #else /// /// 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 three built in locking models, , and . /// The first locks the file from the start of logging to the end, the /// second locks only for the minimal amount of time when logging each message /// and the last synchronizes processes using a named system wide Mutex. /// /// /// The default locking model is the . /// /// #endif 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; if (m_fileName != null) { using(SecurityContext.Impersonate(this)) { m_fileName = ConvertToFullPath(m_fileName.Trim()); } SafeOpenFile(m_fileName, m_appendToFile); } else { LogLog.Warn(declaringType, "FileAppender: File option not set for appender ["+Name+"]."); LogLog.Warn(declaringType, "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(declaringType, "INTERNAL ERROR. OpenFile("+fileName+"): File name is not fully qualified."); } } lock(this) { Reset(); LogLog.Debug(declaringType, "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 #region Private Static Fields /// /// The fully qualified type of the FileAppender class. /// /// /// Used by the internal logger to record the Type of the /// log message. /// private readonly static Type declaringType = typeof(FileAppender); #endregion Private Static Fields } }