Things I'm likely to forget.

Why use Serilog over NLog

For the longest time I didn't understand why everyone was so excited about Serilog. I've used NLog for a long time and it seemed more than capable of doing what I needed: logging messages to some data store (log files, databases, etc...).

Then I stared using Elasticsearch. Suddenly I saw the light. Structured event data always struck me as one of those neat features that wasn't really needed. However, once you start using something like Elasticsearch the power of structured event data quickly become evident.

Adding the visualizations offered by Kibana takes your logging to the next level. A quick glance at a dashboard and you instantly know if there has been an uptick in errors (or whatever you might be logging). The interactive visualizations allow you to quickly filter out noise and identify the root cause of problems you or your users might be experiencing.

Installing the JDK

This part is frequently overlooked. You need to have Java installed and running on the box you're planning to use as your Elasticsearch server. For the purposes of this tutorial I'm going to assume that you're going to set things up on a Windows machine. You'll also need to ensure that the JAVA_HOME environment variable is correctly set.

Step 1: Download the JDK

You can download the current JDK from Oracle. You'll want to click on the "Java Platform (JDK) 8u111 / 8u112" link. On the following page download the appropriate package (in my case it's jdk-8u111-windows-x64.exe). Once the download completes run the installer and let it do it's thing. In most cases the default options (install location, etc...) are just fine.

Step 2: Setting the JAVA_HOME Environment Variable

In order for Elasticsearch to work you'll need to have the JAVA_HOME variable set. In order to set the JAVA_HOME variable you'll need to access the Advanced System Settings in Windows. You can do that by:

Click on the Start Menu

Right click on Computer

Select Properties

In the window that appears, select Advanced System Settings in the upper left. That will bring up a small dialog window with five tabs across the top:

Select Advanced.

Click on Environment Variables...

In the System variables section click New...

For the Variable Name use JAVA_HOME and for the value use the location of your install's bin directory

Setting up Elasticsearch and Kibana

I'm not going to teach you the ins and outs of Elasticsearch. I'm going to give you just enough information to get things up and running. Elasticsearch is incredibly powerful and I strongly encourage you to get a book or consult with someone more knowledgeable than me.

Step 1: Download and Install Elasticsearch

Elastic does a great job walking you through the steps necessary to setup Elasticsearch on any platform. Rather than repeating that information here, I'll simply point you in the right direction: https://www.elastic.co/downloads/elasticsearch.

Although the download page has some basic installation instructions, I found the instruction in the documentation to be much more helpful. You can find that information here.

I would strongly recommend setting up Elasticsearch as a service. However, you'll want to make sure you have Elasticsearch successfully running before setting up the service. It's much easier to resolve problems when you can see the errors in the console window.

Step 2: Download and Install Kibana

Installing Kibana goes pretty much exactly like installing Elasticsearch. Simply download the compressed file, decompress to wherever you like, then run the bat file. You can download Kibana from here: https://www.elastic.co/downloads/kibana. You can find better installation documentation here.

If you want to setup Kibana to run as a service you can use the following command in the Windows Console or your preferred terminal (you can see my setup here):

At this point you should be able to verify that Elasticsearch is running at http://localhost:9200 and that Kibana is running at http://localhost:5601 by visiting those URLs in your preferred browser.

Using Serilog

As mentioned in the introduction, we'll be using Serilog instead of NLog. This is so that we can take advantage of the structured data Serilog gives us in our Elasticsearch indexes. Setting up Serilog with .NET Core is pretty straight forward.

Looking at the code you should notice that loading settings from my appsettings.json file. If you need some help with that you read my previous post.

I've enriched my events in two ways. First I've configured Serilog to use the LogContext. For more information take a look at the Serilog documentation here. The second enrichment simply puts the application name on every event generated by Serilog.

I always want Serilog to write to the console (at least while the application is being developed). To accomplish that I'm using the LiterateConsole Sink. If you want to know why I'm using the LiterateConsole over the ColoredConsole you can read more about it here.

Lastly, depending on the value in esConfig.Enabled I'm conditionally setting up the Elasticsearch sink. You can find all the information about the various configuration options here. Here is the short version:

AutoRegisterTemplate - Tells Serilog to automatically register a template for the indexes it creates using a template optimized for working with Serilog events.

MinimumLogEventLevel - Kind of straight forward.

CustomFormatter - In order to avoid deeply nested objects Serilog writes inner exceptions as an array of exceptions. This can be problematic for visualizations and some queries. You can change this behavior using the ExceptionAsJsonObjectFormatter.

IndexFormat - This is the pattern Serilog will use to generate the indexes it creates. Typically it's something like "api-logs-{0:yyyy.MM.dd}". If you do not provide a format Serilog will use it's default value.

That's it. Now you're ready to start writing some events to Elasticsearch.

Step 3: Write some exceptions to Elasticsearch

