ASP.NET Core Logging Tutorial

This is part 4 in our series about ASP.NET Core:

Most parts of elmah.io consist of small services. While they may not be microservices, they are in fact small and each do one thing. We recently started experimenting with ASP.NET Core (or just Core for short) for some internal services and are planning a number of blog posts about the experiences we have made while developing these services. This is the fourth part in the series.

In the previous posts, the focus was on configuration. In this post, we will take a look at the new logging features in Core. Where previous versions of ASP.NET had hooks to capture information about exceptions and similar, ASP.NET Core comes with its own logging framework called Microsoft.Extensions.Logging. Microsoft.Extensions.Logging is available on NuGet and isn't bound to ASP.NET Core in any way. This mean that you will be able to utilize this new logging framework from Microsoft throughout all of your applications and services. Logging is based on providers which works as appenders in log4net and sinks in Serilog. A range of providers are available on NuGet for existing logging frameworks like NLog, Serilog and elmah.io. In other words, you don't need to switch to another logging framework, since you can always configure a provider for a logging framework of choice.

Let's learn about Microsoft.Extensions.Logging from an example. Create a new ASP.NET Core project through Visual Studio or the command line. In my case, I'm using Visual Studio 2017 RC, but the generated code looks similar when generated from VS2015 or the CLI. When generated, open the Startup.cs file and navigate to the Configure method:

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    ...
}

As stated in the comment in the top, the Configure method is called when starting the website. See the ILoggerFactory parameter sent to the method? That's our main entry point for configuring logging. ILoggerFactory provides a fluent API for setting up our logging needs. To do some logging, we need an instance of another interface called ILogger, but let's with that for a minute. While you can create an instance of ILoggerFactory yourself, using the instance provided by Core, ensures that you capture log events from Kestrel and other components already initialized at this point.

The code is pretty much self-documented. We tell Microsoft.Extensions.Logging to log messages to the console and debug (through System.Diagnostics.Debug.WriteLine). In a future post, I'll show you how to configure different aspects of logging. For now, the only thing I'll mention is log levels. Like Serilog, NLog etc., Microsoft.Extensions.Logging implements a range of different log levels from Trace to Critical. As default, most providers only logs from Information and up. If you want to extend this to also log Trace and Debug messages, you can configure this as part of adding each provider:

loggerFactory.AddDebug(LogLevel.Trace);

The console provider configured as part of the generated code, uses the appsettings.json file as explained in the previous three posts. To change the minimum log level for this provider, either configure the provider all through C# code or change the Logging.LogLevel.Default setting in appsettings.json:

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Trace",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

Until now, we haven't logged anything. To do so, use dependency injection in Core to inject an instance of ILoggerFactory in your controller:

public class HomeController : Controller
{
    ILoggerFactory _loggerFactory;

    public HomeController(ILoggerFactory loggerFactory)
    {
        _loggerFactory = loggerFactory;
    }

    public IActionResult Index()
    {
        var logger = _loggerFactory.CreateLogger("MyLogger");
        logger.LogInformation("Calling the Index action");
        return View();
    }
}

I've injected ILoggerFactory into the HomeController. To start logging, call the CreateLogger method to get an instance of ILogger. As previously mentioned, ILogger is the client used to log messages to the configured providers. In this example, we log an information message. The MyLogger string sent to the CreateLogger method, tells Microsoft.Extensions.Logging that all messages logged to this logger, will be in the MyLogger category. Categories is something that I want to elaborate on in a future post.

Instead of creating a new logger every time, we can simply inject an instance of ILogger instead:

public class HomeController : Controller
{
    ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    public IActionResult Index()
    {
        _logger.LogInformation("Calling the Index action");
        return View();
    }
}

Decorating the ILogger with a generic parameter of HomeController, automatically generates a logger with a category of the HomeController.

In the next post, I'll show you how to implement custom Error Logging Middleware in ASP.NET Core. Also make sure to visit our post Best practices logging in Web API and MVC with C#