123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- #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
- // .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
- {
- /// <summary>
- /// Delivers logging events to a remote logging sink.
- /// </summary>
- /// <remarks>
- /// <para>
- /// This Appender is designed to deliver events to a remote sink.
- /// That is any object that implements the <see cref="IRemoteLoggingSink"/>
- /// interface. It delivers the events using .NET remoting. The
- /// object to deliver events to is specified by setting the
- /// appenders <see cref="RemotingAppender.Sink"/> property.</para>
- /// <para>
- /// The RemotingAppender buffers events before sending them. This allows it to
- /// make more efficient use of the remoting infrastructure.</para>
- /// <para>
- /// 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 <see cref="CallContext"/>),
- /// if they are marked as <see cref="ILogicalThreadAffinative"/>, across the
- /// remoting boundary. If the server is not contactable then
- /// the remoting infrastructure will clear the <see cref="ILogicalThreadAffinative"/>
- /// objects from the <see cref="CallContext"/>. 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 <see cref="ThreadPool"/>
- /// thread is used for this. If no <see cref="ThreadPool"/> thread is available then
- /// the events will block in the thread pool manager until a thread is available.</para>
- /// <para>
- /// 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.</para>
- /// <para>
- /// If this appender is being closed because the <see cref="AppDomain.ProcessExit"/>
- /// event has fired it may not be possible to send all the queued events. During process
- /// exit the runtime limits the time that a <see cref="AppDomain.ProcessExit"/>
- /// 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
- /// <see cref="log4net.Core.LoggerManager.Shutdown"/> for details on how to shutdown
- /// log4net programmatically.</para>
- /// </remarks>
- /// <seealso cref="IRemoteLoggingSink" />
- /// <author>Nicko Cadell</author>
- /// <author>Gert Driesen</author>
- /// <author>Daniel Cazzulino</author>
- public class RemotingAppender : BufferingAppenderSkeleton
- {
- #region Public Instance Constructors
- /// <summary>
- /// Initializes a new instance of the <see cref="RemotingAppender" /> class.
- /// </summary>
- /// <remarks>
- /// <para>
- /// Default constructor.
- /// </para>
- /// </remarks>
- public RemotingAppender()
- {
- }
- #endregion Public Instance Constructors
- #region Public Instance Properties
- /// <summary>
- /// Gets or sets the URL of the well-known object that will accept
- /// the logging events.
- /// </summary>
- /// <value>
- /// The well-known URL of the remote sink.
- /// </value>
- /// <remarks>
- /// <para>
- /// The URL of the remoting sink that will accept logging events.
- /// The sink must implement the <see cref="IRemoteLoggingSink"/>
- /// interface.
- /// </para>
- /// </remarks>
- public string Sink
- {
- get { return m_sinkUrl; }
- set { m_sinkUrl = value; }
- }
- #endregion Public Instance Properties
- #region Implementation of IOptionHandler
- /// <summary>
- /// Initialize the appender based on the options set
- /// </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>
- /// </remarks>
- #if NET_4_0 || MONO_4_0
- [System.Security.SecuritySafeCritical]
- #endif
- 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
- /// <summary>
- /// Send the contents of the buffer to the remote sink.
- /// </summary>
- /// <remarks>
- /// 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 <see cref="CallContext"/>),
- /// if they are marked as <see cref="ILogicalThreadAffinative"/>, across the
- /// remoting boundary. If the server is not contactable then
- /// the remoting infrastructure will clear the <see cref="ILogicalThreadAffinative"/>
- /// objects from the <see cref="CallContext"/>. 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 <see cref="ThreadPool"/>
- /// thread is used for this. If no <see cref="ThreadPool"/> thread is available then
- /// the events will block in the thread pool manager until a thread is available.
- /// </remarks>
- /// <param name="events">The events to send.</param>
- 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.");
- }
- }
- /// <summary>
- /// Override base class close.
- /// </summary>
- /// <remarks>
- /// <para>
- /// This method waits while there are queued work items. The events are
- /// sent asynchronously using <see cref="ThreadPool"/> 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.</para>
- /// <para>
- /// This method attempts to wait until all the queued events have been sent, but this
- /// method will timeout after 30 seconds regardless.</para>
- /// <para>
- /// If the appender is being closed because the <see cref="AppDomain.ProcessExit"/>
- /// event has fired it may not be possible to send all the queued events. During process
- /// exit the runtime limits the time that a <see cref="AppDomain.ProcessExit"/>
- /// event handler is allowed to run for.</para>
- /// </remarks>
- 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.");
- }
- }
- /// <summary>
- /// Flushes any buffered log data.
- /// </summary>
- /// <param name="millisecondsTimeout">The maximum time to wait for logging events to be flushed.</param>
- /// <returns><c>True</c> if all logging events were flushed successfully, else <c>false</c>.</returns>
- public override bool Flush(int millisecondsTimeout)
- {
- base.Flush();
- return m_workQueueEmptyEvent.WaitOne(millisecondsTimeout, false);
- }
- #endregion
- /// <summary>
- /// A work item is being queued into the thread pool
- /// </summary>
- private void BeginAsyncSend()
- {
- // The work queue is not empty
- m_workQueueEmptyEvent.Reset();
- // Increment the queued count
- Interlocked.Increment(ref m_queuedCallbackCount);
- }
- /// <summary>
- /// A work item from the thread pool has completed
- /// </summary>
- 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();
- }
- }
- /// <summary>
- /// Send the contents of the buffer to the remote sink.
- /// </summary>
- /// <remarks>
- /// This method is designed to be used with the <see cref="ThreadPool"/>.
- /// This method expects to be passed an array of <see cref="LoggingEvent"/>
- /// objects in the state param.
- /// </remarks>
- /// <param name="state">the logging events to send</param>
- 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
- /// <summary>
- /// The URL of the remote sink.
- /// </summary>
- private string m_sinkUrl;
- /// <summary>
- /// The local proxy (.NET remoting) for the remote logging sink.
- /// </summary>
- private IRemoteLoggingSink m_sinkObj;
- /// <summary>
- /// The number of queued callbacks currently waiting or executing
- /// </summary>
- private int m_queuedCallbackCount = 0;
- /// <summary>
- /// Event used to signal when there are no queued work items
- /// </summary>
- /// <remarks>
- /// This event is set when there are no queued work items. In this
- /// state it is safe to close the appender.
- /// </remarks>
- private ManualResetEvent m_workQueueEmptyEvent = new ManualResetEvent(true);
- #endregion Private Instance Fields
- /// <summary>
- /// Interface used to deliver <see cref="LoggingEvent"/> objects to a remote sink.
- /// </summary>
- /// <remarks>
- /// This interface must be implemented by a remoting sink
- /// if the <see cref="RemotingAppender"/> is to be used
- /// to deliver logging events to the sink.
- /// </remarks>
- public interface IRemoteLoggingSink
- {
- /// <summary>
- /// Delivers logging events to the remote sink
- /// </summary>
- /// <param name="events">Array of events to log.</param>
- /// <remarks>
- /// <para>
- /// Delivers logging events to the remote sink
- /// </para>
- /// </remarks>
- void LogEvents(LoggingEvent[] events);
- }
- }
- }
- #endif // !NETCF
|