[
https://issues.apache.org/jira/browse/LOG4NET-407?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13830024#comment-13830024
]
Michael Goldfinger commented on LOG4NET-407:
--------------------------------------------
This is my implementation
{code:java}
namespace Goldfinger.Appender
{
using System;
using System.Threading.Tasks;
using log4net.Appender;
using log4net.Core;
using log4net.Util;
/// <summary>
/// Appender that forwards LoggingEvents asynchronously
/// </summary>
/// <remarks>
/// This appender forwards LoggingEvents to a list of attached
appenders.
/// The events are forwarded asynchronously using the PTL.
/// This allows the calling thread to be released quickly. This
/// implementation does guarantee the ordering of events deliverted
to
/// the attached appenders, however it does not guarantee that all
events
/// got logged if the application exits and there are events in the
queue.
/// To force all events to be logged the last command of an
application
/// before exit should be an LogManager.Shutdown() call.
/// </remarks>
public sealed class AsyncAppender : IBulkAppender, IOptionHandler,
IAppenderAttachable
{
#region Static Fields
/// <summary>
/// The declaring type.
/// </summary>
private static readonly Type DeclaringType =
typeof(AsyncAppender);
#endregion
#region Fields
private AppenderAttachedImpl _appenderAttachedImpl;
private Task _asyncLogTask;
private bool _closed;
private IErrorHandler _errorHandler;
private FixFlags _fixFlags = FixFlags.All;
private string _name;
private bool _recursiveGuard;
#endregion
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see
cref="AsyncAppender"/> class.
/// </summary>
public AsyncAppender()
{
_errorHandler = new
OnlyOnceErrorHandler(GetType().Name);
// Initialise the task for the async operation.
// There is no need to do anything now, just make sure
the Task got scheduled.
_asyncLogTask = new Task(() => { });
_asyncLogTask.Start();
}
/// <summary>
/// Finalizes an instance of the <see cref="AsyncAppender"/>
class.
/// </summary>
~AsyncAppender()
{
// An appender might be closed then garbage collected.
// There is no point in closing twice.
if (!_closed)
{
LogLog.Debug(DeclaringType, "Finalizing
appender named [" + _name + "].");
Close();
}
}
#endregion
#region Public Properties
/// <summary>
/// Gets the known appenders.
/// </summary>
public AppenderCollection Appenders
{
get
{
lock (this)
{
return _appenderAttachedImpl == null ?
AppenderCollection.EmptyCollection : _appenderAttachedImpl.Appenders;
}
}
}
/// <summary>
/// Gets or sets the <see cref="IErrorHandler" /> for this
appender.
/// </summary>
/// <value>The <see cref="IErrorHandler" /> of the
appender</value>
/// <remarks>
/// <para>
/// The <see cref="AppenderSkeleton" /> provides a
default
/// implementation for the <see cref="ErrorHandler" />
property.
/// </para>
/// </remarks>
public IErrorHandler ErrorHandler
{
get
{
return _errorHandler;
}
set
{
lock (this)
{
if (value == null)
{
// We do not throw exception
here since the cause is probably a
// bad config file.
LogLog.Warn(DeclaringType, "You
have tried to set a null error-handler.");
}
else
{
_errorHandler = value;
}
}
}
}
/// <summary>
/// Gets or sets the fix.
/// </summary>
public FixFlags Fix
{
get
{
return _fixFlags;
}
set
{
_fixFlags = value;
}
}
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
#endregion
#region Public Methods and Operators
/// <summary>
/// The activate options.
/// </summary>
public void ActivateOptions()
{
}
/// <summary>
/// Add a new appender.
/// </summary>
/// <param name="newAppender">
/// The new appender.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="newAppender"/> is null.
/// </exception>
public void AddAppender(IAppender newAppender)
{
if (newAppender == null)
{
throw new ArgumentNullException("newAppender");
}
lock (this)
{
if (_appenderAttachedImpl == null)
{
_appenderAttachedImpl = new
AppenderAttachedImpl();
}
_appenderAttachedImpl.AddAppender(newAppender);
}
}
/// <summary>
/// Close the appender.
/// </summary>
public void Close()
{
// Remove all the attached appenders
lock (this)
{
if (!_closed)
{
// Wait till all queued logevents are
written to the connected appenders.
_asyncLogTask.Wait();
if (_appenderAttachedImpl != null)
{
_appenderAttachedImpl.RemoveAllAppenders();
}
_closed = true;
}
}
}
/// <summary>
/// Performs threshold checks and invokes filters before
/// delegating actual logging to the subclasses specific
/// Append method.
/// </summary>
/// <param name="loggingEvent">
/// The event to event.
/// </param>
public void DoAppend(LoggingEvent loggingEvent)
{
lock (this)
{
if (_closed)
{
ErrorHandler.Error("Attempted to append
to closed appender named [" + _name + "].");
return;
}
// prevent re-entry
if (_recursiveGuard)
{
return;
}
try
{
_recursiveGuard = true;
// Makes sure all properties are
cached. To speed up the logging the flags could be set to some other value
// than the default FixFlags.All.
However all Threadbased values such as user properties written to
// ThreadContext.Properties[string] are
lost, if not fixed.
loggingEvent.Fix = _fixFlags;
// This is where the magic happens.
ContinueWith starts the AsyncAppend Method
// as soon as the parent tasks has
finished. Finished tasks are cleaned up by the GC.
// This is because AttachToParent is
not used. In .Net 4.5 this can be enforced with
//
TaskContinuationOptions.DenyChildAttach. Using ContinueWith also enures that the
// logging Events maintain there order.
_asyncLogTask =
_asyncLogTask.ContinueWith(t => AsyncAppend(loggingEvent));
}
catch (Exception ex)
{
ErrorHandler.Error("Failed in
DoAppend", ex);
}
finally
{
_recursiveGuard = false;
}
}
}
/// <summary>
/// Performs threshold checks and invokes filters before
/// delegating actual logging to the subclasses specific
/// Append method.
/// </summary>
/// <param name="loggingEvents">
/// The events to log.
/// </param>
public void DoAppend(LoggingEvent[] loggingEvents)
{
lock (this)
{
if (_closed)
{
ErrorHandler.Error("Attempted to append
to closed appender named [" + _name + "].");
return;
}
// prevent re-entry
if (_recursiveGuard)
{
return;
}
try
{
_recursiveGuard = true;
// Makes sure all properties are
cached. To speed up the logging the flags could be set to some other value
// than the default FixFlags.All.
However all Threadbased values such as user properties written to
// ThreadContext.Properties[string] are
lost, if not fixed.
Parallel.ForEach(loggingEvents, item =>
item.Fix = _fixFlags);
// This is where the magic happens.
ContinueWith starts the AsyncAppend Method
// as soon as the parent tasks has
finished. Finished tasks are cleaned up by the GC.
// This is because AttachToParent is
not used. In .Net 4.5 this can be enforced with
//
TaskContinuationOptions.DenyChildAttach. Using ContinueWith also enures that the
// logging Events maintain there order.
_asyncLogTask =
_asyncLogTask.ContinueWith(t => AsyncAppend(loggingEvents));
}
catch (Exception ex)
{
ErrorHandler.Error("Failed in Bulk
DoAppend", ex);
}
finally
{
_recursiveGuard = false;
}
}
}
/// <summary>
/// Get the appender by name.
/// </summary>
/// <param name="name">
/// The name of the appender.
/// </param>
/// <returns>
/// The <see cref="IAppender"/> reference.
/// </returns>
public IAppender GetAppender(string name)
{
lock (this)
{
if (_appenderAttachedImpl == null || name ==
null)
{
return null;
}
return _appenderAttachedImpl.GetAppender(name);
}
}
/// <summary>
/// Remove all appenders.
/// </summary>
public void RemoveAllAppenders()
{
lock (this)
{
// Wait till all queued logevents are written
to the connected appenders.
_asyncLogTask.Wait();
if (_appenderAttachedImpl != null)
{
_appenderAttachedImpl.RemoveAllAppenders();
_appenderAttachedImpl = null;
}
}
}
/// <summary>
/// Remove appender method.
/// </summary>
/// <param name="appender">
/// The appender to remove.
/// </param>
/// <returns>
/// The <see cref="IAppender"/> reference.
/// </returns>
public IAppender RemoveAppender(IAppender appender)
{
lock (this)
{
// Wait till all queued logevents are written
to the connected appenders.
_asyncLogTask.Wait();
if (appender != null && _appenderAttachedImpl
!= null)
{
return
_appenderAttachedImpl.RemoveAppender(appender);
}
}
return null;
}
/// <summary>
/// Remove appender by name.
/// </summary>
/// <param name="name">
/// The name of the appender.
/// </param>
/// <returns>
/// The <see cref="IAppender"/> reference.
/// </returns>
public IAppender RemoveAppender(string name)
{
lock (this)
{
// Wait till all queued logevents are written
to the connected appenders.
_asyncLogTask.Wait();
if (name != null && _appenderAttachedImpl !=
null)
{
return
_appenderAttachedImpl.RemoveAppender(name);
}
}
return null;
}
#endregion
#region Methods
/// <summary>
/// The async append.
/// </summary>
/// <param name="loggingEvent">
/// The logging event.
/// </param>
private void AsyncAppend(LoggingEvent loggingEvent)
{
if (_appenderAttachedImpl != null)
{
_appenderAttachedImpl.AppendLoopOnAppenders(loggingEvent);
}
}
/// <summary>
/// The async append.
/// </summary>
/// <param name="loggingEvents">
/// The logging events.
/// </param>
private void AsyncAppend(LoggingEvent[] loggingEvents)
{
if (_appenderAttachedImpl != null)
{
_appenderAttachedImpl.AppendLoopOnAppenders(loggingEvents);
}
}
#endregion
}
}
{code}
> AsyncAppender - better Implementation
> -------------------------------------
>
> Key: LOG4NET-407
> URL: https://issues.apache.org/jira/browse/LOG4NET-407
> Project: Log4net
> Issue Type: Improvement
> Components: Appenders
> Affects Versions: 4.0
> Environment: .Net 4.0 and newer
> Reporter: Michael Goldfinger
> Priority: Minor
>
> I checked out the AsyncAppender and found some drawbacks.
> * logevents are not logged if the appender close
> * order of logevents got lost
> I created an new implementation that waits for all logevents to be computed
> before close and maintains the order of the events. If the application
> process got killed the logevents are lost too but in any other case the loss
> of logevents could be prevented. The drawback of my implementation is that
> the TLP is requred so .NET 2.0 is not supported.
> I could not find the place to contribute so I created this ticket. I hope
> it's useful.
--
This message was sent by Atlassian JIRA
(v6.1#6144)