Problem
How to log messages (information, errors, warnings) in ASP.NET Core.
Solution
Starting from a empty project (previous post), inject ILogger
as dependency and use various Log<log-level>
methods:
public class HelloLoggingMiddleware
{
private readonly RequestDelegate next;
private readonly ILogger<HelloLoggingMiddleware> logger;
public HelloLoggingMiddleware(
RequestDelegate next,
ILogger<HelloLoggingMiddleware> logger)
{
this.next = next;
this.logger = logger;
}
public async Task Invoke(HttpContext context)
{
this.logger.LogInformation(101, "Invoke executing");
await context.Response.WriteAsync("Hello Logging!");
this.logger.LogInformation(201, "Invoke executed");
}
}
Running the application will show messages in Debug/Console windows:
Discussion
Framework provides logging capability through ILoggerFactory
, to which you could attach one or more providers. Providers act on the logged data in some form, e.g., log it to file, database, Azure, etc.
Both built-in and third party providers exist and they attach themselves to the ILoggerFactory
via its AddProvider()
method, however, they all expose extension methods on ILoggerFactory
to simplify this task.
Although it seems like magic (how was the Console logger setup!), ASP.NET Core 2.0 hides the adding of logging providers behind CreateDefaultBuilder()
method of WebHost
, in Program.cs. To see this, replace the BuildWebHost()
method in Program.cs and run the program, you’ll get the same result:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging((context, builder) =>
{
builder.AddConsole();
builder.AddDebug();
})
.UseStartup<Startup>()
.Build();
To use logging providers, simply inject ILogger
as a dependency, where T
is the category of logs. Category
is normally the fully qualified name of class in which logs are written and is written with every log message.
Log Category
You could override category value by injecting ILoggerFactory
as dependency (rather than ILogger
) and using ILoggerFactory.CreateLogger()
method to build a new ILogger
:
public class HelloLoggingMiddleware
{
private readonly ILogger logger;
public HelloLoggingMiddleware(
RequestDelegate next,
ILoggerFactory loggerFactory)
{
this.next = next;
this.logger = loggerFactory.CreateLogger("Hello_Logging_Middleware");
}
}
Log Levels
Every log message you write will have a severity indicated by LogLevel
enum
. In practice, you would use the following extension methods that encapsulates the call to ILogger.Log()
and passes in log levels:
LogTrace/LogDebug
: to log information useful during development and debugging
LogInformation
: to log messages showing application flow
LogWarning
: to log exceptions that can be handled
LogError
: to log exceptions that can’t be handled and show failure of an operation
LogCritical
: to log exceptions of serious consequences to the entire application
Log Event ID
Every log message you write can have an event ID (integer), which is useful to group messages of a certain type. Every provider will deal with event ID in their own way. In the solution above, you could see the console output showing these in square brackets.
Tip: Use a class with constants or static readonly
fields to represent event IDs.
Log Filters
You could filter the log messages using AddFilter()
method:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging((context, builder) =>
{
builder.AddFilter((category, level) =>
category.Contains("Fiver.Asp.Logging.HelloLoggingMiddleware"));
})
.UseStartup<Startup>()
.Build();
Third-party Providers
To see how a third-party provider works, please refer to my post on Structured Logging using Serilog.