Ads by Lake Quincy Media
Gibraltar - Learn about the best analysis tool for NLog

Archive for May, 2010

NLog nightly builds are now available

Nightly builds of NLog are now available on CodePlex. They are generated every night around 1AM PST which is 10AM in most of Europe. Builds are generated using CruiseControl.NET which is pretty awesome piece of software – except the fact that they don’t use NLog yet :) .

After each build, binaries are automatically pushed to CodePlex servers using CodePlex Web Services API. Once the release is uploaded, a direct link to it is also placed on the Download page. I still haven’t figured out how to delete older releases automatically because CodePlex API does not support this, so I’ll be doing that manually for now.

You can subscribe to the RSS feed at http://feeds.feedburner.com/nlogreleases to be notified about new builds.

Ads by Lake Quincy Media

Asynchronous Makeover – NLog Edition

I just checked in code to enable asynchronous execution of wrappers in NLog. This is the last big architectural change before bug-fixing and stabilization for 2.0 release.

New features

  • Exceptions thrown from wrappers such as AsyncTargetWrapper, BufferingTargetWrapper, AspNetBufferingTargetWrapper are now handled as opposed to being swallowed.
  • LogReceiverWebServiceTarget now handles asynchronous exceptions.
  • Exceptions from batch-aware targets are now handled for each item separately. It means that if you write 100 messages to a BufferedWrapper which wraps File target and one of those file writes fails, you will get 99 successes and 1 failure as opposed to one massive failure which no wrapper can reasonably handle. The same thing applies to email, etc.

There has been a small change to the design described in the last post. Asynchronous continuations are represented as delegates instead of interface. It makes makes it much easier to use ad-hoc continuations in code without sacrificing composability.

Downloads

The source code is available in GitHub repository. I’ve published a private build from the master branch: http://nlog-project.org/public/asyncpreview1/.

I would appreciate people giving this build a spin and reporting any anomalies. Given the scope of changes and lack of unit tests in some areas I expect many things to be broken, so any usage in production is highly discouraged.

NLog project metrics on Ohloh.net

Today I came by Ohloh.net which I found to be very interesting. It provides detailed statistics about open source projects. NLog was already registered by some good soul, but was pointing at the old source repository on sourceforge.net, so I updated it to use Github repo and recomputed the stats.

The statistics are generally pretty interesting, and seem pretty accurate – but there are also some entertaining ones, like the estimation of the cost it would take to recreate such a project from scratch (obviously in corporate environment):

image

There are many more interesting statistics, so go and check them out.

I’ve also put I USE IT button on top of this page. You can add NLog to your tool stack on Ohloh by clicking on it, which will count in project statistics.

Asynchronous logging in NLog 2.0

NLog 1.0 supports asynchronous logging, but there is no good support for asynchronous exception handling. This is because wrappers targets are not capable of receiving exceptions which are raised on other threads.

Since NLog 2.0 is going to support Silverlight where entire networking stack is completely asynchronous, it is critical to enable wrappers for those scenarios. Without it some important wrapper-based features, such as load balancing or failover would not work properly.

This post will present new APIs to support asynchronous logging features that are coming in the next release of NLog.

Exception Handling in NLog 1.0

NLog 1.0 uses very simple, synchronous exception handling pattern:

try
{
    // do something
}
catch (Exception ex)
{
    // handle exception
}

The problem arises if the code block inside try { } clause performs an asynchronous operation such as network call which may result in an exception, as in the following example:

try
{
    WebClient client = new WebClient();
    client.DownloadStringCompleted += (sender, e) =>
    {
        // this event will be raised asynchronously
        // on another thread, long after try/catch block completes

        // any exceptions raised here will not be handled by the catch {} block below
    }

    client.DownloadStringAsync(new Uri("http://example.com"));
}
catch (Exception ex)
{
    // handle exception
}

NLog cannot handle exceptions in such cases, since the original stack frame is gone, so it just swallows exceptions raised asynchronously and logs them to the internal log. Not catching exceptions on background threads would be fatal and might result in application termination.

