31

By default, log4net is a synchronous logging mechanism, and I was wondering if there was a way to have asynchronous logging with log4net?

pcdev
  • 2,852
  • 2
  • 23
  • 39
Jonathan Beerhalter
  • 7,229
  • 16
  • 68
  • 78

6 Answers6

14

If you go to the log4net website, you can find some examples, at least one of which is an asynchronous Appender.

http://logging.apache.org/log4net/release/example-apps.html

Note that I have not used any of these examples, so I cannot vouch for them one way or the other.

Here is a link to the actual asynchronous appender from the log4net Examples area in their code repository:

http://svn.apache.org/viewvc/logging/log4net/trunk/examples/net/2.0/Appenders/SampleAppendersApp/cs/src/Appender/AsyncAppender.cs?view=markup

I looked at it briefly, and it apparently acts as a wrapper around one or more "conventional" Appenders. On each logging request (containing one or more LoggingEvent objects), a ThreadPool thread is used to forward the LoggingEvents to the list of wrapped Appenders.

wageoghe
  • 27,390
  • 13
  • 88
  • 116
  • 1
    In the end, this is pretty much what I ended up doing. I switched it so my logging is on it's own thread, rather than running on the thread pool. – Jonathan Beerhalter Aug 12 '11 at 19:42
14

Just wanted to provide my complete solution for reference. Couple of important items, the FixFlags let you capture the thread that's actually doing the logging. The Blocking Collection is in the ReactiveExtensions. The jist here is that your forwarding appender handles the Asynchronous stuff and then just forwards on the LoggingEvent to a standard Log4Net appender, which lets Log4Net do all of the things that it's good at. No re-inventing the wheel.

/// <summary>
/// Provides an extension for the log4net libraries to provide ansynchronous logging capabilities to the log4net architecture
/// </summary>
public class AsyncLogFileAppender : log4net.Appender.ForwardingAppender
{
    private static int _asyncLogFileAppenderCount = 0;
    private readonly Thread _loggingThread;
    private readonly BlockingCollection<log4net.Core.LoggingEvent> _logEvents = new BlockingCollection<log4net.Core.LoggingEvent>();

    protected override void Append(log4net.Core.LoggingEvent loggingEvent)
    {
        loggingEvent.Fix = FixFlags.ThreadName;
        _logEvents.Add(loggingEvent);
    }

    public AsyncLogFileAppender()
    {

        _loggingThread = new Thread(LogThreadMethod) { IsBackground = true, Name = "AsyncLogFileAppender-" + Interlocked.Increment(ref _asyncLogFileAppenderCount), };
        _loggingThread.Start();
    }

    private void LogThreadMethod()
    {
        while (true)
        {
            LoggingEvent le = _logEvents.Take();
            foreach (var appender in Appenders)
            {
                appender.DoAppend(le);
            }
        }
    }
}

Then, in your log4net.xml you setup the appenders thusly

<!-- Standard form output target location and form -->
<appender name="StandardAppender" type="TSUIC.Logging.AsyncLogFileAppender">
<appender-ref ref="StandardAppenderSync" />
</appender>

<appender name="StandardAppenderSync" type="log4net.Appender.RollingFileAppender">
    <!-- The standard pattern layout to use -->
    <file value="log\Log_" />
    <appendToFile value="true" />
    <rollingStyle value="Date" />
    <maxSizeRollBackups value="-1" />
    <maximumFileSize value="5GB" />
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
    <staticLogFileName value="false" />
    <datePattern value="yyyyMMdd'.txt'" />
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
    </layout>
</appender>

Update:

If you want to use context in log4net like "log4net.ThreadContext.Properties["CustomColumn"]"

Then you need to update above code like

loggingEvent.Fix = FixFlags.All;
Rikin Patel
  • 8,848
  • 7
  • 70
  • 78
Jonathan Beerhalter
  • 7,229
  • 16
  • 68
  • 78
  • How does this work in a production environment? Is there any noticable performance improvement? Obviously the improvement would depend on the number logging statements. I'm wondering if there is a reason to do this other than benchmarks in a dev or test environment.. thanks in advance – tap Apr 27 '13 at 18:56
  • How does that handle LoggingEvents that are queued up when an application exits? – Allan Elder Sep 23 '13 at 17:40
  • 1
    Instead of running the thread in the background, you could register a callback with the repository ShutdownEvent. Then in the event you would simply call BlockingCollection.CompleteAdding(). To consume in this manner, don't do a while(true), instead use foreach(var le=_LogEvents.GetConsumingEnumerable()) Then it automatically exits the tread when the collection has been emptied. – Michael Erickson Jul 10 '14 at 17:30
  • I am adding some other fields to log in to the log file by using log4net.ThreadContext.Properties["CustomColumn"] = "value"; but this is being printed as null if i use the above code – ace Mar 21 '16 at 11:08
  • The code above does not work. If you try to debug it s not getting inside the for loop. – pantonis Nov 17 '17 at 12:09
9

This is how I do it:

Task.Factory.StartNew(() => log.Info("My Info"));

That way log4net performs logging on a separate thread, asynchronously...

BTW, Task class is in System.Threading.Tasks namespace.

Dean Kuga
  • 11,878
  • 8
  • 54
  • 108
8

Some of the ideas here are incorrect and result in invalid/stale data, out of order logging or very bad performance. For instance the accepted answer suggests using the log4net AsyncAppender which uses the ThreadPool resulting in out of order entries which might not be a problem for some but I certainly want my log events to be one after another it also can have terrible performance and put too much strain on the ThreadPool also it does not batch the log entries. The answer suggested by Jonathan is certainly a much better solution but still lacks optimal performance.

A good example of how this should be implemented can be found HERE and the benchmarking results and the explanation HERE.

Another good feature of this solution is that it has been implemented as a Forwarder not an Appender allowing the user to include more than one Appender and log to each of them at the same time.

MaYaN
  • 6,683
  • 12
  • 57
  • 109
5

I came across this problem this week however I did not want to keep firing requests off to the thread pool because it could end up starving the rest of the application of threads so I came up with an Asyncronous appender that runs a dedicated thread for appending which is fed through a buffer. Check it out here: https://github.com/cjbhaines/Log4Net.Async

Chris Haines
  • 628
  • 1
  • 6
  • 14
  • Thanks Chris, this is exactly what I was looking for. Can you please indicate if this is production ready code? Have you used it in production under sufficient load to know its battle hardened? – afif May 01 '13 at 02:13
  • 1
    Hi afif. This appender is used by every single product in my company. The application I work on has a massive amount of throughput running anywhere between 300-1500 worker threads. N.b, I am taking no responsibility for the code. If you want to test its safety you can write some extra unit tests with an invalid connection string, bad config, etc so it throws exceptions on the background task. The background task has a continuation to log any errors which will stop any unobserved exceptions throwing in the finalizer when the thread is cleaned up. – Chris Haines May 08 '13 at 18:22
  • 1
    @Chris, I just ran the benchmarks for this appender, both the `Log4Net.Async.AsyncForwardingAppender` and `Log4Net.Async.ParallelForwardingAppender` and they have terrible performance, please look at the my answer below. You can run the benchmarks yourself just add your own NuGet package to the project. – MaYaN May 30 '16 at 21:50
0

https://github.com/cjbhaines/Log4Net.Async

We have asynchronous log4net methods available now. For the people who are looking for updated answers.

https://www.nuget.org/packages/Log4Net.Async/

Sanjeevi Subramani
  • 501
  • 1
  • 5
  • 16