Quantcast
Channel: logging – .NET Liberty
Viewing all articles
Browse latest Browse all 4

ASP.NET 5 Logging Framework

$
0
0

In my experience building web services I have found that having rich logging to be one of the most important tools for tracking down issues. Logging allows us to record interesting events and capture some state of the system and share it with operational staff, administrators, and engineers in order to evaluate the health of an application and diagnose and debug issues after they occurred.

main-image

The Challenge

In previous versions of ASP.NET we were mostly on our own when it came to logging. Typically we would pull in a third party library like log4net or NLog and use that throughout our application. This worked pretty well, however it also meant that we had likely coupled our code to a particular logging framework and replacing or adding to that framework was quite difficult.

Perhaps some of us thought ahead and wrote our own abstraction layer as to separate the interface from implementation. The problem with that is we now got into the business of writing logging frameworks instead of focusing on the core business logic we ought to focus on.

The Solution

ASP.NET 5 attempts to solve this problem by introducing an entirely new mechanism for logging in our applications that integrates nicely with the dependency injection system. The main idea is this: rather than explicitly creating a logger instance that is tied to some particular framework like log4net, we declare in our constructor that we need an

ILogger
(https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.Abstractions/ILogger.cs), and the ASP.NET 5 framework figures out how to create it and handles forwarding log entries to various logging providers that are configured ahead of time.

As you can see already, this is not complicated stuff. Instead of using concrete third party implementations of logger instances throughout our application, we just use the Microsoft provided facade. This allows us to decouple our applications from individual logging frameworks with the added benefit that we don’t have to write the abstraction layer ourselves.

Third Party Providers

Currently, two third party providers are supported: Serilog and Elmah.io. It seems that since we’re in the early stages of development, not all third parties have completed their integration just yet. I even noticed that certain “sinks” were not available in Serilog yet when targetting the CoreCLR – like writing to a file (We could target just the full .NET framework though which does have support for file sinks). Elmah.io is a cloud logging service, meaning our logs get written to some service in the cloud and we access them there, not on the local filesystem.

In this post we’ll focus on logging using the default configuration provided by the ASP.NET 5 web application template. Currently that means we will log out to the Debug output window as well as the Console (if it is available).

How to Log

asp-net-5-forest-log

Okay – so how do we use this new logging framework? Lets say we want to log something from our

HomeController
. The easiest way of doing that is to add a constructor that requires an
ILogger<HomeController>
instance. We can then save this instance to a field to be used by our actions later on.
private ILogger<HomeController> _logger;

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

Next we can add a log statement in our

Index
action:
public IActionResult Index()
{
    _logger.LogInformation("Index action requested at {requestTime}", DateTime.Now);
    return View();
}

Take note of the

{requestTime}
placeholder we are using. Don’t confuse this with C# 6.0 string interpolation, this is more akin to
String.Format
except the placeholders are named. This will allow certain logging providers to be able to search and filter based on placeholders, which as you can image can be quite powerful.

Log Levels

You may have noticed we used the

LogInformation
method above. There are actually a handful of these extension methods (
LogDebug
,
LogInformation
,
LogWarning
, etc) that allow us to associate a
LogLevel
with a given log statement. We can then adjust the framework logging level so that we only log the level of information we are concerned about. For example: we may want to enable the most detailed level of logging in our development environment, but only show potential issues (warnings) and errors in production.

Lets take a look at the documentation for each of the available log levels from the enum definition on GitHub:

  • Debug (1): Logs that contain the most detailed messages. These messages may contain sensitive application data. These messages are disabled by default and should never be enabled in a production environment.
  • Verbose (2): Logs that are used for interactive investigation during development.  These logs should primarily contain information useful for debugging and have no long-term value.
  • Information (3): Logs that track the general flow of the application. These logs should have long-term value.
  • Warning (4): Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the application execution to stop.
  • Error (5): Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a failure in the current activity, not an application-wide failure.
  • Critical (6): Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires immediate attention.
  • None (int.MaxValue): Not used for writing log messages. Specifies that a logging category should not write any messages.

One interesting thing to note here is that other logging frameworks may use

Debug
as the second-most granular log level, with something like
Verbose
or
Trace
as the most detailed. With the Microsoft logging framework
Debug
is the most detailed. I think this makes sense actually – when I’m debugging an issue I want to typically see absolutely everything that can help me diagnose what is going on.

Logging Configuration

When we create a new ASP.NET 5 project, the

Startup
class will contain a few lines of code to configure the logging framework. Let’s take a look at the first few lines of the
Configure
method:
loggerFactory.MinimumLevel = LogLevel.Information;
loggerFactory.AddConsole();
loggerFactory.AddDebug();

The first line is setting the framework logging level. As mentioned above, each time we write to our log, we associate a log level with that entry by calling the appropriate extension method like

LogInformation
or
LogDebug
. When the Microsoft logging framework receives that entry, it will discard it if it is below the framework minimum level. This is really nice since our application doesn’t have to check all over the place just how detailed the logs should be. We just log everything at various log levels and the framework will know whether to use or discard it.

The next two lines are extension methods that register logging “providers” with the logging framework. We can add as many logging providers with the framework as we like – each time we write a log entry, it will be forwarded to each logging provider. In this case, one provider will write to the Console window (if available), and the other will write to the Debug output window.

Provider Logging Level

In addition to having a framework minimum logging level, each individual provider can configure their own logging levels. For example: we may want to log only warnings or higher to our Console. We can do this by using an overload on the

AddConsole
extension method:
loggerFactory.MinimumLevel = LogLevel.Information;
loggerFactory.AddConsole(LogLevel.Warning);
loggerFactory.AddDebug();

Now our Debug window will show all messages at

Information
level or higher while the Console will only be used for
Warnings
or higher.

An Unfortunate Name

asp-net-5-logging-on-fire

Let’s take a look at the following example:

loggerFactory.MinimumLevel = LogLevel.Debug;
loggerFactory.AddDebug();
var logger = loggerFactory.CreateLogger("Test");
logger.LogDebug("This is a debug statement.");

You might imagine that if we look at our Debug window, we should see

"This is a debug statement"
. Well, this is not quite the case. When we call
AddDebug
without specifying the provider log level, it defaults to… LogLevel.Information.

That means even though we set our framework log level to Debug, we are using the Debug log provider, and using the

LogDebug
method on our
ILogger
, the entry will get dropped because the Debug provider by default only wants
LogLevel.Information
or higher. If we want to log
Debug
level messages to the Debug window, we have to specify that using an overload on
AddDebug
:
loggerFactory.MinimumLevel = LogLevel.Debug;
loggerFactory.AddDebug(LogLevel.Debug);
var logger = loggerFactory.CreateLogger("Test");
logger.LogDebug("This is a debug statement.");

Searching for Answers

This seemed pretty counter intuitive to me. I actually filed an issue with the Logging team on GitHub to figure out what’s happening here, specifically why

LogLevel.Information
was chosen as the default log level for the Debug provider. Eilon Lipton (GitHub: Eilon) actually gave a pretty reasonable answer:

The reason for Information being the default is that as a whole we decided that the frameworks (ASP.NET + EF) should emit about ~10 log messages at the Information level or higher on a “typical” request (started, some DB queries, routing, MVC, view rendering, etc.).

If the default was Debug or Verbose then a typical request would have hundreds of log messages flowing through, and that would probably make the log output unusable in most cases.

Okay, fair enough. The reasoning is sensible but I still think it is unfortunate because it is not intuitive to people new to this new logging framework. I had no idea at first that we could specify the provider log level in addition to the root log level and I only figured out what was going on after digging into the source on GitHub.

One promising avenue is there’s a bunch of activity on the Logging repository on GitHub right now and it looks like they’re working on a new way of configuring loggers through JSON (much in the same way that EntityFramework 7 stores its connection string in

appsettings.json
). I’m looking forward to seeing what they come up with.

Nonetheless I think what we have today is a really good starting point for making use of the new logging framework and keeping our application separated from our logger implementations so that we can easily add and modify logging providers in the future without having to make sweeping changes across our application. If you’re using the new logging framework in ASP.NET 5 today, let us know your experiences with it below.

 


Viewing all articles
Browse latest Browse all 4

Trending Articles