LOG4NET-511 Implement flushing of appenders that buffer data
Patch by @JJoe2
closes #37
diff --git a/src/Appender/BufferingAppenderSkeleton.cs b/src/Appender/BufferingAppenderSkeleton.cs
index fcb2b50..a35ef38 100644
--- a/src/Appender/BufferingAppenderSkeleton.cs
+++ b/src/Appender/BufferingAppenderSkeleton.cs
@@ -68,7 +68,7 @@
/// </remarks>
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
- public abstract class BufferingAppenderSkeleton : AppenderSkeleton
+ public abstract class BufferingAppenderSkeleton : AppenderSkeleton, IFlushable
{
#region Protected Instance Constructors
@@ -261,6 +261,17 @@
#region Public Methods
+ /// <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 virtual bool Flush(int millisecondsTimeout)
+ {
+ Flush();
+ return true;
+ }
+
/// <summary>
/// Flush the currently buffered events
/// </summary>
diff --git a/src/Appender/DebugAppender.cs b/src/Appender/DebugAppender.cs
index d3cea92..3ad3f8c 100644
--- a/src/Appender/DebugAppender.cs
+++ b/src/Appender/DebugAppender.cs
@@ -40,7 +40,7 @@
/// </para>
/// </remarks>
/// <author>Nicko Cadell</author>
- public class DebugAppender : AppenderSkeleton
+ public class DebugAppender : AppenderSkeleton, IFlushable
{
#region Public Instance Constructors
@@ -102,6 +102,23 @@
#endregion Public Instance Properties
+
+ /// <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 bool Flush(int millisecondsTimeout)
+ {
+ // Nothing to do if ImmediateFlush is true
+ if (m_immediateFlush) return true;
+
+ // System.Diagnostics.Debug is thread-safe, so no need for lock(this).
+ System.Diagnostics.Debug.Flush();
+
+ return true;
+ }
+
#region Override implementation of AppenderSkeleton
/// <summary>
diff --git a/src/Appender/IFlushable.cs b/src/Appender/IFlushable.cs
new file mode 100644
index 0000000..63d3cd9
--- /dev/null
+++ b/src/Appender/IFlushable.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace log4net.Appender
+{
+ /// <summary>
+ /// Interface that can be implemented by Appenders that buffer logging data and expose a <see cref="Flush"/> method.
+ /// </summary>
+ public interface IFlushable
+ {
+ /// <summary>
+ /// Flushes any buffered log data.
+ /// </summary>
+ /// <remarks>
+ /// Appenders that implement the <see cref="Flush"/> method must do so in a thread-safe manner: it can be called concurrently with
+ /// the <see cref="log4net.Appender.IAppender.DoAppend"/> method.
+ /// <para>
+ /// Typically this is done by locking on the Appender instance, e.g.:
+ /// <code>
+ /// <![CDATA[
+ /// public bool Flush(int millisecondsTimeout)
+ /// {
+ /// lock(this)
+ /// {
+ /// // Flush buffered logging data
+ /// ...
+ /// }
+ /// }
+ /// ]]>
+ /// </code>
+ /// </para>
+ /// <para>
+ /// The <paramref name="millisecondsTimeout"/> parameter is only relevant for appenders that process logging events asynchronously,
+ /// such as <see cref="RemotingAppender"/>.
+ /// </para>
+ /// </remarks>
+ /// <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>
+ bool Flush(int millisecondsTimeout);
+ }
+}
diff --git a/src/Appender/RemotingAppender.cs b/src/Appender/RemotingAppender.cs
index a517c61..b334920 100644
--- a/src/Appender/RemotingAppender.cs
+++ b/src/Appender/RemotingAppender.cs
@@ -218,6 +218,17 @@
}
}
+ /// <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>
diff --git a/src/Appender/TextWriterAppender.cs b/src/Appender/TextWriterAppender.cs
index e566c8f..5708d30 100644
--- a/src/Appender/TextWriterAppender.cs
+++ b/src/Appender/TextWriterAppender.cs
@@ -42,7 +42,7 @@
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
/// <author>Douglas de la Torre</author>
- public class TextWriterAppender : AppenderSkeleton
+ public class TextWriterAppender : AppenderSkeleton, IFlushable
{
#region Public Instance Constructors
@@ -481,5 +481,24 @@
private readonly static Type declaringType = typeof(TextWriterAppender);
#endregion Private Static Fields
+
+ /// <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 bool Flush(int millisecondsTimeout)
+ {
+ // Nothing to do if ImmediateFlush is true
+ if (m_immediateFlush) return true;
+
+ // lock(this) will block any Appends while the buffer is flushed.
+ lock (this)
+ {
+ m_qtw.Flush();
+ }
+
+ return true;
+ }
}
}
diff --git a/src/Appender/TraceAppender.cs b/src/Appender/TraceAppender.cs
index 2aacb86..68e15f7 100644
--- a/src/Appender/TraceAppender.cs
+++ b/src/Appender/TraceAppender.cs
@@ -51,7 +51,7 @@
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
/// <author>Ron Grabowski</author>
- public class TraceAppender : AppenderSkeleton
+ public class TraceAppender : AppenderSkeleton, IFlushable
{
#region Public Instance Constructors
@@ -206,5 +206,24 @@
private PatternLayout m_category = new PatternLayout("%logger");
#endregion Private Instance Fields
- }
+
+ /// <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 bool Flush(int millisecondsTimeout)
+ {
+ // Nothing to do if ImmediateFlush is true
+ if (m_immediateFlush) return true;
+
+ // System.Diagnostics.Trace and System.Diagnostics.Debug are thread-safe, so no need for lock(this).
+#if NETCF
+ System.Diagnostics.Debug.Flush();
+#else
+ System.Diagnostics.Trace.Flush();
+#endif
+ return true;
+ }
+ }
}
diff --git a/src/LogManager.cs b/src/LogManager.cs
index 7ec8d0c..0440cce 100644
--- a/src/LogManager.cs
+++ b/src/LogManager.cs
@@ -751,6 +751,25 @@
return LoggerManager.GetAllRepositories();
}
+ /// <summary>
+ /// Flushes logging events buffered in all configured appenders in the default repository.
+ /// </summary>
+ /// <param name="millisecondsTimeout">The maximum time in milliseconds to wait for logging events from asycnhronous appenders to be flushed,
+ /// or <see cref="Timeout.Infinite"/> to wait indefinitely.</param>
+ /// <returns><c>True</c> if all logging events were flushed successfully, else <c>false</c>.</returns>
+ public static bool Flush(int millisecondsTimeout)
+ {
+ Appender.IFlushable flushableRepository = LoggerManager.GetRepository(Assembly.GetCallingAssembly()) as Appender.IFlushable;
+ if (flushableRepository == null)
+ {
+ return false;
+ }
+ else
+ {
+ return flushableRepository.Flush(millisecondsTimeout);
+ }
+ }
+
#endregion Domain & Repository Manager Methods
#region Extension Handlers
diff --git a/src/Repository/LoggerRepositorySkeleton.cs b/src/Repository/LoggerRepositorySkeleton.cs
index d566331..a231d24 100644
--- a/src/Repository/LoggerRepositorySkeleton.cs
+++ b/src/Repository/LoggerRepositorySkeleton.cs
@@ -23,6 +23,7 @@
using log4net.Core;
using log4net.Util;
using log4net.Plugin;
+using System.Threading;
namespace log4net.Repository
{
@@ -40,7 +41,7 @@
/// </remarks>
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
- public abstract class LoggerRepositorySkeleton : ILoggerRepository
+ public abstract class LoggerRepositorySkeleton : ILoggerRepository, Appender.IFlushable
{
#region Member Variables
@@ -573,5 +574,59 @@
{
OnConfigurationChanged(e);
}
+
+ private static int GetWaitTime(DateTime startTimeUtc, int millisecondsTimeout)
+ {
+ if (millisecondsTimeout == Timeout.Infinite) return Timeout.Infinite;
+ if (millisecondsTimeout == 0) return 0;
+
+ int elapsedMilliseconds = (int)(DateTime.UtcNow - startTimeUtc).TotalMilliseconds;
+ int timeout = millisecondsTimeout - elapsedMilliseconds;
+ if (timeout < 0) timeout = 0;
+ return timeout;
+ }
+
+ /// <summary>
+ /// Flushes all configured Appenders that implement <see cref="log4net.Appender.IFlushable"/>.
+ /// </summary>
+ /// <param name="millisecondsTimeout">The maximum time in milliseconds to wait for logging events from asycnhronous appenders to be flushed,
+ /// or <see cref="Timeout.Infinite"/> to wait indefinitely.</param>
+ /// <returns><c>True</c> if all logging events were flushed successfully, else <c>false</c>.</returns>
+ public bool Flush(int millisecondsTimeout)
+ {
+ if (millisecondsTimeout < -1) throw new ArgumentOutOfRangeException("millisecondsTimeout", "Timeout must be -1 (Timeout.Infinite) or non-negative");
+
+ // Assume success until one of the appenders fails
+ bool result = true;
+
+ // Use DateTime.UtcNow rather than a System.Diagnostics.Stopwatch for compatibility with .NET 1.x
+ DateTime startTimeUtc = DateTime.UtcNow;
+
+ // Do buffering appenders first. These may be forwarding to other appenders
+ foreach(var appender in GetAppenders())
+ {
+ log4net.Appender.IFlushable flushable = appender as log4net.Appender.IFlushable;
+ if (flushable == null) continue;
+ if (appender is Appender.BufferingAppenderSkeleton)
+ {
+ int timeout = GetWaitTime(startTimeUtc, millisecondsTimeout);
+ if (!flushable.Flush(timeout)) result = false;
+ }
+ }
+
+ // Do non-buffering appenders.
+ foreach (var appender in GetAppenders())
+ {
+ log4net.Appender.IFlushable flushable = appender as log4net.Appender.IFlushable;
+ if (flushable == null) continue;
+ if (!(appender is Appender.BufferingAppenderSkeleton))
+ {
+ int timeout = GetWaitTime(startTimeUtc, millisecondsTimeout);
+ if (!flushable.Flush(timeout)) result = false;
+ }
+ }
+
+ return result;
+ }
}
}
diff --git a/src/log4net.vs2008.csproj b/src/log4net.vs2008.csproj
index dfcff1f..4998b0a 100644
--- a/src/log4net.vs2008.csproj
+++ b/src/log4net.vs2008.csproj
@@ -148,6 +148,9 @@
<Compile Include="Appender\IBulkAppender.cs">
<SubType>Code</SubType>
</Compile>
+ <Compile Include="Appender\IFlushable.cs">
+ <SubType>Code</SubType>
+ </Compile>
<Compile Include="Appender\LocalSyslogAppender.cs">
<SubType>Code</SubType>
</Compile>
diff --git a/src/log4net.vs2010.csproj b/src/log4net.vs2010.csproj
index 18430a5..cd42bb4 100644
--- a/src/log4net.vs2010.csproj
+++ b/src/log4net.vs2010.csproj
@@ -166,6 +166,9 @@
<Compile Include="Appender\IBulkAppender.cs">
<SubType>Code</SubType>
</Compile>
+ <Compile Include="Appender\IFlushable.cs">
+ <SubType>Code</SubType>
+ </Compile>
<Compile Include="Appender\LocalSyslogAppender.cs">
<SubType>Code</SubType>
</Compile>
diff --git a/src/log4net.vs2012.csproj b/src/log4net.vs2012.csproj
index 070534d..de32793 100644
--- a/src/log4net.vs2012.csproj
+++ b/src/log4net.vs2012.csproj
@@ -166,6 +166,9 @@
<Compile Include="Appender\IBulkAppender.cs">
<SubType>Code</SubType>
</Compile>
+ <Compile Include="Appender\IFlushable.cs">
+ <SubType>Code</SubType>
+ </Compile>
<Compile Include="Appender\LocalSyslogAppender.cs">
<SubType>Code</SubType>
</Compile>