#region Copyright & License
//
// Copyright 2001-2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System;
using System.Collections;
using log4net.Util;
using log4net.Core;
namespace log4net.Appender
{
///
/// Abstract base class implementation of that
/// buffers events in a fixed size buffer.
///
///
///
/// This base class should be used by appenders that need to buffer a
/// number of events before logging them. For example the
/// buffers events and then submits the entire contents of the buffer to
/// the underlying database in one go.
///
///
/// Subclasses should override the
/// method to deliver the buffered events.
///
/// The BufferingAppenderSkeleton maintains a fixed size cyclic
/// buffer of events. The size of the buffer is set using
/// the property.
///
/// A is used to inspect
/// each event as it arrives in the appender. If the
/// triggers, then the current buffer is sent immediately
/// (see ). Otherwise the event
/// is stored in the buffer. For example, an evaluator can be used to
/// deliver the events immediately when an ERROR event arrives.
///
///
/// The buffering appender can be configured in a mode.
/// By default the appender is NOT lossy. When the buffer is full all
/// the buffered events are sent with .
/// If the property is set to true then the
/// buffer will not be sent when it is full, and new events arriving
/// in the appender will overwrite the oldest event in the buffer.
/// In lossy mode the buffer will only be sent when the
/// triggers. This can be useful behavior when you need to know about
/// ERROR events but not about events with a lower level, configure an
/// evaluator that will trigger when an ERROR event arrives, the whole
/// buffer will be sent which gives a history of events leading up to
/// the ERROR event.
///
///
/// Nicko Cadell
/// Gert Driesen
public abstract class BufferingAppenderSkeleton : AppenderSkeleton
{
#region Protected Instance Constructors
///
/// Initializes a new instance of the class.
///
///
///
/// Protected default constructor to allow subclassing.
///
///
protected BufferingAppenderSkeleton() : this(true)
{
}
///
/// Initializes a new instance of the class.
///
/// the events passed through this appender must be
/// fixed by the time that they arrive in the derived class' SendBuffer method.
///
///
/// Protected constructor to allow subclassing.
///
///
/// The should be set if the subclass
/// expects the events delivered to be fixed even if the
/// is set to zero, i.e. when no buffering occurs.
///
///
protected BufferingAppenderSkeleton(bool eventMustBeFixed) : base()
{
m_eventMustBeFixed = eventMustBeFixed;
}
#endregion Protected Instance Constructors
#region Public Instance Properties
///
/// Gets or sets a value that indicates whether the appender is lossy.
///
///
/// true if the appender is lossy, otherwise false. The default is false.
///
///
///
/// This appender uses a buffer to store logging events before
/// delivering them. A triggering event causes the whole buffer
/// to be send to the remote sink. If the buffer overruns before
/// a triggering event then logging events could be lost. Set
/// to false to prevent logging events
/// from being lost.
///
/// If is set to true then an
/// must be specified.
///
public bool Lossy
{
get { return m_lossy; }
set { m_lossy = value; }
}
///
/// Gets or sets the size of the cyclic buffer used to hold the
/// logging events.
///
///
/// The size of the cyclic buffer used to hold the logging events.
///
///
///
/// The option takes a positive integer
/// representing the maximum number of logging events to collect in
/// a cyclic buffer. When the is reached,
/// oldest events are deleted as new events are added to the
/// buffer. By default the size of the cyclic buffer is 512 events.
///
///
/// If the is set to a value less than
/// or equal to 1 then no buffering will occur. The logging event
/// will be delivered synchronously (depending on the
/// and properties). Otherwise the event will
/// be buffered.
///
///
public int BufferSize
{
get { return m_bufferSize; }
set { m_bufferSize = value; }
}
///
/// Gets or sets the that causes the
/// buffer to be sent immediately.
///
///
/// The that causes the buffer to be
/// sent immediately.
///
///
///
/// The evaluator will be called for each event that is appended to this
/// appender. If the evaluator triggers then the current buffer will
/// immediately be sent (see ).
///
/// If is set to true then an
/// must be specified.
///
public ITriggeringEventEvaluator Evaluator
{
get { return m_evaluator; }
set { m_evaluator = value; }
}
///
/// Gets or sets the value of the to use.
///
///
/// The value of the to use.
///
///
///
/// The evaluator will be called for each event that is discarded from this
/// appender. If the evaluator triggers then the current buffer will immediately
/// be sent (see ).
///
///
public ITriggeringEventEvaluator LossyEvaluator
{
get { return m_lossyEvaluator; }
set { m_lossyEvaluator = value; }
}
///
/// Gets or sets a value indicating if only part of the logging event data
/// should be fixed.
///
///
/// true if the appender should only fix part of the logging event
/// data, otherwise false. The default is false.
///
///
///
/// Setting this property to true will cause only part of the
/// event data to be fixed and serialized. This will improve performance.
///
///
/// See for more information.
///
///
[Obsolete("Use Fix property")]
virtual public bool OnlyFixPartialEventData
{
get { return (Fix == FixFlags.Partial); }
set
{
if (value)
{
Fix = FixFlags.Partial;
}
else
{
Fix = FixFlags.All;
}
}
}
///
/// Gets or sets a the fields that will be fixed in the event
///
///
/// The event fields that will be fixed before the event is buffered
///
///
///
/// The logging event needs to have certain thread specific values
/// captured before it can be buffered. See
/// for details.
///
///
///
virtual public FixFlags Fix
{
get { return m_fixFlags; }
set { m_fixFlags = value; }
}
#endregion Public Instance Properties
#region Public Methods
///
/// Flush the currently buffered events
///
///
///
/// Flushes any events that have been buffered.
///
///
/// If the appender is buffering in mode then the contents
/// of the buffer will NOT be flushed to the appender.
///
///
public virtual void Flush()
{
Flush(false);
}
///
/// Flush the currently buffered events
///
/// set to true to flush the buffer of lossy events
///
///
/// Flushes events that have been buffered. If is
/// false then events will only be flushed if this buffer is non-lossy mode.
///
///
/// If the appender is buffering in mode then the contents
/// of the buffer will only be flushed if is true.
/// In this case the contents of the buffer will be tested against the
/// and if triggering will be output. All other buffered
/// events will be discarded.
///
///
/// If is true then the buffer will always
/// be emptied by calling this method.
///
///
public virtual void Flush(bool flushLossyBuffer)
{
// This method will be called outside of the AppenderSkeleton DoAppend() method
// therefore it needs to be protected by its own lock. This will block any
// Appends while the buffer is flushed.
lock(this)
{
if (m_cb != null && m_cb.Length > 0)
{
if (m_lossy)
{
// If we are allowed to eagerly flush from the lossy buffer
if (flushLossyBuffer)
{
if (m_lossyEvaluator != null)
{
// Test the contents of the buffer against the lossy evaluator
LoggingEvent[] bufferedEvents = m_cb.PopAll();
ArrayList filteredEvents = new ArrayList(bufferedEvents.Length);
foreach(LoggingEvent loggingEvent in bufferedEvents)
{
if (m_lossyEvaluator.IsTriggeringEvent(loggingEvent))
{
filteredEvents.Add(loggingEvent);
}
}
// Send the events that meet the lossy evaluator criteria
if (filteredEvents.Count > 0)
{
SendBuffer((LoggingEvent[])filteredEvents.ToArray(typeof(LoggingEvent)));
}
}
else
{
// No lossy evaluator, all buffered events are discarded
m_cb.Clear();
}
}
}
else
{
// Not lossy, send whole buffer
SendFromBuffer(null, m_cb);
}
}
}
}
#endregion Public Methods
#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.
///
///
override public void ActivateOptions()
{
base.ActivateOptions();
// If the appender is in Lossy mode then we will
// only send the buffer when the Evaluator triggers
// therefore check we have an evaluator.
if (m_lossy && m_evaluator == null)
{
ErrorHandler.Error("Appender ["+Name+"] is Lossy but has no Evaluator. The buffer will never be sent!");
}
if (m_bufferSize > 1)
{
m_cb = new CyclicBuffer(m_bufferSize);
}
else
{
m_cb = null;
}
}
#endregion Implementation of IOptionHandler
#region Override implementation of AppenderSkeleton
///
/// Close this appender instance.
///
///
///
/// Close this appender instance. If this appender is marked
/// as not then the remaining events in
/// the buffer must be sent when the appender is closed.
///
///
override protected void OnClose()
{
// Flush the buffer on close
Flush(true);
}
///
/// This method is called by the method.
///
/// the event to log
///
///
/// Stores the in the cyclic buffer.
///
///
/// The buffer will be sent (i.e. passed to the
/// method) if one of the following conditions is met:
///
///
/// -
/// The cyclic buffer is full and this appender is
/// marked as not lossy (see )
///
/// -
/// An is set and
/// it is triggered for the
/// specified.
///
///
///
/// Before the event is stored in the buffer it is fixed
/// (see ) to ensure that
/// any data referenced by the event will be valid when the buffer
/// is processed.
///
///
override protected void Append(LoggingEvent loggingEvent)
{
// If the buffer size is set to 1 or less then the buffer will be
// sent immediately because there is not enough space in the buffer
// to buffer up more than 1 event. Therefore as a special case
// we don't use the buffer at all.
if (m_cb == null || m_bufferSize <= 1)
{
// Only send the event if we are in non lossy mode or the event is a triggering event
if ((!m_lossy) ||
(m_evaluator != null && m_evaluator.IsTriggeringEvent(loggingEvent)) ||
(m_lossyEvaluator != null && m_lossyEvaluator.IsTriggeringEvent(loggingEvent)))
{
if (m_eventMustBeFixed)
{
// Derive class expects fixed events
loggingEvent.Fix = this.Fix;
}
// Not buffering events, send immediately
SendBuffer(new LoggingEvent[] { loggingEvent } );
}
}
else
{
// Because we are caching the LoggingEvent beyond the
// lifetime of the Append() method we must fix any
// volatile data in the event.
loggingEvent.Fix = this.Fix;
// Add to the buffer, returns the event discarded from the buffer if there is no space remaining after the append
LoggingEvent discardedLoggingEvent = m_cb.Append(loggingEvent);
if (discardedLoggingEvent != null)
{
// Buffer is full and has had to discard an event
if (!m_lossy)
{
// Not lossy, must send all events
SendFromBuffer(discardedLoggingEvent, m_cb);
}
else
{
// Check if the discarded event should not be logged
if (m_lossyEvaluator == null || !m_lossyEvaluator.IsTriggeringEvent(discardedLoggingEvent))
{
// Clear the discarded event as we should not forward it
discardedLoggingEvent = null;
}
// Check if the event should trigger the whole buffer to be sent
if (m_evaluator != null && m_evaluator.IsTriggeringEvent(loggingEvent))
{
SendFromBuffer(discardedLoggingEvent, m_cb);
}
else if (discardedLoggingEvent != null)
{
// Just send the discarded event
SendBuffer(new LoggingEvent[] { discardedLoggingEvent } );
}
}
}
else
{
// Buffer is not yet full
// Check if the event should trigger the whole buffer to be sent
if (m_evaluator != null && m_evaluator.IsTriggeringEvent(loggingEvent))
{
SendFromBuffer(null, m_cb);
}
}
}
}
#endregion Override implementation of AppenderSkeleton
#region Protected Instance Methods
///
/// Sends the contents of the buffer.
///
/// The first logging event.
/// The buffer containing the events that need to be send.
///
///
/// The subclass must override .
///
///
virtual protected void SendFromBuffer(LoggingEvent firstLoggingEvent, CyclicBuffer buffer)
{
LoggingEvent[] bufferEvents = buffer.PopAll();
if (firstLoggingEvent == null)
{
SendBuffer(bufferEvents);
}
else if (bufferEvents.Length == 0)
{
SendBuffer(new LoggingEvent[] { firstLoggingEvent } );
}
else
{
// Create new array with the firstLoggingEvent at the head
LoggingEvent[] events = new LoggingEvent[bufferEvents.Length + 1];
Array.Copy(bufferEvents, 0, events, 1, bufferEvents.Length);
events[0] = firstLoggingEvent;
SendBuffer(events);
}
}
#endregion Protected Instance Methods
///
/// Sends the events.
///
/// The events that need to be send.
///
///
/// The subclass must override this method to process the buffered events.
///
///
abstract protected void SendBuffer(LoggingEvent[] events);
#region Private Static Fields
///
/// The default buffer size.
///
///
/// The default size of the cyclic buffer used to store events.
/// This is set to 512 by default.
///
private const int DEFAULT_BUFFER_SIZE = 512;
#endregion Private Static Fields
#region Private Instance Fields
///
/// The size of the cyclic buffer used to hold the logging events.
///
///
/// Set to by default.
///
private int m_bufferSize = DEFAULT_BUFFER_SIZE;
///
/// The cyclic buffer used to store the logging events.
///
private CyclicBuffer m_cb;
///
/// The triggering event evaluator that causes the buffer to be sent immediately.
///
///
/// The object that is used to determine if an event causes the entire
/// buffer to be sent immediately. This field can be null, which
/// indicates that event triggering is not to be done. The evaluator
/// can be set using the property. If this appender
/// has the ( property) set to
/// true then an must be set.
///
private ITriggeringEventEvaluator m_evaluator;
///
/// Indicates if the appender should overwrite events in the cyclic buffer
/// when it becomes full, or if the buffer should be flushed when the
/// buffer is full.
///
///
/// If this field is set to true then an must
/// be set.
///
private bool m_lossy = false;
///
/// The triggering event evaluator filters discarded events.
///
///
/// The object that is used to determine if an event that is discarded should
/// really be discarded or if it should be sent to the appenders.
/// This field can be null, which indicates that all discarded events will
/// be discarded.
///
private ITriggeringEventEvaluator m_lossyEvaluator;
///
/// Value indicating which fields in the event should be fixed
///
///
/// By default all fields are fixed
///
private FixFlags m_fixFlags = FixFlags.All;
///
/// The events delivered to the subclass must be fixed.
///
private readonly bool m_eventMustBeFixed;
#endregion Private Instance Fields
}
}