How To Programmatically Flush log4net Message Buffer

If you have set a buffer size on one or more of your log4net appenders, the logger will buffer the messages before dumping them, for instance, into a file or a database. That optimizes the performance but what if you need to flush messages right away? I’ll give you an example: let’s say your application logs a message by calling a Web API which, in turn, uses log4net to log the message to a text file and a database. In a hypothetical scenario, suppose you want logging to the file buffered, since you analyze it infrequently, but want the buffer flushed to the database right away because you have a front-facing alert website which gets notified by the API of any errors in real-time. So, how can this be achieved? – it all comes down to looking at appenders: you’ll need to get those and then flush each individually (if desired). You can do this in two ways: get appenders for all loggers you have configured or for a particular logger. Let’s examine them:

Flush buffer for appenders across all configured loggers:
This can be achieved in one statement, gotta love LINQ!

using System.Threading.Tasks;
using log4net;
using log4net.Appender;
...
Parallel.ForEach(
	LogManager.GetRepository()
		.GetAppenders()
		.Where(appender => appender is BufferingAppenderSkeleton)
		.Cast<BufferingAppenderSkeleton>(), appender => appender.Flush());

To elaborate on the statement above, here’s what’s happening here: first, we get the log4net repository and consequently the the appenders (LogManager.GetRepository().GetAppenders()). After that, we narrow down to only appenders that support buffering (.Where(appender => appender is BufferingAppenderSkeleton)). Since that returns an enumerable of the appender interface, IEnumerable<IAppender>, we want to cast it first so that we can call Flush on the object itself. All of that gets fed to Parallel.ForEach() – this way we get the most of hardware and can reduce flushing times significantly in high volume logging environments. Just keep in mind all the stuff that comes with parallel processing. If you are more comfortable with not using it, you can always get rid of Parallel.ForEach() and replace with .ForEach(appender => appender.Flush()) after the Cast call.

Flush buffer for appenders for a specific logger:
Here is an example of flushing the buffer for a specific logger. Again, LINQ gives us a single statement:

using log4net;
using log4net.Appender;
using log4net.Repository.Hierarchy;
...
((LogManager.GetLogger("my-logger")?.Logger as Logger)?.Appenders.Cast())?
	.OfType().ToList().ForEach(appender => appender.Flush());

Again, we are employing the same thing (although I am giving an example of synchronous ForEach() call here), the only difference here is that we pull the appenders from the logger itself instead of the repository which has all of them.

Now, one more point, going back to the example of logging to a text file and a database. The above code will flush all appenders that support buffering. What if you wanted to keep buffering the text file one but flush the database one? If you haven’t figured it out already, this can’t be easier than modifying above code to the likes of or similar:

.Where(appender => appender is BufferingAppenderSkeleton && appender.Name == "my-db-appender-name")
// or
OfType().Where(appender => appender.Name == "my-db-appender-name").ToList()

As I was about to post this, I thought of something, so enjoy this class (add it to your extension method library so you can share between solutions):

public static class Log4NetExtensions
{

	public static void FlushAll(bool async = true)
	{
		if (async)
		{
			Parallel.ForEach(
				LogManager.GetRepository()
					.GetAppenders()
					.Where(appender => appender is BufferingAppenderSkeleton)
					.Cast(), appender => appender.Flush());
			return;
		}
		LogManager.GetRepository()
			.GetAppenders()
			.Where(appender => appender is BufferingAppenderSkeleton)
			.Cast()
			.ToList()
			.ForEach(appender => appender.Flush());
	}

	public static void Flush(this ILog log, bool async = true)
	{
		var appenders = (log.Logger as Logger)?.Appenders.Cast().OfType();
		if (appenders == null) { return; }
		if (async)
		{
			Parallel.ForEach(
				appenders,
				appender => appender.Flush()
				);
			return;
		}
		appenders.ToList().ForEach(appender => appender.Flush());
	}

}

As always, hope this helped someone! If you have questions or need help, just post a comment below!

This entry was posted in C# and tagged , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *