#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.Collections;
using log4net.Filter;
using log4net.Util;
using log4net.Layout;
using log4net.Core;
namespace log4net.Appender
{
///
/// Abstract base class implementation of .
///
///
///
/// This class provides the code for common functionality, such
/// as support for threshold filtering and support for general filters.
///
///
/// Appenders can also implement the interface. Therefore
/// they would require that the method
/// be called after the appenders properties have been configured.
///
///
/// Nicko Cadell
/// Gert Driesen
public abstract class AppenderSkeleton : IAppender, IBulkAppender, IOptionHandler
{
#region Protected Instance Constructors
///
/// Default constructor
///
///
/// Empty default constructor
///
protected AppenderSkeleton()
{
m_errorHandler = new OnlyOnceErrorHandler(this.GetType().Name);
}
#endregion Protected Instance Constructors
#region Finalizer
///
/// Finalizes this appender by calling the implementation's
/// method.
///
///
///
/// If this appender has not been closed then the Finalize method
/// will call .
///
///
~AppenderSkeleton()
{
// An appender might be closed then garbage collected.
// There is no point in closing twice.
if (!m_closed)
{
LogLog.Debug(declaringType, "Finalizing appender named ["+m_name+"].");
Close();
}
}
#endregion Finalizer
#region Public Instance Properties
///
/// Gets or sets the threshold of this appender.
///
///
/// The threshold of the appender.
///
///
///
/// All log events with lower level than the threshold level are ignored
/// by the appender.
///
///
/// In configuration files this option is specified by setting the
/// value of the option to a level
/// string, such as "DEBUG", "INFO" and so on.
///
///
public Level Threshold
{
get { return m_threshold; }
set { m_threshold = value; }
}
///
/// Gets or sets the for this appender.
///
/// The of the appender
///
///
/// The provides a default
/// implementation for the property.
///
///
virtual public IErrorHandler ErrorHandler
{
get { return this.m_errorHandler; }
set
{
lock(this)
{
if (value == null)
{
// We do not throw exception here since the cause is probably a
// bad config file.
LogLog.Warn(declaringType, "You have tried to set a null error-handler.");
}
else
{
m_errorHandler = value;
}
}
}
}
///
/// The filter chain.
///
/// The head of the filter chain filter chain.
///
///
/// Returns the head Filter. The Filters are organized in a linked list
/// and so all Filters on this Appender are available through the result.
///
///
virtual public IFilter FilterHead
{
get { return m_headFilter; }
}
///
/// Gets or sets the for this appender.
///
/// The layout of the appender.
///
///
/// See for more information.
///
///
///
virtual public ILayout Layout
{
get { return m_layout; }
set { m_layout = value; }
}
#endregion
#region Implementation of IOptionHandler
///
/// Initialize the appender based on the options set
///
///
///
/// This is part of the delayed object
/// activation scheme. The method must
/// be called on this object after the configuration properties have
/// been set. Until is called this
/// object is in an undefined state and must not be used.
///
///
/// If any of the configuration properties are modified then
/// must be called again.
///
///
virtual public void ActivateOptions()
{
}
#endregion Implementation of IOptionHandler
#region Implementation of IAppender
///
/// Gets or sets the name of this appender.
///
/// The name of the appender.
///
///
/// The name uniquely identifies the appender.
///
///
public string Name
{
get { return m_name; }
set { m_name = value; }
}
///
/// Closes the appender and release resources.
///
///
///
/// Release any resources allocated within the appender such as file handles,
/// network connections, etc.
///
///
/// It is a programming error to append to a closed appender.
///
///
/// This method cannot be overridden by subclasses. This method
/// delegates the closing of the appender to the
/// method which must be overridden in the subclass.
///
///
public void Close()
{
// This lock prevents the appender being closed while it is still appending
lock(this)
{
if (!m_closed)
{
OnClose();
m_closed = true;
}
}
}
///
/// Performs threshold checks and invokes filters before
/// delegating actual logging to the subclasses specific
/// method.
///
/// The event to log.
///
///
/// This method cannot be overridden by derived classes. A
/// derived class should override the method
/// which is called by this method.
///
///
/// The implementation of this method is as follows:
///
///
///
/// -
///
/// Checks that the severity of the
/// is greater than or equal to the of this
/// appender.
///
/// -
///
/// Checks that the chain accepts the
/// .
///
///
/// -
///
/// Calls and checks that
/// it returns true.
///
///
///
///
/// If all of the above steps succeed then the
/// will be passed to the abstract method.
///
///
public void DoAppend(LoggingEvent loggingEvent)
{
// This lock is absolutely critical for correct formatting
// of the message in a multi-threaded environment. Without
// this, the message may be broken up into elements from
// multiple thread contexts (like get the wrong thread ID).
lock(this)
{
if (m_closed)
{
ErrorHandler.Error("Attempted to append to closed appender named ["+m_name+"].");
return;
}
// prevent re-entry
if (m_recursiveGuard)
{
return;
}
try
{
m_recursiveGuard = true;
if (FilterEvent(loggingEvent) && PreAppendCheck())
{
this.Append(loggingEvent);
}
}
catch(Exception ex)
{
ErrorHandler.Error("Failed in DoAppend", ex);
}
#if !MONO && !NET_2_0
// on .NET 2.0 (and higher) and Mono (all profiles),
// exceptions that do not derive from System.Exception will be
// wrapped in a RuntimeWrappedException by the runtime, and as
// such will be catched by the catch clause above
catch
{
// Catch handler for non System.Exception types
ErrorHandler.Error("Failed in DoAppend (unknown exception)");
}
#endif
finally
{
m_recursiveGuard = false;
}
}
}
#endregion Implementation of IAppender
#region Implementation of IBulkAppender
///
/// Performs threshold checks and invokes filters before
/// delegating actual logging to the subclasses specific
/// method.
///
/// The array of events to log.
///
///
/// This method cannot be overridden by derived classes. A
/// derived class should override the method
/// which is called by this method.
///
///
/// The implementation of this method is as follows:
///
///
///
/// -
///
/// Checks that the severity of the
/// is greater than or equal to the of this
/// appender.
///
/// -
///
/// Checks that the chain accepts the
/// .
///
///
/// -
///
/// Calls and checks that
/// it returns true.
///
///
///
///
/// If all of the above steps succeed then the
/// will be passed to the method.
///
///
public void DoAppend(LoggingEvent[] loggingEvents)
{
// This lock is absolutely critical for correct formatting
// of the message in a multi-threaded environment. Without
// this, the message may be broken up into elements from
// multiple thread contexts (like get the wrong thread ID).
lock(this)
{
if (m_closed)
{
ErrorHandler.Error("Attempted to append to closed appender named ["+m_name+"].");
return;
}
// prevent re-entry
if (m_recursiveGuard)
{
return;
}
try
{
m_recursiveGuard = true;
ArrayList filteredEvents = new ArrayList(loggingEvents.Length);
foreach(LoggingEvent loggingEvent in loggingEvents)
{
if (FilterEvent(loggingEvent))
{
filteredEvents.Add(loggingEvent);
}
}
if (filteredEvents.Count > 0 && PreAppendCheck())
{
this.Append((LoggingEvent[])filteredEvents.ToArray(typeof(LoggingEvent)));
}
}
catch(Exception ex)
{
ErrorHandler.Error("Failed in Bulk DoAppend", ex);
}
#if !MONO && !NET_2_0
// on .NET 2.0 (and higher) and Mono (all profiles),
// exceptions that do not derive from System.Exception will be
// wrapped in a RuntimeWrappedException by the runtime, and as
// such will be catched by the catch clause above
catch
{
// Catch handler for non System.Exception types
ErrorHandler.Error("Failed in Bulk DoAppend (unknown exception)");
}
#endif
finally
{
m_recursiveGuard = false;
}
}
}
#endregion Implementation of IBulkAppender
///
/// Test if the logging event should we output by this appender
///
/// the event to test
/// true if the event should be output, false if the event should be ignored
///
///
/// This method checks the logging event against the threshold level set
/// on this appender and also against the filters specified on this
/// appender.
///
///
/// The implementation of this method is as follows:
///
///
///
/// -
///
/// Checks that the severity of the
/// is greater than or equal to the of this
/// appender.
///
/// -
///
/// Checks that the chain accepts the
/// .
///
///
///
///
///
virtual protected bool FilterEvent(LoggingEvent loggingEvent)
{
if (!IsAsSevereAsThreshold(loggingEvent.Level))
{
return false;
}
IFilter f = this.FilterHead;
while(f != null)
{
switch(f.Decide(loggingEvent))
{
case FilterDecision.Deny:
return false; // Return without appending
case FilterDecision.Accept:
f = null; // Break out of the loop
break;
case FilterDecision.Neutral:
f = f.Next; // Move to next filter
break;
}
}
return true;
}
#region Public Instance Methods
///
/// Adds a filter to the end of the filter chain.
///
/// the filter to add to this appender
///
///
/// The Filters are organized in a linked list.
///
///
/// Setting this property causes the new filter to be pushed onto the
/// back of the filter chain.
///
///
virtual public void AddFilter(IFilter filter)
{
if (filter == null)
{
throw new ArgumentNullException("filter param must not be null");
}
if (m_headFilter == null)
{
m_headFilter = m_tailFilter = filter;
}
else
{
m_tailFilter.Next = filter;
m_tailFilter = filter;
}
}
///
/// Clears the filter list for this appender.
///
///
///
/// Clears the filter list for this appender.
///
///
virtual public void ClearFilters()
{
m_headFilter = m_tailFilter = null;
}
#endregion Public Instance Methods
#region Protected Instance Methods
///
/// Checks if the message level is below this appender's threshold.
///
/// to test against.
///
///
/// If there is no threshold set, then the return value is always true.
///
///
///
/// true if the meets the
/// requirements of this appender.
///
virtual protected bool IsAsSevereAsThreshold(Level level)
{
return ((m_threshold == null) || level >= m_threshold);
}
///
/// Is called when the appender is closed. Derived classes should override
/// this method if resources need to be released.
///
///
///
/// Releases any resources allocated within the appender such as file handles,
/// network connections, etc.
///
///
/// It is a programming error to append to a closed appender.
///
///
virtual protected void OnClose()
{
// Do nothing by default
}
///
/// Subclasses of should implement this method
/// to perform actual logging.
///
/// The event to append.
///
///
/// A subclass must implement this method to perform
/// logging of the .
///
/// This method will be called by
/// if all the conditions listed for that method are met.
///
///
/// To restrict the logging of events in the appender
/// override the method.
///
///
abstract protected void Append(LoggingEvent loggingEvent);
///
/// Append a bulk array of logging events.
///
/// the array of logging events
///
///
/// This base class implementation calls the
/// method for each element in the bulk array.
///
///
/// A sub class that can better process a bulk array of events should
/// override this method in addition to .
///
///
virtual protected void Append(LoggingEvent[] loggingEvents)
{
foreach(LoggingEvent loggingEvent in loggingEvents)
{
Append(loggingEvent);
}
}
///
/// Called before as a precondition.
///
///
///
/// This method is called by
/// before the call to the abstract method.
///
///
/// This method can be overridden in a subclass to extend the checks
/// made before the event is passed to the method.
///
///
/// A subclass should ensure that they delegate this call to
/// this base class if it is overridden.
///
///
/// true if the call to should proceed.
virtual protected bool PreAppendCheck()
{
if ((m_layout == null) && RequiresLayout)
{
ErrorHandler.Error("AppenderSkeleton: No layout set for the appender named ["+m_name+"].");
return false;
}
return true;
}
///
/// Renders the to a string.
///
/// The event to render.
/// The event rendered as a string.
///
///
/// Helper method to render a to
/// a string. This appender must have a
/// set to render the to
/// a string.
///
/// If there is exception data in the logging event and
/// the layout does not process the exception, this method
/// will append the exception text to the rendered string.
///
///
/// Where possible use the alternative version of this method
/// .
/// That method streams the rendering onto an existing Writer
/// which can give better performance if the caller already has
/// a open and ready for writing.
///
///
protected string RenderLoggingEvent(LoggingEvent loggingEvent)
{
// Create the render writer on first use
if (m_renderWriter == null)
{
m_renderWriter = new ReusableStringWriter(System.Globalization.CultureInfo.InvariantCulture);
}
lock (m_renderWriter)
{
// Reset the writer so we can reuse it
m_renderWriter.Reset(c_renderBufferMaxCapacity, c_renderBufferSize);
RenderLoggingEvent(m_renderWriter, loggingEvent);
return m_renderWriter.ToString();
}
}
///
/// Renders the to a string.
///
/// The event to render.
/// The TextWriter to write the formatted event to
///
///
/// Helper method to render a to
/// a string. This appender must have a
/// set to render the to
/// a string.
///
/// If there is exception data in the logging event and
/// the layout does not process the exception, this method
/// will append the exception text to the rendered string.
///
///
/// Use this method in preference to
/// where possible. If, however, the caller needs to render the event
/// to a string then does
/// provide an efficient mechanism for doing so.
///
///
protected void RenderLoggingEvent(TextWriter writer, LoggingEvent loggingEvent)
{
if (m_layout == null)
{
throw new InvalidOperationException("A layout must be set");
}
if (m_layout.IgnoresException)
{
string exceptionStr = loggingEvent.GetExceptionString();
if (exceptionStr != null && exceptionStr.Length > 0)
{
// render the event and the exception
m_layout.Format(writer, loggingEvent);
writer.WriteLine(exceptionStr);
}
else
{
// there is no exception to render
m_layout.Format(writer, loggingEvent);
}
}
else
{
// The layout will render the exception
m_layout.Format(writer, loggingEvent);
}
}
///
/// Tests if this appender requires a to be set.
///
///
///
/// In the rather exceptional case, where the appender
/// implementation admits a layout but can also work without it,
/// then the appender should return true.
///
///
/// This default implementation always returns false.
///
///
///
/// true if the appender requires a layout object, otherwise false.
///
virtual protected bool RequiresLayout
{
get { return false; }
}
#endregion
#region Private Instance Fields
///
/// The layout of this appender.
///
///
/// See for more information.
///
private ILayout m_layout;
///
/// The name of this appender.
///
///
/// See for more information.
///
private string m_name;
///
/// The level threshold of this appender.
///
///
///
/// There is no level threshold filtering by default.
///
///
/// See for more information.
///
///
private Level m_threshold;
///
/// It is assumed and enforced that errorHandler is never null.
///
///
///
/// It is assumed and enforced that errorHandler is never null.
///
///
/// See for more information.
///
///
private IErrorHandler m_errorHandler;
///
/// The first filter in the filter chain.
///
///
///
/// Set to null initially.
///
///
/// See for more information.
///
///
private IFilter m_headFilter;
///
/// The last filter in the filter chain.
///
///
/// See for more information.
///
private IFilter m_tailFilter;
///
/// Flag indicating if this appender is closed.
///
///
/// See for more information.
///
private bool m_closed = false;
///
/// The guard prevents an appender from repeatedly calling its own DoAppend method
///
private bool m_recursiveGuard = false;
///
/// StringWriter used to render events
///
private ReusableStringWriter m_renderWriter = null;
#endregion Private Instance Fields
#region Constants
///
/// Initial buffer size
///
private const int c_renderBufferSize = 256;
///
/// Maximum buffer size before it is recycled
///
private const int c_renderBufferMaxCapacity = 1024;
#endregion
#region Private Static Fields
///
/// The fully qualified type of the AppenderSkeleton class.
///
///
/// Used by the internal logger to record the Type of the
/// log message.
///
private readonly static Type declaringType = typeof(AppenderSkeleton);
#endregion Private Static Fields
}
}