#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
// .NET Compact Framework 1.0 has no support for System.Runtime.Remoting
#if !NETCF
using System;
using System.Collections;
using System.Threading;
using System.Runtime.Remoting.Messaging;
using log4net.Layout;
using log4net.Core;
using log4net.Util;
namespace log4net.Appender
{
///
/// Delivers logging events to a remote logging sink.
///
///
///
/// This Appender is designed to deliver events to a remote sink.
/// That is any object that implements the
/// interface. It delivers the events using .NET remoting. The
/// object to deliver events to is specified by setting the
/// appenders property.
///
/// The RemotingAppender buffers events before sending them. This allows it to
/// make more efficient use of the remoting infrastructure.
///
/// Once the buffer is full the events are still not sent immediately.
/// They are scheduled to be sent using a pool thread. The effect is that
/// the send occurs asynchronously. This is very important for a
/// number of non obvious reasons. The remoting infrastructure will
/// flow thread local variables (stored in the ),
/// if they are marked as , across the
/// remoting boundary. If the server is not contactable then
/// the remoting infrastructure will clear the
/// objects from the . To prevent a logging failure from
/// having side effects on the calling application the remoting call must be made
/// from a separate thread to the one used by the application. A
/// thread is used for this. If no thread is available then
/// the events will block in the thread pool manager until a thread is available.
///
/// Because the events are sent asynchronously using pool threads it is possible to close
/// this appender before all the queued events have been sent.
/// When closing the appender attempts to wait until all the queued events have been sent, but
/// this will timeout after 30 seconds regardless.
///
/// If this appender is being closed because the
/// event has fired it may not be possible to send all the queued events. During process
/// exit the runtime limits the time that a
/// event handler is allowed to run for. If the runtime terminates the threads before
/// the queued events have been sent then they will be lost. To ensure that all events
/// are sent the appender must be closed before the application exits. See
/// for details on how to shutdown
/// log4net programmatically.
///
///
/// Nicko Cadell
/// Gert Driesen
/// Daniel Cazzulino
public class RemotingAppender : BufferingAppenderSkeleton
{
#region Public Instance Constructors
///
/// Initializes a new instance of the class.
///
///
///
/// Default constructor.
///
///
public RemotingAppender()
{
}
#endregion Public Instance Constructors
#region Public Instance Properties
///
/// Gets or sets the URL of the well-known object that will accept
/// the logging events.
///
///
/// The well-known URL of the remote sink.
///
///
///
/// The URL of the remoting sink that will accept logging events.
/// The sink must implement the
/// interface.
///
///
public string Sink
{
get { return m_sinkUrl; }
set { m_sinkUrl = value; }
}
#endregion Public Instance Properties
#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();
IDictionary channelProperties = new Hashtable();
channelProperties["typeFilterLevel"] = "Full";
m_sinkObj = (IRemoteLoggingSink)Activator.GetObject(typeof(IRemoteLoggingSink), m_sinkUrl, channelProperties);
}
#endregion
#region Override implementation of BufferingAppenderSkeleton
///
/// Send the contents of the buffer to the remote sink.
///
///
/// The events are not sent immediately. They are scheduled to be sent
/// using a pool thread. The effect is that the send occurs asynchronously.
/// This is very important for a number of non obvious reasons. The remoting
/// infrastructure will flow thread local variables (stored in the ),
/// if they are marked as , across the
/// remoting boundary. If the server is not contactable then
/// the remoting infrastructure will clear the
/// objects from the . To prevent a logging failure from
/// having side effects on the calling application the remoting call must be made
/// from a separate thread to the one used by the application. A
/// thread is used for this. If no thread is available then
/// the events will block in the thread pool manager until a thread is available.
///
/// The events to send.
override protected void SendBuffer(LoggingEvent[] events)
{
// Setup for an async send
BeginAsyncSend();
// Send the events
if (!ThreadPool.QueueUserWorkItem(new WaitCallback(SendBufferCallback), events))
{
// Cancel the async send
EndAsyncSend();
ErrorHandler.Error("RemotingAppender ["+Name+"] failed to ThreadPool.QueueUserWorkItem logging events in SendBuffer.");
}
}
///
/// Override base class close.
///
///
///
/// This method waits while there are queued work items. The events are
/// sent asynchronously using work items. These items
/// will be sent once a thread pool thread is available to send them, therefore
/// it is possible to close the appender before all the queued events have been
/// sent.
///
/// This method attempts to wait until all the queued events have been sent, but this
/// method will timeout after 30 seconds regardless.
///
/// If the appender is being closed because the
/// event has fired it may not be possible to send all the queued events. During process
/// exit the runtime limits the time that a
/// event handler is allowed to run for.
///
override protected void OnClose()
{
base.OnClose();
// Wait for the work queue to become empty before closing, timeout 30 seconds
if (!m_workQueueEmptyEvent.WaitOne(30 * 1000, false))
{
ErrorHandler.Error("RemotingAppender ["+Name+"] failed to send all queued events before close, in OnClose.");
}
}
#endregion
///
/// A work item is being queued into the thread pool
///
private void BeginAsyncSend()
{
// The work queue is not empty
m_workQueueEmptyEvent.Reset();
// Increment the queued count
Interlocked.Increment(ref m_queuedCallbackCount);
}
///
/// A work item from the thread pool has completed
///
private void EndAsyncSend()
{
// Decrement the queued count
if (Interlocked.Decrement(ref m_queuedCallbackCount) <= 0)
{
// If the work queue is empty then set the event
m_workQueueEmptyEvent.Set();
}
}
///
/// Send the contents of the buffer to the remote sink.
///
///
/// This method is designed to be used with the .
/// This method expects to be passed an array of
/// objects in the state param.
///
/// the logging events to send
private void SendBufferCallback(object state)
{
try
{
LoggingEvent[] events = (LoggingEvent[])state;
// Send the events
m_sinkObj.LogEvents(events);
}
catch(Exception ex)
{
ErrorHandler.Error("Failed in SendBufferCallback", ex);
}
finally
{
EndAsyncSend();
}
}
#region Private Instance Fields
///
/// The URL of the remote sink.
///
private string m_sinkUrl;
///
/// The local proxy (.NET remoting) for the remote logging sink.
///
private IRemoteLoggingSink m_sinkObj;
///
/// The number of queued callbacks currently waiting or executing
///
private int m_queuedCallbackCount = 0;
///
/// Event used to signal when there are no queued work items
///
///
/// This event is set when there are no queued work items. In this
/// state it is safe to close the appender.
///
private ManualResetEvent m_workQueueEmptyEvent = new ManualResetEvent(true);
#endregion Private Instance Fields
///
/// Interface used to deliver objects to a remote sink.
///
///
/// This interface must be implemented by a remoting sink
/// if the is to be used
/// to deliver logging events to the sink.
///
public interface IRemoteLoggingSink
{
///
/// Delivers logging events to the remote sink
///
/// Array of events to log.
///
///
/// Delivers logging events to the remote sink
///
///
void LogEvents(LoggingEvent[] events);
}
}
}
#endif // !NETCF