You'll need to use Serilog's ILogger interface wherever you need to log an event. I tend to use StructureMap as my IoC container instead of the default implementation Microsoft offers. This means I need to register the interface in my StructureMap configuration:

_.For<Serilog.ILogger>().Use(Log.Logger);

Once that is done, I can easily inject Serilog into any object created via the IoC container (i.e. my controllers). Writing an event with structured data to Elasticsearch is as simple as making the following call in your code wherever appropriate:

For more information about the features Serilog offers please refer to their documentation. I encourage you to take advantage of source contexts whenever possible. Having the SourceContext property in your event data makes filtering a lot easier.

Using Kibana

It's taken a while, but you've finally got Elasticsearch setup, Kibana installed and running, and your source code writing events to an Elasticsearch index. Great... now it's time to start seeing the effort pay off.

Step 1: Setup Your Index Pattern

If this is the first time you've run Kibana you will most likely be looking at the screen where Kibana asks you to tell it about your index pattern:

If you recall, back when we setup the Serilog Elasticsearch sink one of the properties we configured was the IndexFormat. This is the value you'll want to use here less the date format portion of the string. If you used "api-logs-{0:yyyy.MM.dd}" for your IndexFormat, then the Index Pattern is "api-logs-".

With the Index Pattern set you'll want to head over to the Discover tab.

Step 2: Save a Simple Query

Before you can discover anything you'll need to make sure you've logged at least a few events to Elasticsearch. You'll also want to make sure that they occurred within the time frame you're currently viewing (look in the upper right corner of the Kibana window). As long as you have some events stored in ES, clicking on Discover should display a window that looks something like this:

In order to create a visualization you're going to need to save a search. You can find the full Discover documentation here. For the purposes of moving forward, we'll save a simple search:

In the Query bar type in: level:Error

Click on the search button (magnifying glass)

Click on Save in the upper right corner

Give the search a slick name like All Errors

Click Save

Step 3: Create a Simple Visualization

With the search saved it's time to move over to the Visualize section of Kibana.

There are several visualizations you can create. In this example we'll create a simple Vertical Bar Chart using a Date Histogram to group the errors by date and time. Creating this visualization is pretty straight forward:

Select the Vertical Bar Chart option from the list of visualizations

On the right you can select All Errors from the list of saved searches

In the next window select X-Axis

Under Aggregation choose Date Histogram

Leave all of the default settings

Click on the run button (Looks like a play button)

Click Save in the upper right

Step 4: Build your dashboard

With the visualization saved you can easily add it to your dashboard. You can find a lot more information about building dashboards than I can find in the official Kibana documentation.

Update

Configuration.GetSection(string).Bind(object) has been moved to a new package in .NET Core 1.1: Microsoft.Extensions.Configuration.Binder. You will need Microsoft.Extensions.Options.ConfigurationExtensions for the services.Configure<TConfig>(Configuration.GetSection(string)) bits.

First, the bad news

Before .NET Core I used build specific web.config transforms. When building MVC apps I took advantage XML transforms to have build specific configurations (obvious examples being Debug vs. Release). If the project type didn't have transforms out of the box I used something like SlowCheetah to handle the XML transform (for example WPF).

While just about every tutorial out there tells you how to setup environment specific appsettings.json files, I haven't found any information about build specific application settings. Hopefully I'm just missing something. While this isn't a huge loss, it was convenient to be able to select "Debug - Mock API" as my build configuration and have a transform in place to adjust my web.config as necessary.

A Basic Example

Microsoft's new approach to configuration makes it incredibly easy to use strongly typed configurations via the IOptions<T> interface. Let's start with the following appsettings.json file:

With those two things in place, it's simply a matter of adding the appropriate code to your project's Startup.cs. The following example code includes several things that are not necessary for this basic example. My hope is that you might see something that answers a question you may have that I don't explicitly address in this post.

StructureMap

By default you get Microsoft's IoC container. While it does the job for simple projects, I much prefer the power that StructureMap gives me. However, I was having trouble getting IOptions<IdentityServerConfig> properly injected into my controllers.

The solution to my problem ended up being pretty straight forward. Just make sure that all of your calls to services.configure<T> come before you make you're call to:

In hind site that's a pretty obvious thing to do. StructureMap won't know anything about what you've added to the default IoC container after you call container.Populate(servcies).

Using your settings

After the configuration has been loaded and StructureMap has been configured you can get access to the values from your appsettings.json file by injecting IOptions<T> (where T would be IdentityServerConfig in my example) into the controller (or whatever class you need).

That's great, unless you need to access the values in Startup.cs for some reason. The solution to that problem is to use the following code after the configuration has been loaded (via builder.Build()):

While that's pretty simple code, I had some trouble finding that information.

Overriding settings

If you look at the "Logging" section in my appsettings.json you'll notice there is a Boolean value indicating whether or not Elasticsearch should be used. I have Elasticsearch running locally, but not in the development environment.