Logging with Serilog, LibLog, and Seq
3 min read

Logging with Serilog, LibLog, and Seq

I’ve written and rewritten logging providers about 300 times in my career. It’s something I know how to do, I know how to extend, and I know how to hate it. Every time I write it, I hope for some great abstraction layer that will allow me to reuse this library across every future application. I inevitably fail at this.

Along comes The Solution To All My Problems.

Some History

A little backstory on my personal opinions: I have just about no problem having a hard dependency on a logging framework if that logging framework is really freaking good. Some people don’t agree with that opinion, and that’s okay.

To that end, the first iteration of this blog post was “Logging with Serilog and Seq,” mainly because I love Serilog that much. To me, it is the perfect logging framework, as it accomplishes logging the way my brain works. Additionally, having it send to Seq is a no-brainer. Being able to deconstruct the structure logs in a normal, developer-centric way is just too good to pass up.

However, every once in awhile, a product comes along that completely shakes up How You Do Things.

Player 3 Has Joined The Game

Just when I thought I would never change the way I log anything in any of my applications, along comes LibLog. LibLog is a deceptively simple logging abstraction that uses some sneaky reflection in order to transparently support specific logging libraries (such as Serilog, for example).

The stated goal for LibLog is to provide transparent logging support for several loggers, specifically and primarily for library developers. In a simple example, let’s assume there exists a library that everyone uses (Newtonsoft.Json, aka JSON.Net, would be a good example). If JSON.Net would include LibLog in its library, then utilize the logging interfaces provided by LibLog, any application that consumes JSON.Net that has a logging framework like Serilog implemented would immediately get JSON.Net’s logging as well for free.

This is a very powerful feature, and it allows library developers to not have to think about what type of logging frameworks the consuming applications will use.

How I Use LibLog

Unfortunately, it’s not realistic for the work that I do to create libraries for other applications to consume. It’s just not what I typically work on. I tend to work on applications themselves. That doesn’t mean I don’t get to use LibLog! In fact, rather than calling Serilog directly, I just setup Serilog, and then utilize LibLog’s ILog interface everywhere. This allows me to change logging frameworks in the future (however unrealistic that may be). Some code samples below.

Logging Setup

namespace LoggingTest
{
    using Serilog;
    using Serilog.Core;
    using Serilog.Events;
    using ServiceStack.Configuration;

    public class Global : HttpApplication
    {
        private readonly IAppSettings settings = new AppSettings();

        void Application_Start(object sender, EventArgs e)
        {
            this.SetupLogging();
        }

        private void SetupLogging()
        {
            var level = new LoggingLevelSwitch(this.settings.Get("SeqLevel", LogEventLevel.Information));
            Log.Logger = new LoggingConfiguration()
                .WriteTo.Seq(this.settings.Get("SeqUrl", "http://localhost:5341"))
                .Enrich.WithProperty("Application", this.settings.Get("ApplicationName", "LoggingTest"))
                .Enrich.WithProperty("ApplicationVersion", this.settings.Get("ApplicationVersion", "1.0.0"))
                .MinimumLevel.ControlledBy(level)
                .CreateLogger();
        }
    }
}

Home Controller

namespace LoggingTest
{
    using LoggingTest.Logging;

    public class HomeController : Controller
    {
        private readonly ILog log = LogProvider.GetCurrentClassLogger();

        public ActionResult Home()
        {
            this.log.InfoFormat("{MethodName} Entry", nameof(Home));
            var stopwatch = Stopwatch.StartNew();
            // do lots of work
            stopwatch.Stop();
            this.log.InfoFormat("{MethodName} Exit | {ElapsedMilliseconds} ms", nameof(Home), stopwatch.ElapsedMilliseconds);
            return this.View();
        }
    }
}

The code samples above contain a Global class that sets up Serilog, utilizing many of the built-in enrichers to include more information in every message. Note that I also utilize ServiceStack’s wonderful IAppSettings application setting abstraction to do a lot of the legwork of retrieving and converting data to the correct type.

Later down the line, we have an ASP.NET MVC controller. Within this controller, we instantiate LibLog’s ILog interface with the LogProvider.GetCurrentClassLogger() static method. This method adds a Context property to the message, giving the current class.

If you’re used to using Serilog, you’ll notice that we pass in a templated string to the log and then adding parameters using the ILog.InfoFormat method. This transparently passes the structured message and its parameters to Serilog’s Log.Info method (I’ve simplified this greatly).

Wrapping It Up

I already utilized Serilog and Seq in every new application I build, both personal and professional. With the introduction of LibLog, I am able to decouple my logging provider with a simple logging interface. Additionally, in the unlikely case that I become some great library developer, I’ll be utilizing a tool that any of my future applications will transparently support. All in all, LibLog is seriously the best.