#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 { /// /// 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. /// /// #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 /// /// 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."); } } /// /// Flushes any buffered log data. /// /// The maximum time to wait for logging events to be flushed. /// True if all logging events were flushed successfully, else false. public override bool Flush(int millisecondsTimeout) { base.Flush(); return m_workQueueEmptyEvent.WaitOne(millisecondsTimeout, false); } #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