Using NLog to provide custom tracing for your ASP.NET Web API - StrathWeb

Strath

Using NLog to provide custom tracing for your ASP.NET Web API

Cause everything in Web API is pluggable

One of the things I love most about MVC3, and now ASP.NET Web API, is that pretty much any functionality or service that is used by your application, can be replaced with a custom one. One of these is the entire tracing mechanism that Web API uses.

Let’s have a look today at how you can build a simple System.Web.Http.Tracing.ITraceWriter implementation to provide support for a custom logging framework in your Web API – in our case it will be NLog.

More after the jump.

What is NLog?

Well, if you don’t know your are probably on a wrong blog :) – but in short, NLog is a free open source logging framework for .NET (official site here), started by Microsoft’s Jarek Kowalski. It is extremely powerful, and very easy to setup. And it also happens to be my favorite one, that’s why I am going to use it here today.

With NLog you can easily write debug info to:
– files
– event log
– database
– network
– email
– command line console
– and many other providers

Why do we even need this and how to start?

A good question is why would you even need to do something? Wouldn’t using System.Diagnostics.Trace in your code enough?

Well, depends on how you look at it. In simple cases probably yes, but with plugging your own logging framework to ASP.NET Web API you get 2 major advantages:
– you can leverage on all the functionalities offered by NLog, like the above mentioned logging providers and easily log to different targets
– you channel all the internal ASP.NET Web API tracing to your provider as well. This means you can easily keep track of the traces that were put in by MSFT team and use those to debug/optimize your applications.

In order to plug your own trace provider to ASP.NET Web API you need a class that derives from System.Web.Http.Tracing.ITraceWriter.

In Web API RC the interface requires us to implement an additional method IsEnabled(). This is going to be obsolete in full release, as this method has been removed from the interface on May 15 in this commit. Therefore, since this code is written against RC, we will explicitly implement it but not use it for anything.

Our sample implementation

Let’s build a sample implementation. You can use any dummy Web API project for that – I just happen to have a redundant EF Code first/Ninject project, but that doesn’t really matter.

We need to start off by grabbing nLog from Nuget.

Next, we need to configure nLog in web.config. In the “configSections” add:

And directly under “configuration”, add:

In this example we will use logging to a file and logging to event viewer, but obviously you are free to leverage on any of the powerful features of nLog. Note that I added ${basedir} in the file path – that is required to tell nLog that the file needs to be created under the web application’s root folder; otherwise it wouldn’t know where to put the file and no log file would be created.

We set the minimum log level to “Trace” – so that we are as verbose as possible, and log everything.

Now, we need a class implementing ITraceWriter:

So, whenever the Trace() method would be invoked, we check if logging is on, build an instance of System.Web.Http.Tracing.TraceRecord, invoke a delegate (if passed) and call our private Log method, where we will introduce nLog logging.

The above mentioned delagates are used by internal Web API tracers from the System.Web.Http.Tracing.Tracers namespace. In our example we will use shorthand extensions from the static System.Web.Tracing.ITraceWriterExtensions, so we won’t explicitly pass any delegates to our ITraceWriter.

Notice that I also introduced a private Dictionary which will serve us as mapping between Web API’s System.Web.Http.Tracing.TraceLevel enum and nLog logging level methods.

Let’s look at the Log method:

This is a very simple method; depending on whether the “Operator” and “Exception” properties are set the message is more or less detailed. Then we invoke the appropriate nLog method to log the information.

Final step is to add our implmentation of ITraceWriter to GlobalConfigurarion. A side note – if you are using dependency injection, i.e. Ninject, and want to have logging available already there, you need to add the line below just at application start – and since DI frameworks usually use web activator, it would have to be somehwere under App_start folder.

Adding logging to our application

Now let’s add logging to several places in our application.

We can add some elsewhere, i.e. in Ninject resolver and Global.asax:

A note here – since the ITraceWriter requires an instance of HttpRequestMessage to be explicitly passed to the Trace method, we cheat a little, and pass an empty one in the places where it is not available (i.e. Global.asax).

Now let’s run the application, play around a little bit and see the log messages piling up. Since we set a very verbose trace level, you can see lots of helpful information coming from the internal Web API tracers.

The event viewer is also logging everything as it should:

One last thing worth mentioning, is that you can go even a step further, and instead of implementing a custom ITraceWriter you might implement an entirely own System.Web.Http.Tracing.ITraceManager. You’d then get the maximum control over anything that Web API does; however you’d have to effectively replace or rewrite everything that’s located under System.Web.Http.Tracing.Tracers – so all Web API’s internal tracers.

Summary

As most of the Web API stuff, plugging in your custom logging framework is very easy and smooth. With that in hand, you get very granular control over your tracing and debugging mechanisms and can leverage on the powerful features of your favorite logging framework – like I used nLog in this case.

Till next time!

Be Sociable, Share!

  • Pingback: Dew Drop – June 26, 2012 (#1,351) | Alvin Ashcraft's Morning Dew

  • Pingback: How to ASP.NET Web API Logging and Tracing all what you want « cazysblog

  • May

    I get an error at this line:
    loggingMap[record.Level](message);

    • Filip W

      Can you post the error?

  • Andrei

    I get the same error:

    Cannot apply indexing with [] to an expression of type ‘System.Lazy<System.Collections.Generic.Dictionary<System.Web.Http.Tracing.TraceLevel,System.Action>>’

    at the line:

    loggingMap[record.Level](message);

  • Chandra

    I get the same error.

  • Filip W

    Sorry, you are right. There was a mistake in the code, instead of referencing Lazy, we should be using the private property based on it:

    change
    loggingMap[record.Level](message);
    to
    _logger[record.Level](message);

    I updated the code samples as well.

  • Andrei

    Thank you. Should have guessed ;-)

  • http://www.cametoofar.com Abhilash

    Thanks! Saved my time. :)

  • http://twitter.com/petrsnobelt Petr Snobelt
  • http://twitter.com/omegaluz Andy Cohen

    This is awesome! Thank you!

  • Joe Sheble

    How would this be used via a selfhosted WebAPI since the GlobalConfiguration isn;t used. Replacing the service is fine, however in the Controller itself, getting the instance of the logger is only possible if I pass it as a parameter to each and every constructor, which I’m not overly crazy about.

    • http://www.strathweb.com/ Filip W

      There is a Configuration property on the base controller (ApiController)

      • Joe Sheble

        In a self hosted environment, the following is not possible:

        GlobalConfiguration.Configuration.Services.GetTraceWriter()

        Self hosted configurations are done through HttpSelfHostConfiguration

        • http://www.strathweb.com/ Filip W

          Yes that’s what I’m saying, that you can use the Configuration property off the base ApiController class to get the configuration object.

          base.Configuration.Services.GetTraceWriter();

          you can also get configuration anywhere where you have a flowing HttpRequestMessage:

          request.GetConfiguration()

          • Joe Sheble

            Ahhh, I misunderstood. Thanx.

  • Khai

    How can I set the level == TraceLevel.Off in the web.config