#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
|
[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);
|
}
|
}
|
|
/// <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;
|
|
using(SecurityContext.Impersonate(this))
|
{
|
m_fileName = ConvertToFullPath(m_fileName.Trim());
|
}
|
|
if (m_fileName != null)
|
{
|
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="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="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="SetQWForFiles(Stream)"/> creates a <see cref="StreamWriter"/>
|
/// over the <paramref name="fileStream"/> and passes it to the
|
/// <see cref="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
|
}
|
}
|