You can probably see why swallowing exceptions prevents wrappers, such as RetryingWrapper from working. If you write declare the following wrappers in your configuration file, the outermost wrapper will never implement any retry logic, since AsyncWrapper will never pass any asynchronous exceptions to RetryingWrapper.

<target type="RetryingWrapper" ...>
   <target type="AsyncWrapper" ...>
      <target type="File" ...>
   </target>
</target>

Asynchronous Exception Handling in NLog 2.0

In order to implement proper asynchronous exception handling we need to let asynchronous methods know what to do in case of success and failure. This is typically done through continuation functions. There are many ways to represent continuation information, I’ve decided to represent it as an interface with two methods:

public interface IAsyncContinuation
{
    void OnSuccess();
    void OnException(Exception exception);
}

The Target.Write() API will be refactored to look like this:

public void WriteLogEvent(LogEventInfo logEvent, IAsyncContinuation asyncContinuation)
{
    try
    {
        this.Write(logEvent, asyncContinuation);
    }
    catch (Exception ex)
    {
        asyncContinuation.OnException(ex);
    }
}

protected virtual void Write(LogEventInfo logEvent, IAsyncContinuation asyncContinuation)
{
    try
    {
        this.Write(logEvent);
    }
    catch (Exception ex)
    {
        asyncContinuation.OnException(ex);
        return;
    }

    asyncContinuation.OnSuccess();
}

protected abstract void Write(LogEventInfo logEvent);

As you can see, by default the asynchronous code gets forwarded to the synchronous Write method. This lets us keep the existing extensibility interface for targets. If you want to implement asynchronous target, you need to override both synchronous and asynchronous write methods:

public class MyAsyncTarget : TargetWithLayout
{
    [RequiredParameter]
    public Uri TargetUri { get; set; }

    protected override void Write(LogEventInfo logEvent)
    {
        throw new NotSupportedException("Synchronous write operation is not supported.");
    }

    protected override void Write(LogEventInfo logEvent, IAsyncContinuation asyncContinuation)
    {
        var wc = new WebClient();
        wc.UploadDataCompleted += (sender, e) =>
            {
                wc.Dispose();
                if (e.Error != null)
                {
                    asyncContinuation.OnException(e.Error);
                    return;
                }

                asyncContinuation.OnSuccess();
            };

        byte[] data = Encoding.UTF8.GetBytes(this.Layout.GetFormattedMessage(logEvent));
        wc.UploadDataAsync(this.TargetUri, data);
    }
}

Target.Flush() method will be changed in a similar way, except it will be asynchronous only:

public void Flush(IAsyncContinuation asyncContinuation)
{
    try
    {
        this.FlushAsync(asyncContinuation);
    }
    catch (Exception ex)
    {
        asyncContinuation.OnException(ex);
    }
}

protected virtual void FlushAsync(IAsyncContinuation asyncContinuation)
{
    asyncContinuation.OnSuccess();
}

LogManager and LogFactory will also be enhanced with asynchronous Flush() methods. Their synchronous overloads will not be available in Silverlight, since there is no way to wait on a potential network call without causing a deadlock:

public class LogFactory
{
#if !SILVERLIGHT
  void Flush();
  void Flush(TimeSpan timeout);
  void Flush(int timeoutMilliseconds);
#endif

  void Flush(IAsyncContinuation asyncContinuation);
  void Flush(IAsyncContinuation asyncContinuation, TimeSpan timeout);
  void Flush(IAsyncContinuation asyncContinuation, int timeoutMilliseconds);
}

Working with continuations

NLog 2.0 will provide default implementation of continuations creatable through AsyncHelpers.MakeContinuation() factory method:

IAsyncContinuation continuation = AsyncHelpers.MakeContinuation(
    () => { /* code to execute on success */ }
    ex => { /* code to execute on failure */ });

In addition to this I am planning to expose helpers which will make working with and composing continuations easier:

public delegate void AsynchronousAction(IAsyncContinuation asyncContinuation);
public delegate void AsynchronousAction<T>(IAsyncContinuation asyncContinuation, T argument);

