| #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 |