Author: dpsenner Date: Sun Nov 1 16:29:57 2015 New Revision: 1711829 URL: http://svn.apache.org/viewvc?rev=1711829&view=rev Log: LOG4NET-484: fix object disposed exception
This is a modified version of the patch supplied by nn1436401 at gmail dot com. Modified: logging/log4net/trunk/src/Appender/FileAppender.cs logging/log4net/trunk/tests/src/Appender/RollingFileAppenderTest.cs Modified: logging/log4net/trunk/src/Appender/FileAppender.cs URL: http://svn.apache.org/viewvc/logging/log4net/trunk/src/Appender/FileAppender.cs?rev=1711829&r1=1711828&r2=1711829&view=diff ============================================================================== --- logging/log4net/trunk/src/Appender/FileAppender.cs (original) +++ logging/log4net/trunk/src/Appender/FileAppender.cs Sun Nov 1 16:29:57 2015 @@ -1,1360 +1,1366 @@ -#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 - /// <summary> - /// Appends logging events to a file. - /// </summary> - /// <remarks> - /// <para> - /// Logging events are sent to the file specified by - /// the <see cref="File"/> property. - /// </para> - /// <para> - /// The file can be opened in either append or overwrite mode - /// by specifying the <see cref="AppendToFile"/> 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 <see cref="Encoding"/> property. - /// </para> - /// <para> - /// The layout's <see cref="ILayout.Header"/> and <see cref="ILayout.Footer"/> - /// values will be written each time the file is opened and closed - /// respectively. If the <see cref="AppendToFile"/> property is <see langword="true"/> - /// then the file may contain multiple copies of the header and footer. - /// </para> - /// <para> - /// This appender will first try to open the file for writing when <see cref="ActivateOptions"/> - /// 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. - /// </para> - /// <para> - /// The <see cref="FileAppender"/> supports pluggable file locking models via - /// the <see cref="LockingModel"/> property. - /// The default behavior, implemented by <see cref="FileAppender.ExclusiveLock"/> - /// 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 (<see cref="FileAppender.MinimalLock"/>) - /// or synchronize by using a named system wide Mutex (<see cref="FileAppender.InterProcessLock"/>). - /// </para> - /// <para> - /// All locking strategies have issues and you should seriously consider using a different strategy that - /// avoids having multiple processes logging to the same file. - /// </para> - /// </remarks> - /// <author>Nicko Cadell</author> - /// <author>Gert Driesen</author> - /// <author>Rodrigo B. de Oliveira</author> - /// <author>Douglas de la Torre</author> - /// <author>Niall Daley</author> -#else - /// <summary> - /// Appends logging events to a file. - /// </summary> - /// <remarks> - /// <para> - /// Logging events are sent to the file specified by - /// the <see cref="File"/> property. - /// </para> - /// <para> - /// The file can be opened in either append or overwrite mode - /// by specifying the <see cref="AppendToFile"/> 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 <see cref="Encoding"/> property. - /// </para> - /// <para> - /// The layout's <see cref="ILayout.Header"/> and <see cref="ILayout.Footer"/> - /// values will be written each time the file is opened and closed - /// respectively. If the <see cref="AppendToFile"/> property is <see langword="true"/> - /// then the file may contain multiple copies of the header and footer. - /// </para> - /// <para> - /// This appender will first try to open the file for writing when <see cref="ActivateOptions"/> - /// 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. - /// </para> - /// <para> - /// The <see cref="FileAppender"/> supports pluggable file locking models via - /// the <see cref="LockingModel"/> property. - /// The default behavior, implemented by <see cref="FileAppender.ExclusiveLock"/> - /// 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 (<see cref="FileAppender.MinimalLock"/>). - /// </para> - /// <para> - /// All locking strategies have issues and you should seriously consider using a different strategy that - /// avoids having multiple processes logging to the same file. - /// </para> - /// </remarks> - /// <author>Nicko Cadell</author> - /// <author>Gert Driesen</author> - /// <author>Rodrigo B. de Oliveira</author> - /// <author>Douglas de la Torre</author> - /// <author>Niall Daley</author> -#endif - public class FileAppender : TextWriterAppender - { - #region LockingStream Inner Class - - /// <summary> - /// Write only <see cref="Stream"/> that uses the <see cref="LockingModelBase"/> - /// to manage access to an underlying resource. - /// </summary> - 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; - } - - /// <summary> - /// True asynchronous writes are not supported, the implementation forces a synchronous write. - /// </summary> - 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 - - /// <summary> - /// Locking model base class - /// </summary> - /// <remarks> - /// <para> - /// Base class for the locking models available to the <see cref="FileAppender"/> derived loggers. - /// </para> - /// </remarks> - public abstract class LockingModelBase - { - private FileAppender m_appender=null; - - /// <summary> - /// Open the output file - /// </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> - /// <remarks> - /// <para> - /// Open the file specified and prepare for logging. - /// No writes will be made until <see cref="AcquireLock"/> is called. - /// Must be called before any calls to <see cref="AcquireLock"/>, - /// <see cref="ReleaseLock"/> and <see cref="CloseFile"/>. - /// </para> - /// </remarks> - public abstract void OpenFile(string filename, bool append,Encoding encoding); - - /// <summary> - /// Close the file - /// </summary> - /// <remarks> - /// <para> - /// Close the file. No further writes will be made. - /// </para> - /// </remarks> - public abstract void CloseFile(); - - /// <summary> - /// Acquire the lock on the file - /// </summary> - /// <returns>A stream that is ready to be written to.</returns> - /// <remarks> - /// <para> - /// Acquire the lock on the file in preparation for writing to it. - /// Return a stream pointing to the file. <see cref="ReleaseLock"/> - /// must be called to release the lock on the output file. - /// </para> - /// </remarks> - public abstract Stream AcquireLock(); - - /// <summary> - /// Release the lock on the file - /// </summary> - /// <remarks> - /// <para> - /// Release the lock on the file. No further writes will be made to the - /// stream until <see cref="AcquireLock"/> is called again. - /// </para> - /// </remarks> - public abstract void ReleaseLock(); - - /// <summary> - /// Gets or sets the <see cref="FileAppender"/> for this LockingModel - /// </summary> - /// <value> - /// The <see cref="FileAppender"/> for this LockingModel - /// </value> - /// <remarks> - /// <para> - /// The file appender this locking model is attached to and working on - /// behalf of. - /// </para> - /// <para> - /// The file appender is used to locate the security context and the error handler to use. - /// </para> - /// <para> - /// The value of this property will be set before <see cref="OpenFile"/> is - /// called. - /// </para> - /// </remarks> - public FileAppender CurrentAppender - { - get { return m_appender; } - set { m_appender = value; } - } - - /// <summary> - /// Helper method that creates a FileStream under CurrentAppender's SecurityContext. - /// </summary> - /// <remarks> - /// <para> - /// Typically called during OpenFile or AcquireLock. - /// </para> - /// <para> - /// If the directory portion of the <paramref name="filename"/> does not exist, it is created - /// via Directory.CreateDirecctory. - /// </para> - /// </remarks> - /// <param name="filename"></param> - /// <param name="append"></param> - /// <param name="fileShare"></param> - /// <returns></returns> - 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); - } - } - - /// <summary> - /// Helper method to close <paramref name="stream"/> under CurrentAppender's SecurityContext. - /// </summary> - /// <remarks> - /// Does not set <paramref name="stream"/> to null. - /// </remarks> - /// <param name="stream"></param> - protected void CloseStream(Stream stream) - { - using (CurrentAppender.SecurityContext.Impersonate(this)) - { - stream.Close(); - } - } - } - - /// <summary> - /// Hold an exclusive lock on the output file - /// </summary> - /// <remarks> - /// <para> - /// Open the file once for writing and hold it open until <see cref="CloseFile"/> is called. - /// Maintains an exclusive lock on the file during this time. - /// </para> - /// </remarks> - public class ExclusiveLock : LockingModelBase - { - private Stream m_stream = null; - - /// <summary> - /// Open the file specified and prepare for logging. - /// </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> - /// <remarks> - /// <para> - /// Open the file specified and prepare for logging. - /// No writes will be made until <see cref="AcquireLock"/> is called. - /// Must be called before any calls to <see cref="AcquireLock"/>, - /// <see cref="ReleaseLock"/> and <see cref="CloseFile"/>. - /// </para> - /// </remarks> - 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); - } - } - - /// <summary> - /// Close the file - /// </summary> - /// <remarks> - /// <para> - /// Close the file. No further writes will be made. - /// </para> - /// </remarks> - public override void CloseFile() - { - CloseStream(m_stream); - m_stream = null; - } - - /// <summary> - /// Acquire the lock on the file - /// </summary> - /// <returns>A stream that is ready to be written to.</returns> - /// <remarks> - /// <para> - /// Does nothing. The lock is already taken - /// </para> - /// </remarks> - public override Stream AcquireLock() - { - return m_stream; - } - - /// <summary> - /// Release the lock on the file - /// </summary> - /// <remarks> - /// <para> - /// Does nothing. The lock will be released when the file is closed. - /// </para> - /// </remarks> - public override void ReleaseLock() - { - //NOP - } - } - - /// <summary> - /// Acquires the file lock for each write - /// </summary> - /// <remarks> - /// <para> - /// Opens the file once for each <see cref="AcquireLock"/>/<see cref="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. - /// </para> - /// </remarks> - public class MinimalLock : LockingModelBase - { - private string m_filename; - private bool m_append; - private Stream m_stream=null; - - /// <summary> - /// Prepares to open the file when the first message is logged. - /// </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> - /// <remarks> - /// <para> - /// Open the file specified and prepare for logging. - /// No writes will be made until <see cref="AcquireLock"/> is called. - /// Must be called before any calls to <see cref="AcquireLock"/>, - /// <see cref="ReleaseLock"/> and <see cref="CloseFile"/>. - /// </para> - /// </remarks> - public override void OpenFile(string filename, bool append, Encoding encoding) - { - m_filename=filename; - m_append=append; - } - - /// <summary> - /// Close the file - /// </summary> - /// <remarks> - /// <para> - /// Close the file. No further writes will be made. - /// </para> - /// </remarks> - public override void CloseFile() - { - // NOP - } - - /// <summary> - /// Acquire the lock on the file - /// </summary> - /// <returns>A stream that is ready to be written to.</returns> - /// <remarks> - /// <para> - /// Acquire the lock on the file in preparation for writing to it. - /// Return a stream pointing to the file. <see cref="ReleaseLock"/> - /// must be called to release the lock on the output file. - /// </para> - /// </remarks> - 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; - } - - /// <summary> - /// Release the lock on the file - /// </summary> - /// <remarks> - /// <para> - /// Release the lock on the file. No further writes will be made to the - /// stream until <see cref="AcquireLock"/> is called again. - /// </para> - /// </remarks> - public override void ReleaseLock() - { - CloseStream(m_stream); - m_stream = null; - } - } - -#if !NETCF - /// <summary> - /// Provides cross-process file locking. - /// </summary> - /// <author>Ron Grabowski</author> - /// <author>Steve Wranovsky</author> - public class InterProcessLock : LockingModelBase - { - private Mutex m_mutex = null; - private bool m_mutexClosed = false; - private Stream m_stream = null; - - /// <summary> - /// Open the file specified and prepare for logging. - /// </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> - /// <remarks> - /// <para> - /// Open the file specified and prepare for logging. - /// No writes will be made until <see cref="AcquireLock"/> is called. - /// Must be called before any calls to <see cref="AcquireLock"/>, - /// -<see cref="ReleaseLock"/> and <see cref="CloseFile"/>. - /// </para> - /// </remarks> -#if NET_4_0 || MONO_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); - m_mutexClosed = false; - } - catch (Exception e1) - { - CurrentAppender.ErrorHandler.Error("Unable to acquire lock on file " + filename + ". " + e1.Message); - } - } - - /// <summary> - /// Close the file - /// </summary> - /// <remarks> - /// <para> - /// Close the file. No further writes will be made. - /// </para> - /// </remarks> - public override void CloseFile() - { - try { - CloseStream(m_stream); - m_stream = null; - } - finally { - m_mutex.ReleaseMutex(); - m_mutex.Close(); - m_mutexClosed = true; - } - } - - /// <summary> - /// Acquire the lock on the file - /// </summary> - /// <returns>A stream that is ready to be written to.</returns> - /// <remarks> - /// <para> - /// Does nothing. The lock is already taken - /// </para> - /// </remarks> - 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; - } - - /// <summary> - /// - /// </summary> - public override void ReleaseLock() - { - if (m_mutexClosed == false && m_mutex != null) - { - m_mutex.ReleaseMutex(); - } - } - } -#endif - - #endregion Locking Models - - #region Public Instance Constructors - - /// <summary> - /// Default constructor - /// </summary> - /// <remarks> - /// <para> - /// Default constructor - /// </para> - /// </remarks> - public FileAppender() - { - } - - /// <summary> - /// Construct a new appender using the layout, file and append mode. - /// </summary> - /// <param name="layout">the layout to use with this appender</param> - /// <param name="filename">the full path to the file to write to</param> - /// <param name="append">flag to indicate if the file should be appended to</param> - /// <remarks> - /// <para> - /// Obsolete constructor. - /// </para> - /// </remarks> - [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(); - } - - /// <summary> - /// Construct a new appender using the layout and file specified. - /// The file will be appended to. - /// </summary> - /// <param name="layout">the layout to use with this appender</param> - /// <param name="filename">the full path to the file to write to</param> - /// <remarks> - /// <para> - /// Obsolete constructor. - /// </para> - /// </remarks> - [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 - - /// <summary> - /// Gets or sets the path to the file that logging will be written to. - /// </summary> - /// <value> - /// The path to the file that logging will be written to. - /// </value> - /// <remarks> - /// <para> - /// If the path is relative it is taken as relative from - /// the application base directory. - /// </para> - /// </remarks> - virtual public string File - { - get { return m_fileName; } - set { m_fileName = value; } - } - - /// <summary> - /// Gets or sets a flag that indicates whether the file should be - /// appended to or overwritten. - /// </summary> - /// <value> - /// Indicates whether the file should be appended to or overwritten. - /// </value> - /// <remarks> - /// <para> - /// 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. - /// </para> - /// The default value is true. - /// </remarks> - public bool AppendToFile - { - get { return m_appendToFile; } - set { m_appendToFile = value; } - } - - /// <summary> - /// Gets or sets <see cref="Encoding"/> used to write to the file. - /// </summary> - /// <value> - /// The <see cref="Encoding"/> used to write to the file. - /// </value> - /// <remarks> - /// <para> - /// The default encoding set is <see cref="System.Text.Encoding.Default"/> - /// which is the encoding for the system's current ANSI code page. - /// </para> - /// </remarks> - public Encoding Encoding - { - get { return m_encoding; } - set { m_encoding = value; } - } - - /// <summary> - /// Gets or sets the <see cref="SecurityContext"/> used to write to the file. - /// </summary> - /// <value> - /// The <see cref="SecurityContext"/> used to write to the file. - /// </value> - /// <remarks> - /// <para> - /// Unless a <see cref="SecurityContext"/> specified here for this appender - /// the <see cref="SecurityContextProvider.DefaultProvider"/> is queried for the - /// security context to use. The default behavior is to use the security context - /// of the current thread. - /// </para> - /// </remarks> - public SecurityContext SecurityContext - { - get { return m_securityContext; } - set { m_securityContext = value; } - } - -#if NETCF - /// <summary> - /// Gets or sets the <see cref="FileAppender.LockingModel"/> used to handle locking of the file. - /// </summary> - /// <value> - /// The <see cref="FileAppender.LockingModel"/> used to lock the file. - /// </value> - /// <remarks> - /// <para> - /// Gets or sets the <see cref="FileAppender.LockingModel"/> used to handle locking of the file. - /// </para> - /// <para> - /// There are two built in locking models, <see cref="FileAppender.ExclusiveLock"/> and <see cref="FileAppender.MinimalLock"/>. - /// 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. - /// </para> - /// <para> - /// The default locking model is the <see cref="FileAppender.ExclusiveLock"/>. - /// </para> - /// </remarks> -#else - /// <summary> - /// Gets or sets the <see cref="FileAppender.LockingModel"/> used to handle locking of the file. - /// </summary> - /// <value> - /// The <see cref="FileAppender.LockingModel"/> used to lock the file. - /// </value> - /// <remarks> - /// <para> - /// Gets or sets the <see cref="FileAppender.LockingModel"/> used to handle locking of the file. - /// </para> - /// <para> - /// There are three built in locking models, <see cref="FileAppender.ExclusiveLock"/>, <see cref="FileAppender.MinimalLock"/> and <see cref="FileAppender.InterProcessLock"/> . - /// 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. - /// </para> - /// <para> - /// The default locking model is the <see cref="FileAppender.ExclusiveLock"/>. - /// </para> - /// </remarks> -#endif - public FileAppender.LockingModelBase LockingModel - { - get { return m_lockingModel; } - set { m_lockingModel = value; } - } - - #endregion Public Instance Properties - - #region Override implementation of AppenderSkeleton - - /// <summary> - /// Activate the options on the file appender. - /// </summary> - /// <remarks> - /// <para> - /// This is part of the <see cref="IOptionHandler"/> delayed object - /// activation scheme. The <see cref="ActivateOptions"/> method must - /// be called on this object after the configuration properties have - /// been set. Until <see cref="ActivateOptions"/> is called this - /// object is in an undefined state and must not be used. - /// </para> - /// <para> - /// If any of the configuration properties are modified then - /// <see cref="ActivateOptions"/> must be called again. - /// </para> - /// <para> - /// This will cause the file to be opened. - /// </para> - /// </remarks> - 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 - - /// <summary> - /// Closes any previously opened file and calls the parent's <see cref="TextWriterAppender.Reset"/>. - /// </summary> - /// <remarks> - /// <para> - /// Resets the filename and the file stream. - /// </para> - /// </remarks> - override protected void Reset() - { - base.Reset(); - m_fileName = null; - } - - /// <summary> - /// Called to initialize the file writer - /// </summary> - /// <remarks> - /// <para> - /// Will be called for each logged message until the file is - /// successfully opened. - /// </para> - /// </remarks> - override protected void PrepareWriter() - { - SafeOpenFile(m_fileName, m_appendToFile); - } - - /// <summary> - /// This method is called by the <see cref="M:AppenderSkeleton.DoAppend(LoggingEvent)"/> - /// 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) - { - if (m_stream.AcquireLock()) - { - try - { - base.Append(loggingEvent); - } - finally - { - m_stream.ReleaseLock(); - } - } - } - - /// <summary> - /// This method is called by the <see cref="M:AppenderSkeleton.DoAppend(LoggingEvent[])"/> - /// method. - /// </summary> - /// <param name="loggingEvents">The array of events to log.</param> - /// <remarks> - /// <para> - /// Acquires the output file locks once before writing all the events to - /// the stream. - /// </para> - /// </remarks> - override protected void Append(LoggingEvent[] loggingEvents) - { - if (m_stream.AcquireLock()) - { - try - { - base.Append(loggingEvents); - } - finally - { - 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.AcquireLock(); - try - { - base.WriteFooter(); - } - finally - { - 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) - { - if (m_stream.AcquireLock()) - { - try - { - base.WriteHeader(); - } - finally - { - 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.AcquireLock(); - try - { - base.CloseWriter(); - } - finally - { - m_stream.ReleaseLock(); - } - } - } - - #endregion Override implementation of TextWriterAppender - - #region Public Instance Methods - - /// <summary> - /// Closes the previously opened file. - /// </summary> - /// <remarks> - /// <para> - /// Writes the <see cref="ILayout.Footer"/> to the file and then - /// closes the file. - /// </para> - /// </remarks> - protected void CloseFile() - { - WriteFooterAndCloseWriter(); - } - - #endregion Public Instance Methods - - #region Protected Instance Methods - - /// <summary> - /// Sets and <i>opens</i> the file where the log output will go. The specified file must be writable. - /// </summary> - /// <param name="fileName">The path to the log file. Must be a fully qualified path.</param> - /// <param name="append">If true will append to fileName. Otherwise will truncate fileName</param> - /// <remarks> - /// <para> - /// Calls <see cref="OpenFile"/> but guarantees not to throw an exception. - /// Errors are passed to the <see cref="TextWriterAppender.ErrorHandler"/>. - /// </para> - /// </remarks> - 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); - } - } - - /// <summary> - /// Sets and <i>opens</i> the file where the log output will go. The specified file must be writable. - /// </summary> - /// <param name="fileName">The path to the log file. Must be a fully qualified path.</param> - /// <param name="append">If true will append to fileName. Otherwise will truncate fileName</param> - /// <remarks> - /// <para> - /// If there was already an opened file, then the previous file - /// is closed first. - /// </para> - /// <para> - /// This method will ensure that the directory structure - /// for the <paramref name="fileName"/> specified exists. - /// </para> - /// </remarks> - 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(); - } - } - - /// <summary> - /// Sets the quiet writer used for file output - /// </summary> - /// <param name="fileStream">the file stream that has been opened for writing</param> - /// <remarks> - /// <para> - /// This implementation of <see cref="M:SetQWForFiles(Stream)"/> creates a <see cref="StreamWriter"/> - /// over the <paramref name="fileStream"/> and passes it to the - /// <see cref="M:SetQWForFiles(TextWriter)"/> method. - /// </para> - /// <para> - /// This method can be overridden by sub classes that want to wrap the - /// <see cref="Stream"/> in some way, for example to encrypt the output - /// data using a <c>System.Security.Cryptography.CryptoStream</c>. - /// </para> - /// </remarks> - virtual protected void SetQWForFiles(Stream fileStream) - { - SetQWForFiles(new StreamWriter(fileStream, m_encoding)); - } - - /// <summary> - /// Sets the quiet writer being used. - /// </summary> - /// <param name="writer">the writer over the file stream that has been opened for writing</param> - /// <remarks> - /// <para> - /// This method can be overridden by sub classes that want to - /// wrap the <see cref="TextWriter"/> in some way. - /// </para> - /// </remarks> - virtual protected void SetQWForFiles(TextWriter writer) - { - QuietWriter = new QuietTextWriter(writer, ErrorHandler); - } - - #endregion Protected Instance Methods - - #region Protected Static Methods - - /// <summary> - /// Convert a path into a fully qualified path. - /// </summary> - /// <param name="path">The path to convert.</param> - /// <returns>The fully qualified path.</returns> - /// <remarks> - /// <para> - /// Converts the path specified to a fully - /// qualified path. If the path is relative it is - /// taken as relative from the application base - /// directory. - /// </para> - /// </remarks> - protected static string ConvertToFullPath(string path) - { - return SystemInfo.ConvertToFullPath(path); - } - - #endregion Protected Static Methods - - #region Private Instance Fields - - /// <summary> - /// Flag to indicate if we should append to the file - /// or overwrite the file. The default is to append. - /// </summary> - private bool m_appendToFile = true; - - /// <summary> - /// The name of the log file. - /// </summary> - private string m_fileName = null; - - /// <summary> - /// The encoding to use for the file stream. - /// </summary> - private Encoding m_encoding = Encoding.Default; - - /// <summary> - /// The security context to use for privileged calls - /// </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 - - #region Private Static Fields - - /// <summary> - /// The fully qualified type of the FileAppender class. - /// </summary> - /// <remarks> - /// Used by the internal logger to record the Type of the - /// log message. - /// </remarks> - private readonly static Type declaringType = typeof(FileAppender); - - #endregion Private Static Fields - } -} +#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 + /// <summary> + /// Appends logging events to a file. + /// </summary> + /// <remarks> + /// <para> + /// Logging events are sent to the file specified by + /// the <see cref="File"/> property. + /// </para> + /// <para> + /// The file can be opened in either append or overwrite mode + /// by specifying the <see cref="AppendToFile"/> 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 <see cref="Encoding"/> property. + /// </para> + /// <para> + /// The layout's <see cref="ILayout.Header"/> and <see cref="ILayout.Footer"/> + /// values will be written each time the file is opened and closed + /// respectively. If the <see cref="AppendToFile"/> property is <see langword="true"/> + /// then the file may contain multiple copies of the header and footer. + /// </para> + /// <para> + /// This appender will first try to open the file for writing when <see cref="ActivateOptions"/> + /// 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. + /// </para> + /// <para> + /// The <see cref="FileAppender"/> supports pluggable file locking models via + /// the <see cref="LockingModel"/> property. + /// The default behavior, implemented by <see cref="FileAppender.ExclusiveLock"/> + /// 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 (<see cref="FileAppender.MinimalLock"/>) + /// or synchronize by using a named system wide Mutex (<see cref="FileAppender.InterProcessLock"/>). + /// </para> + /// <para> + /// All locking strategies have issues and you should seriously consider using a different strategy that + /// avoids having multiple processes logging to the same file. + /// </para> + /// </remarks> + /// <author>Nicko Cadell</author> + /// <author>Gert Driesen</author> + /// <author>Rodrigo B. de Oliveira</author> + /// <author>Douglas de la Torre</author> + /// <author>Niall Daley</author> +#else + /// <summary> + /// Appends logging events to a file. + /// </summary> + /// <remarks> + /// <para> + /// Logging events are sent to the file specified by + /// the <see cref="File"/> property. + /// </para> + /// <para> + /// The file can be opened in either append or overwrite mode + /// by specifying the <see cref="AppendToFile"/> 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 <see cref="Encoding"/> property. + /// </para> + /// <para> + /// The layout's <see cref="ILayout.Header"/> and <see cref="ILayout.Footer"/> + /// values will be written each time the file is opened and closed + /// respectively. If the <see cref="AppendToFile"/> property is <see langword="true"/> + /// then the file may contain multiple copies of the header and footer. + /// </para> + /// <para> + /// This appender will first try to open the file for writing when <see cref="ActivateOptions"/> + /// 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. + /// </para> + /// <para> + /// The <see cref="FileAppender"/> supports pluggable file locking models via + /// the <see cref="LockingModel"/> property. + /// The default behavior, implemented by <see cref="FileAppender.ExclusiveLock"/> + /// 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 (<see cref="FileAppender.MinimalLock"/>). + /// </para> + /// <para> + /// All locking strategies have issues and you should seriously consider using a different strategy that + /// avoids having multiple processes logging to the same file. + /// </para> + /// </remarks> + /// <author>Nicko Cadell</author> + /// <author>Gert Driesen</author> + /// <author>Rodrigo B. de Oliveira</author> + /// <author>Douglas de la Torre</author> + /// <author>Niall Daley</author> +#endif + public class FileAppender : TextWriterAppender + { + #region LockingStream Inner Class + + /// <summary> + /// Write only <see cref="Stream"/> that uses the <see cref="LockingModelBase"/> + /// to manage access to an underlying resource. + /// </summary> + 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; + } + + /// <summary> + /// True asynchronous writes are not supported, the implementation forces a synchronous write. + /// </summary> + 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 + + /// <summary> + /// Locking model base class + /// </summary> + /// <remarks> + /// <para> + /// Base class for the locking models available to the <see cref="FileAppender"/> derived loggers. + /// </para> + /// </remarks> + public abstract class LockingModelBase + { + private FileAppender m_appender=null; + + /// <summary> + /// Open the output file + /// </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> + /// <remarks> + /// <para> + /// Open the file specified and prepare for logging. + /// No writes will be made until <see cref="AcquireLock"/> is called. + /// Must be called before any calls to <see cref="AcquireLock"/>, + /// <see cref="ReleaseLock"/> and <see cref="CloseFile"/>. + /// </para> + /// </remarks> + public abstract void OpenFile(string filename, bool append,Encoding encoding); + + /// <summary> + /// Close the file + /// </summary> + /// <remarks> + /// <para> + /// Close the file. No further writes will be made. + /// </para> + /// </remarks> + public abstract void CloseFile(); + + /// <summary> + /// Acquire the lock on the file + /// </summary> + /// <returns>A stream that is ready to be written to.</returns> + /// <remarks> + /// <para> + /// Acquire the lock on the file in preparation for writing to it. + /// Return a stream pointing to the file. <see cref="ReleaseLock"/> + /// must be called to release the lock on the output file. + /// </para> + /// </remarks> + public abstract Stream AcquireLock(); + + /// <summary> + /// Release the lock on the file + /// </summary> + /// <remarks> + /// <para> + /// Release the lock on the file. No further writes will be made to the + /// stream until <see cref="AcquireLock"/> is called again. + /// </para> + /// </remarks> + public abstract void ReleaseLock(); + + /// <summary> + /// Gets or sets the <see cref="FileAppender"/> for this LockingModel + /// </summary> + /// <value> + /// The <see cref="FileAppender"/> for this LockingModel + /// </value> + /// <remarks> + /// <para> + /// The file appender this locking model is attached to and working on + /// behalf of. + /// </para> + /// <para> + /// The file appender is used to locate the security context and the error handler to use. + /// </para> + /// <para> + /// The value of this property will be set before <see cref="OpenFile"/> is + /// called. + /// </para> + /// </remarks> + public FileAppender CurrentAppender + { + get { return m_appender; } + set { m_appender = value; } + } + + /// <summary> + /// Helper method that creates a FileStream under CurrentAppender's SecurityContext. + /// </summary> + /// <remarks> + /// <para> + /// Typically called during OpenFile or AcquireLock. + /// </para> + /// <para> + /// If the directory portion of the <paramref name="filename"/> does not exist, it is created + /// via Directory.CreateDirecctory. + /// </para> + /// </remarks> + /// <param name="filename"></param> + /// <param name="append"></param> + /// <param name="fileShare"></param> + /// <returns></returns> + 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); + } + } + + /// <summary> + /// Helper method to close <paramref name="stream"/> under CurrentAppender's SecurityContext. + /// </summary> + /// <remarks> + /// Does not set <paramref name="stream"/> to null. + /// </remarks> + /// <param name="stream"></param> + protected void CloseStream(Stream stream) + { + using (CurrentAppender.SecurityContext.Impersonate(this)) + { + stream.Close(); + } + } + } + + /// <summary> + /// Hold an exclusive lock on the output file + /// </summary> + /// <remarks> + /// <para> + /// Open the file once for writing and hold it open until <see cref="CloseFile"/> is called. + /// Maintains an exclusive lock on the file during this time. + /// </para> + /// </remarks> + public class ExclusiveLock : LockingModelBase + { + private Stream m_stream = null; + + /// <summary> + /// Open the file specified and prepare for logging. + /// </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> + /// <remarks> + /// <para> + /// Open the file specified and prepare for logging. + /// No writes will be made until <see cref="AcquireLock"/> is called. + /// Must be called before any calls to <see cref="AcquireLock"/>, + /// <see cref="ReleaseLock"/> and <see cref="CloseFile"/>. + /// </para> + /// </remarks> + 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); + } + } + + /// <summary> + /// Close the file + /// </summary> + /// <remarks> + /// <para> + /// Close the file. No further writes will be made. + /// </para> + /// </remarks> + public override void CloseFile() + { + CloseStream(m_stream); + m_stream = null; + } + + /// <summary> + /// Acquire the lock on the file + /// </summary> + /// <returns>A stream that is ready to be written to.</returns> + /// <remarks> + /// <para> + /// Does nothing. The lock is already taken + /// </para> + /// </remarks> + public override Stream AcquireLock() + { + return m_stream; + } + + /// <summary> + /// Release the lock on the file + /// </summary> + /// <remarks> + /// <para> + /// Does nothing. The lock will be released when the file is closed. + /// </para> + /// </remarks> + public override void ReleaseLock() + { + //NOP + } + } + + /// <summary> + /// Acquires the file lock for each write + /// </summary> + /// <remarks> + /// <para> + /// Opens the file once for each <see cref="AcquireLock"/>/<see cref="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. + /// </para> + /// </remarks> + public class MinimalLock : LockingModelBase + { + private string m_filename; + private bool m_append; + private Stream m_stream=null; + + /// <summary> + /// Prepares to open the file when the first message is logged. + /// </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> + /// <remarks> + /// <para> + /// Open the file specified and prepare for logging. + /// No writes will be made until <see cref="AcquireLock"/> is called. + /// Must be called before any calls to <see cref="AcquireLock"/>, + /// <see cref="ReleaseLock"/> and <see cref="CloseFile"/>. + /// </para> + /// </remarks> + public override void OpenFile(string filename, bool append, Encoding encoding) + { + m_filename=filename; + m_append=append; + } + + /// <summary> + /// Close the file + /// </summary> + /// <remarks> + /// <para> + /// Close the file. No further writes will be made. + /// </para> + /// </remarks> + public override void CloseFile() + { + // NOP + } + + /// <summary> + /// Acquire the lock on the file + /// </summary> + /// <returns>A stream that is ready to be written to.</returns> + /// <remarks> + /// <para> + /// Acquire the lock on the file in preparation for writing to it. + /// Return a stream pointing to the file. <see cref="ReleaseLock"/> + /// must be called to release the lock on the output file. + /// </para> + /// </remarks> + 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; + } + + /// <summary> + /// Release the lock on the file + /// </summary> + /// <remarks> + /// <para> + /// Release the lock on the file. No further writes will be made to the + /// stream until <see cref="AcquireLock"/> is called again. + /// </para> + /// </remarks> + public override void ReleaseLock() + { + CloseStream(m_stream); + m_stream = null; + } + } + +#if !NETCF + /// <summary> + /// Provides cross-process file locking. + /// </summary> + /// <author>Ron Grabowski</author> + /// <author>Steve Wranovsky</author> + public class InterProcessLock : LockingModelBase + { + private Mutex m_mutex = null; + private Stream m_stream = null; + + /// <summary> + /// Open the file specified and prepare for logging. + /// </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> + /// <remarks> + /// <para> + /// Open the file specified and prepare for logging. + /// No writes will be made until <see cref="AcquireLock"/> is called. + /// Must be called before any calls to <see cref="AcquireLock"/>, + /// -<see cref="ReleaseLock"/> and <see cref="CloseFile"/>. + /// </para> + /// </remarks> +#if NET_4_0 || MONO_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 mutexFriendlyFilename = filename + .Replace("\\", "_") + .Replace(":", "_") + .Replace("/", "_"); + + m_mutex = new Mutex(false, mutexFriendlyFilename); + } + catch (Exception e1) + { + CurrentAppender.ErrorHandler.Error("Unable to acquire lock on file " + filename + ". " + e1.Message); + } + } + + /// <summary> + /// Close the file + /// </summary> + /// <remarks> + /// <para> + /// Close the file. No further writes will be made. + /// </para> + /// </remarks> + public override void CloseFile() + { + try { + CloseStream(m_stream); + m_stream = null; + } + finally { + ReleaseLock(); + m_mutex.Close(); + m_mutex = null; + } + } + + /// <summary> + /// Acquire the lock on the file + /// </summary> + /// <returns>A stream that is ready to be written to.</returns> + /// <remarks> + /// <para> + /// Does nothing. The lock is already taken + /// </para> + /// </remarks> + 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); + } + } + else + { + CurrentAppender.ErrorHandler.Error("Programming error, no mutex available to acquire lock! From here on things will be dangerous!"); + } + return m_stream; + } + + /// <summary> + /// Releases the lock and allows others to acquire a lock. + /// </summary> + public override void ReleaseLock() + { + if (m_mutex != null) + { + m_mutex.ReleaseMutex(); + } + else + { + CurrentAppender.ErrorHandler.Error("Programming error, no mutex available to release the lock!"); + } + } + } +#endif + + #endregion Locking Models + + #region Public Instance Constructors + + /// <summary> + /// Default constructor + /// </summary> + /// <remarks> + /// <para> + /// Default constructor + /// </para> + /// </remarks> + public FileAppender() + { + } + + /// <summary> + /// Construct a new appender using the layout, file and append mode. + /// </summary> + /// <param name="layout">the layout to use with this appender</param> + /// <param name="filename">the full path to the file to write to</param> + /// <param name="append">flag to indicate if the file should be appended to</param> + /// <remarks> + /// <para> + /// Obsolete constructor. + /// </para> + /// </remarks> + [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(); + } + + /// <summary> + /// Construct a new appender using the layout and file specified. + /// The file will be appended to. + /// </summary> + /// <param name="layout">the layout to use with this appender</param> + /// <param name="filename">the full path to the file to write to</param> + /// <remarks> + /// <para> + /// Obsolete constructor. + /// </para> + /// </remarks> + [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 + + /// <summary> + /// Gets or sets the path to the file that logging will be written to. + /// </summary> + /// <value> + /// The path to the file that logging will be written to. + /// </value> + /// <remarks> + /// <para> + /// If the path is relative it is taken as relative from + /// the application base directory. + /// </para> + /// </remarks> + virtual public string File + { + get { return m_fileName; } + set { m_fileName = value; } + } + + /// <summary> + /// Gets or sets a flag that indicates whether the file should be + /// appended to or overwritten. + /// </summary> + /// <value> + /// Indicates whether the file should be appended to or overwritten. + /// </value> + /// <remarks> + /// <para> + /// 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. + /// </para> + /// The default value is true. + /// </remarks> + public bool AppendToFile + { + get { return m_appendToFile; } + set { m_appendToFile = value; } + } + + /// <summary> + /// Gets or sets <see cref="Encoding"/> used to write to the file. + /// </summary> + /// <value> + /// The <see cref="Encoding"/> used to write to the file. + /// </value> + /// <remarks> + /// <para> + /// The default encoding set is <see cref="System.Text.Encoding.Default"/> + /// which is the encoding for the system's current ANSI code page. + /// </para> + /// </remarks> + public Encoding Encoding + { + get { return m_encoding; } + set { m_encoding = value; } + } + + /// <summary> + /// Gets or sets the <see cref="SecurityContext"/> used to write to the file. + /// </summary> + /// <value> + /// The <see cref="SecurityContext"/> used to write to the file. + /// </value> + /// <remarks> + /// <para> + /// Unless a <see cref="SecurityContext"/> specified here for this appender + /// the <see cref="SecurityContextProvider.DefaultProvider"/> is queried for the + /// security context to use. The default behavior is to use the security context + /// of the current thread. + /// </para> + /// </remarks> + public SecurityContext SecurityContext + { + get { return m_securityContext; } + set { m_securityContext = value; } + } + +#if NETCF + /// <summary> + /// Gets or sets the <see cref="FileAppender.LockingModel"/> used to handle locking of the file. + /// </summary> + /// <value> + /// The <see cref="FileAppender.LockingModel"/> used to lock the file. + /// </value> + /// <remarks> + /// <para> + /// Gets or sets the <see cref="FileAppender.LockingModel"/> used to handle locking of the file. + /// </para> + /// <para> + /// There are two built in locking models, <see cref="FileAppender.ExclusiveLock"/> and <see cref="FileAppender.MinimalLock"/>. + /// 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. + /// </para> + /// <para> + /// The default locking model is the <see cref="FileAppender.ExclusiveLock"/>. + /// </para> + /// </remarks> +#else + /// <summary> + /// Gets or sets the <see cref="FileAppender.LockingModel"/> used to handle locking of the file. + /// </summary> + /// <value> + /// The <see cref="FileAppender.LockingModel"/> used to lock the file. + /// </value> + /// <remarks> + /// <para> + /// Gets or sets the <see cref="FileAppender.LockingModel"/> used to handle locking of the file. + /// </para> + /// <para> + /// There are three built in locking models, <see cref="FileAppender.ExclusiveLock"/>, <see cref="FileAppender.MinimalLock"/> and <see cref="FileAppender.InterProcessLock"/> . + /// 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. + /// </para> + /// <para> + /// The default locking model is the <see cref="FileAppender.ExclusiveLock"/>. + /// </para> + /// </remarks> +#endif + public FileAppender.LockingModelBase LockingModel + { + get { return m_lockingModel; } + set { m_lockingModel = value; } + } + + #endregion Public Instance Properties + + #region Override implementation of AppenderSkeleton + + /// <summary> + /// Activate the options on the file appender. + /// </summary> + /// <remarks> + /// <para> + /// This is part of the <see cref="IOptionHandler"/> delayed object + /// activation scheme. The <see cref="ActivateOptions"/> method must + /// be called on this object after the configuration properties have + /// been set. Until <see cref="ActivateOptions"/> is called this + /// object is in an undefined state and must not be used. + /// </para> + /// <para> + /// If any of the configuration properties are modified then + /// <see cref="ActivateOptions"/> must be called again. + /// </para> + /// <para> + /// This will cause the file to be opened. + /// </para> + /// </remarks> + 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 + + /// <summary> + /// Closes any previously opened file and calls the parent's <see cref="TextWriterAppender.Reset"/>. + /// </summary> + /// <remarks> + /// <para> + /// Resets the filename and the file stream. + /// </para> + /// </remarks> + override protected void Reset() + { + base.Reset(); + m_fileName = null; + } + + /// <summary> + /// Called to initialize the file writer + /// </summary> + /// <remarks> + /// <para> + /// Will be called for each logged message until the file is + /// successfully opened. + /// </para> + /// </remarks> + override protected void PrepareWriter() + { + SafeOpenFile(m_fileName, m_appendToFile); + } + + /// <summary> + /// This method is called by the <see cref="M:AppenderSkeleton.DoAppend(LoggingEvent)"/> + /// 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) + { + if (m_stream.AcquireLock()) + { + try + { + base.Append(loggingEvent); + } + finally + { + m_stream.ReleaseLock(); + } + } + } + + /// <summary> + /// This method is called by the <see cref="M:AppenderSkeleton.DoAppend(LoggingEvent[])"/> + /// method. + /// </summary> + /// <param name="loggingEvents">The array of events to log.</param> + /// <remarks> + /// <para> + /// Acquires the output file locks once before writing all the events to + /// the stream. + /// </para> + /// </remarks> + override protected void Append(LoggingEvent[] loggingEvents) + { + if (m_stream.AcquireLock()) + { + try + { + base.Append(loggingEvents); + } + finally + { + 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.AcquireLock(); + try + { + base.WriteFooter(); + } + finally + { + 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) + { + if (m_stream.AcquireLock()) + { + try + { + base.WriteHeader(); + } + finally + { + 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.AcquireLock(); + try + { + base.CloseWriter(); + } + finally + { + m_stream.ReleaseLock(); + } + } + } + + #endregion Override implementation of TextWriterAppender + + #region Public Instance Methods + + /// <summary> + /// Closes the previously opened file. + /// </summary> + /// <remarks> + /// <para> + /// Writes the <see cref="ILayout.Footer"/> to the file and then + /// closes the file. + /// </para> + /// </remarks> + protected void CloseFile() + { + WriteFooterAndCloseWriter(); + } + + #endregion Public Instance Methods + + #region Protected Instance Methods + + /// <summary> + /// Sets and <i>opens</i> the file where the log output will go. The specified file must be writable. + /// </summary> + /// <param name="fileName">The path to the log file. Must be a fully qualified path.</param> + /// <param name="append">If true will append to fileName. Otherwise will truncate fileName</param> + /// <remarks> + /// <para> + /// Calls <see cref="OpenFile"/> but guarantees not to throw an exception. + /// Errors are passed to the <see cref="TextWriterAppender.ErrorHandler"/>. + /// </para> + /// </remarks> + 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); + } + } + + /// <summary> + /// Sets and <i>opens</i> the file where the log output will go. The specified file must be writable. + /// </summary> + /// <param name="fileName">The path to the log file. Must be a fully qualified path.</param> + /// <param name="append">If true will append to fileName. Otherwise will truncate fileName</param> + /// <remarks> + /// <para> + /// If there was already an opened file, then the previous file + /// is closed first. + /// </para> + /// <para> + /// This method will ensure that the directory structure + /// for the <paramref name="fileName"/> specified exists. + /// </para> + /// </remarks> + 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(); + } + } + + /// <summary> + /// Sets the quiet writer used for file output + /// </summary> + /// <param name="fileStream">the file stream that has been opened for writing</param> + /// <remarks> + /// <para> + /// This implementation of <see cref="M:SetQWForFiles(Stream)"/> creates a <see cref="StreamWriter"/> + /// over the <paramref name="fileStream"/> and passes it to the + /// <see cref="M:SetQWForFiles(TextWriter)"/> method. + /// </para> + /// <para> + /// This method can be overridden by sub classes that want to wrap the + /// <see cref="Stream"/> in some way, for example to encrypt the output + /// data using a <c>System.Security.Cryptography.CryptoStream</c>. + /// </para> + /// </remarks> + virtual protected void SetQWForFiles(Stream fileStream) + { + SetQWForFiles(new StreamWriter(fileStream, m_encoding)); + } + + /// <summary> + /// Sets the quiet writer being used. + /// </summary> + /// <param name="writer">the writer over the file stream that has been opened for writing</param> + /// <remarks> + /// <para> + /// This method can be overridden by sub classes that want to + /// wrap the <see cref="TextWriter"/> in some way. + /// </para> + /// </remarks> + virtual protected void SetQWForFiles(TextWriter writer) + { + QuietWriter = new QuietTextWriter(writer, ErrorHandler); + } + + #endregion Protected Instance Methods + + #region Protected Static Methods + + /// <summary> + /// Convert a path into a fully qualified path. + /// </summary> + /// <param name="path">The path to convert.</param> + /// <returns>The fully qualified path.</returns> + /// <remarks> + /// <para> + /// Converts the path specified to a fully + /// qualified path. If the path is relative it is + /// taken as relative from the application base + /// directory. + /// </para> + /// </remarks> + protected static string ConvertToFullPath(string path) + { + return SystemInfo.ConvertToFullPath(path); + } + + #endregion Protected Static Methods + + #region Private Instance Fields + + /// <summary> + /// Flag to indicate if we should append to the file + /// or overwrite the file. The default is to append. + /// </summary> + private bool m_appendToFile = true; + + /// <summary> + /// The name of the log file. + /// </summary> + private string m_fileName = null; + + /// <summary> + /// The encoding to use for the file stream. + /// </summary> + private Encoding m_encoding = Encoding.Default; + + /// <summary> + /// The security context to use for privileged calls + /// </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 + + #region Private Static Fields + + /// <summary> + /// The fully qualified type of the FileAppender class. + /// </summary> + /// <remarks> + /// Used by the internal logger to record the Type of the + /// log message. + /// </remarks> + private readonly static Type declaringType = typeof(FileAppender); + + #endregion Private Static Fields + } +} Modified: logging/log4net/trunk/tests/src/Appender/RollingFileAppenderTest.cs URL: http://svn.apache.org/viewvc/logging/log4net/trunk/tests/src/Appender/RollingFileAppenderTest.cs?rev=1711829&r1=1711828&r2=1711829&view=diff ============================================================================== --- logging/log4net/trunk/tests/src/Appender/RollingFileAppenderTest.cs (original) +++ logging/log4net/trunk/tests/src/Appender/RollingFileAppenderTest.cs Sun Nov 1 16:29:57 2015 @@ -1397,6 +1397,20 @@ namespace log4net.Tests.Appender /// <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(); @@ -1404,9 +1418,10 @@ namespace log4net.Tests.Appender appender.AppendToFile = false; appender.CountDirection = 0; appender.RollingStyle = RollingFileAppender.RollingMode.Size; - appender.MaxFileSize = 100000; + appender.MaxFileSize = maxFileSize; appender.Encoding = Encoding.ASCII; appender.ErrorHandler = handler; + appender.MaxSizeRollBackups = maxSizeRollBackups; if (lockModel != null) { appender.LockingModel = lockModel; @@ -1686,6 +1701,28 @@ namespace log4net.Tests.Appender 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.log"; + bool locked; + + 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>