public static class AsyncHelpers
{
  public static void RunSequentially<T>(IEnumerable<T> values, IAsyncContinuation asyncContinuation, AsynchronousAction<T> callback);
  public static void RunInParallel<T>(IEnumerable<T> values, IAsyncContinuation asyncContinuation, AsynchronousAction<T> action);
  public static void Repeat(int repeatCount, IAsyncContinuation asyncContinuation, AsynchronousAction action);
  public static IAsyncContinuation FollowedBy(this IAsyncContinuation asyncContinuation, AsynchronousAction action);
  public static IAsyncContinuation WithTimeout(this IAsyncContinuation asyncContinuation, TimeSpan timeout);
  public static void RunSynchronously(AsynchronousAction action);
}

Impact on wrappers

Because of the way the API is designed, the impact on existing targets should be very limited. Unfortunately this does not apply to wrappers, which have to be completely rewritten to be fully asynchronous. Asynchronous code tends to be larger and more difficult to read and follow, as demonstrated in the following example:

For example, the code for retrying wrapper in NLog 1.0 looked like this:

protected internal override void Write(LogEventInfo logEvent)
{
    for (int i = 0; i < RetryCount; ++i)
    {
        try
        {
            if (i > 0)
                InternalLogger.Warn("Retry #{0}", i);
            WrappedTarget.Write(logEvent);
            // success, return
            return;
        }
        catch (Exception ex)
        {
            InternalLogger.Warn("Error while writing to '{0}': {1}", WrappedTarget, ex);
            if (i == RetryCount - 1)
                throw ex;
            System.Threading.Thread.Sleep(RetryDelayMilliseconds);
        }
    }
}

The code for the same operation in NLog 2.0 is much more complex:

protected override void Write(LogEventInfo logEvent, IAsyncContinuation asyncContinuation)
{
    FailureAction failure = null;
    int counter = 0;

    failure = ex =>
        {
            InternalLogger.Warn("Error while writing to '{0}': {1}", this.WrappedTarget, ex);
            int retryNumber = Interlocked.Increment(ref counter);

            // exceeded retry count
            if (retryNumber == this.RetryCount)
            {
                asyncContinuation.OnException(ex);
                return;
            }

            // sleep and try again
            Thread.Sleep(this.RetryDelayMilliseconds);
            InternalLogger.Warn("Retry #{0}", retryNumber);

            this.WrappedTarget.WriteLogEvent(logEvent, AsyncHelpers.MakeContinuation(asyncContinuation.OnSuccess, failure));
        };

    this.WrappedTarget.WriteLogEvent(logEvent, AsyncHelpers.MakeContinuation(asyncContinuation.OnSuccess, failure));
}

Summary

Asynchronous processing is a very difficult matter, and it is very difficult to write correct and robust asynchronous code. I am hoping that proposed APIs and abstraction level are the right ones and will not make the source code too difficult to read and maintain.

Any comments or suggestions are welcome.

NLog 2.0 installer is available for testing

I’ve spent last couple nights working on MSI installer for NLog 2.0 and I have first version of MSI packages ready for testing. They should be reasonably usable – the code passes all unit tests but did not get much integration testing yet.

There are 5 installer packages:

Each package comes with Visual Studio integration, which supports VS2010, VS2008 and VS2005 (completely untested) and should have the same functionality as in NLog 1.0:

  • Code snippets for C# and VB (just type ‘nlogger’ and it will include logger declaration)
  • Item templates for empty, console and typical log file
  • XSD for intellisense

Note that unlike in NLog 1.0, the installer installs those VS items for all users on the machine, not for the user who ran the installer.

Please also note that debug symbols (pdb/mdb), binaries and documentation for some older frameworks, such as Silverlight 2.0 and .NET Framework 2.0 are excluded by default – use custom or full installation to enable them.

Please give the installer a try on as many platform configurations as you can and report success/failure along with your configuration information as comments to this post. I’m particularly interested in testing the following dimensions:

  • various combinations of Visual Studio SKUs installed on the same machine (with or without add-on packages such as Silverlight SDK, Resharper, etc.)
  • different versions of Windows
  • x86 and x64 CPUs
  • non-English versions of Windows
  • non-English versions of Visual Studio

Thanks in advance for all your help.