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>