Fundamentals of Logging in .NET Core
.NET Core SDK is a light weight SDK which includes a bare minimum set of features required to build an application. We can install NuGet packages for other features we require for our application. For this, Microsoft provides .NET APIs as .NET Extensions.
.NET Extensions is an open-source, cross-platform set of APIs for commonly used programming patterns and utilities, such as dependency injection, logging, and app configuration. Most APIs in this project are meant to work on many .NET platforms, such as .NET Core, .NET Framework, Xamarin, and other. While commonly used in ASP.NET Core applications, these APIs are not coupled to the ASP.NET Core application model. They can be used in console apps, WinForms and WPF, etc. You can find the documentation and the source code of extensions at https://github.com/aspnet/Extensions.
All the extensions are included under the Microsoft.Extensions
namespace. You can find all built-in and third party extensions at nuget.org/packages.
The Logging API is included in the Microsoft.Extensions.Logging
package. The Logging API does not work as standalone. It works with one or more logging providers that store or display logs to a particular medium such as Console, Debug, TraceListeners etc.
So, there are two important building blocks for implementing logging in a .NET Core based application:
- Logging API
- Logging Providers
The following figure illustrates logging in .NET Core:
As you can see in the above figure, the logging API in Microsoft.Extensions.Logging
works on the .NET Core based applications whether it is ASP.NET Core or EF Core. You just need to use the logging API with one or more logging providers to implement logging in any .NET Core based application.
Logging API
As mentioned before, Microsoft provides logging API as an extension in the wrapper Microsoft.Extensions.Logging which comes as a NuGet package.
Microsoft.Extensions.Logging
includes the necessary classes and interfaces for logging. The most important are the ILogger, ILoggerFactory, ILoggerProvider interfaces and the LoggerFactory class.
The following figure shows the relationship between logging classes.
Let’s have an overview of each of the above class.
ILoggerFactory
The ILoggerFactory
is the factory interface for creating an appropriate ILogger
type instance and also for adding the ILoggerProvider
instance.
publicinterface
ILoggerFactory
:
IDisposable
{
ILogger
CreateLogger(
stringcategoryName);
void
AddProvider(
ILoggerProviderprovider);
}
The Logging API includes the built-in LoggerFactory
class that implements the ILoggerFactory
interface. We can use it to add an instance of type ILoggerProvider
and to retrieve the ILogger
instance for the specified category. Visit ILoggerFactory and LoggerFactory for more information.
ILoggerProvider
The ILoggerProvider
manages and creates an appropriate logger, specified by the logging category.
publicinterface
ILoggerProvider
:
IDisposable
{
ILogger
CreateLogger(
stringcategoryName);
}
We can create our own logging provider by implementing the ILoggerProvider
interface. Visit ILoggerProvider for more information.
ILogger
The ILogger
interface includes methods for logging to the underlying storage. There are many extension methods which make logging easy. Visit ILogger for more information.
publicinterface
ILogger
{
void
Log<
TState>(
LogLevellogLevel,
EventIdeventId,
TStatestate,
Exceptionexception,
Func<TState, Exception,
string> formatter);
bool
IsEnabled(
LogLevellogLevel);
IDisposable
BeginScope<
TState>(
TStatestate);
}
Logging Providers
A logging provider displays or stores logs to a particular medium such as a console, a debugging event, an event log, a trace listener, and others. Microsoft provides various logging providers as NuGet packages.
The following table lists important logging providers.
Logging Provider’s NuGet Package | Output Target |
Microsoft.Extensions.Logging.Console | Console |
Microsoft.Extensions.Logging.AzureAppServices | Azure App Services ‘Diagnostics logs’ and ‘Log stream’ features |
Microsoft.Extensions.Logging.Debug | Debugger Monitor |
Microsoft.Extensions.Logging.EventLog | Windows Event Log |
Microsoft.Extensions.Logging.EventSource | EventSource/EventListener |
Microsoft.Extensions.Logging.TraceSource | Trace Listener |
Microsoft has also collaborated with various logging framework teams (including third parties like NLog, Serilog, Loggr, Log4Net, and others) to extend the list of providers compatible with Microsoft.Extensions.Logging
. The following are some thrid-party logging providers:
Logging Provider | Description |
elmah.io | Provider for the Elmah.Io service |
Loggr | Provider for the Logger service |
NLog | Provider for the NLog library |
Serilog | Provider for the Serilog library |
Let’s take an example using the Microsoft.Extensions.Logging.Console
package which displays logs on the Console.
Console Logging Provider
Let’s see how to display logs on the console using the NuGet package for a console provider.
The Microsoft.Extensions.Logging.Console
package includes logging classes which send log output to the console.
The following figure illustrates how the logging API works with the console logging provider.
Logging API with Console Logging Provider
As you can see in the above figure, the ConsoleLogger
implements ILogger
, while the ConsoleLoggingProvider
implements ILoggingProvider
. The ConsoleLoggerExtensions
class includes extension method AddConsole()
, which adds a console logger to the LoggerFactory
.
Now, let’s see how to display logs on the Console in the .NET Core console application.
First of all, create a new console application using the Console App (.NET Core) template in Visual Studio 2017 (or later).
Now, you need to install a NuGet package of Microsoft.Extensions.Logging
. You can install it either using the NuGet Package Manager or executing the following command in the Package Manager Console.
PM> Install-Package Microsoft.Extensions.Logging
Now, you need to install a logging provider of your choice. Here, we will send logs on the Console, so, install the Microsoft.Extensions.Logging.Console
package either using NPM or execute the following command in the Package Manager Console in Visual Studio.
PM> Install-Package Microsoft.Extensions.Logging.Console
After successfully installing the above two packages, you can now implement logging in your .NET Core console application, as shown below.
Example: Logging in .NET Core Console App
Copy
namespace DotnetCoreConsoleApp
{
class
Program
{
static
void
Main(
string[] args)
{
ILoggerFactory
loggerFactory =
newLoggerFactory
(
new
[] {
newConsoleLoggerProvider
((_, __) =>
true,
true) }
);
//or
//ILoggerFactory loggerFactory = new LoggerFactory().AddConsole();
ILogger
logger = loggerFactory.CreateLogger<
Program>();
logger.LogInformation(
"This is log message.");
}
}
}
Output:
info: DotnetCoreConsoleApp.Program[0]
This is log message.
In the above example, we created an object of the LoggerFactory
class and assigned it to the ILoggerFactory
type variable, as shown below.
ILoggerFactory loggerFactory = new LoggerFactory(
new[] { new ConsoleLoggerProvider ((_, __) => true, true) }
);
The LoggerFactory
can contain one or more logging providers, which can be used to log to multiple mediums concurrently. The constructor of the LoggerFactory
accepts an array of different logger provider objects as new[] { }
. We want to display logs on the console, so we need to create an object of the console logger provider ConsoleLoggerProvider
.
There are four constructors of the ConsoleLoggerProvider
. Use the one that allows lambda expression (Func<>) for log filtration and includeScope Boolean. Here, we don’t want to filter any information so the lambda expression would always return true (_, __) => true
, as shown below.
new ConsoleLoggerProvider((_, __) => true, true)
Then, we can use this object of the LoggerFactory
to create a logger using which we can actually log information, errors, warnings, traces, debugs etc. loggerFactory.CreateLogger<Program>()
creates a logger specific to the Program
class so that the logger will display a message with context info, e.g. DotnetCoreConsoleApp.Program[0].
Most logging providers include an extension method of ILoggerFactory
, which is a shortcut to add a provider to the logger factory. For example, to add a console logger provider to the LoggerFactory
, just call the LoggerFactory.AddConsole()
extension method with the same parameters as ConsoleLoggerProvider
, as shown below.
publicILoggerFactory loggerFactory =
newLoggerFactory().AddConsole();
This is more readable and maintainable than creating a logger provider manually. The above logger factory will display the same output as above.
Log Levels
Log levels indicate the importance or severity of log messages. Built-in log providers include extension methods to indicate log levels.
The following table lists log levels in .NET Core.
Log Level | Severity | Extension Method | Description |
Trace | 0 | LogTrace() | Logs messages only for tracing purposes for the developers. |
Debug | 1 | LogDebug() | Logs messages for short-term debugging purposes. |
Information | 2 | LogInformation() | Logs messages for the flow of the application. |
Warning | 3 | LogWarning() | Logs messages for abnormal or unexpected events in the application flow. |
Error | 4 | LogError() | Logs error messages. |
Critical | 5 | LogCritical() | Logs failures messages that require immediate attention. |
We can use extension methods to indicate the level of the log messages as shown below.
namespace DotnetCoreConsoleApp
{
class
Program
{
static
void
Main(
string[] args)
{
ILoggerFactory
loggerFactory = new
LoggerFactory().AddConsole((_, __) =>
true);
ILogger
logger = loggerFactory.CreateLogger<
Program>();
logger.LogInformation(
"Logging information.");
logger.LogCritical(
"Logging critical information.");
logger.LogDebug(
"Logging debug information.");
logger.LogError(
"Logging error information.");
logger.LogTrace(
"Logging trace");
logger.LogWarning(
"Logging warning.");
}
}
}
The above example will display the following output:
Logging in ASP.NET Core
ASP.NET Core uses the same logging mechanism as .NET Core logging. So, it is highly recommended to go through the previous chapter Logging in .NET Core before reading this.
Here, we will implement logging in the ASP.NET Core 2.x MVC application.
As explained in the previous chapter, the logging API in Microsoft.Extensions.Logging namespace works with one or more built-in or third party logging providers. So, in an ASP.NET Core MVC application, we will also have to install the NuGet package Microsoft.Extensions.Logging and one or more logging providers of our choice.
Create an ASP.NET Core MVC application in Visual Studio 2017 (or later). When you create the ASP.NET Core MVC web application in Visual Studio 2017 (or later), it automatically includes the NuGet package for Microsoft.Extensions.Logging and the following logging providers under the Microsoft.AspNetCore.App NuGet package. So, we don’t have to install it manually.
- Microsoft.Extensions.Logging.Console
- Microsoft.Extensions.Logging.Debug
- Microsoft.Extensions.Logging.EventSource
- Microsoft.Extensions.Logging.TraceSource
Add Logging Providers
As mentioned in the previous chapter, we need to add providers in LoggerFactory. In the ASP.NET Core MVC application, the call to the WebHost.CreateDefaultBuilder(args) method in the Program.cs internally adds the Console, Debug, and EventSource logging providers.
Example: Program.cs
Copy
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Look at the source code of the WebHost.CreateDefaultBuilder() method on GitHub and you will find the following code:
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection(“Logging”));
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
}).
Thus, if you want to use these providers, no need to add them manually. If you want to use other providers or any default provider, then you need to remove all the existing providers and add the provider of your choice. To configure logging providers, call the ConfigureLogging() extension method of IWebHostBuilder, as shown below.
Example: Program.cs
Copy
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(logBuilder =>
{
logBuilder.ClearProviders(); // removes all providers from LoggerFactory
logBuilder.AddConsole();
logBuilder.AddTraceSource(“Information, ActivityTracing”); // Add Trace listener provider
})
.UseStartup<Startup>();
In the above example, the ConfigureLogging() method takes action to delegate Action<ILogBuilder> to configure logging providers. To add logging providers of your choice, remove all the default providers using ClearProviers() and then call the extension method of a provider to add it, such as AddTraceSource() which will add the trace listener provider, and the AddConsole() method which will add the Console logging provider.
You can also configure the logging provider using ILoggerFactory in the Configure() method of the Startup class. Let’s see an example on how to store logs in a text file.
Store Logs in a Text File
To store logs in a file, install the NuGet package Serilog.Extensions.Logging.File. Serillog includes an extension method for ILoggerFactory but not for ILogBuilder (in v 1.1.0). So, go to the Startup.cs file and add the ILoggerFactory parameter in the Configure() method. Then, call the AddFile() extension method to add Serillog file provider, as shown below. ASP.NET Core dependency injection will automatically pass an instance of the LoggerFactory for this parameter.
Example: Startup.cs
Copy
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// other code remove for clarity
loggerFactory.AddFile(“Logs/mylog-{Date}.txt”);
}
This will store all the logs in the mylog-<date>.txt file, under the Logs folder in your application.
Create Logs in the Controller
We can use ILogger or ILoggerFactory anywhere in an application using ASP.NET Core DI (Dependency Injection). Consider the following example of HomeController:
Example: Logging in MVC Controller
Copy
namespace AspDotNetCoreMvcApp.Controllers
{
public class HomeController : Controller
{
private readonly ILogger _logger;
public HomeController(ILogger<HomeController> logger){
_logger = logger;
}
public IActionResult Index()
{
_logger.LogInformation(“Log message in the Index() method”);
return View();
}
public IActionResult About()
{
_logger.LogInformation(“Log message in the About() method”);
return View();
}
}
}
In the above example, the ILogger<HomeController> parameter is included in the constructor. ASP.NET Core DI will pass the ILogger instance, which can be used to log in the Index() and About() action methods.
Passing HomeController as generic type for the ILogger<HomeController>, will be used as a category. For example, specifying ILogger<HomeController< will display a fully qualified name AspDotNetCoreMvcApp.Controllers.HomeController in the logs, as shown below.
info: AspDoteNetCoreMvcApp.Controllers.HomeController[0]
Log message in the Index() method
Let’s understand the above log message. Here, we logged information using the LogInformation() method, so it starts with “info:” followed by the fully qualified name of the class where a log is created: AspDoteNetCoreMvcApp.Controllers.HomeController[0]. [0] is the event id. You can specify this event id to identify a record, e.g. Id, page number or other important information which uniquely identifies a log. We didn’t specify any event id, so it will be 0. The next line is an actual log message: “Log message in the Index() method”.
The same can be achieved by passing ILoggerFactory in the constructor.
public class HomeController : Controller
{
private readonly ILogger _logger;
public HomeController(ILoggerFactory logFactory)
{
_logger = logFactory.CreateLogger<HomeController>();
}
public IActionResult Index()
{
_logger.LogInformation(“Log message in the Index() method”);
return View();
}
public IActionResult About()
{
_logger.LogInformation(“Log message in the About() method”);
return View();
}
}
Now, run the above application from command prompt by navigating to /<app root folder>/bin/debug/netcoreapp2.1/, run the dotnet <app name>.dll command and then open http://localhost:5000 in the browser. It will display the same logs on the Console as above.
Logs:
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = “Index”, controller = “Home”}. Executing acti
on AspDoteNetCoreMvcApp.Controllers.HomeController.Index (AspDotNetCoreMvcApp)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method AspDoteNetCoreMvcApp.Controllers.HomeController.In
dex (AspDotNetCoreMvcApp) – Validation state: Valid
info: AspDoteNetCoreMvcApp.Controllers.HomeController[0]
Log message in the Index() method
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action method AspDoteNetCoreMvcApp.Controllers.HomeController.Ind
ex (AspDotNetCoreMvcApp), returned result Microsoft.AspNetCore.Mvc.ViewResult in
0.8505ms.
info: Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor[1]
Executing ViewResult, running view Index.
info: Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor[4]
Executed ViewResult – view Index executed in 231.2839ms.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action AspDoteNetCoreMvcApp.Controllers.HomeController.Index (Asp
DotNetCoreMvcApp) in 288.6931ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 946.274ms 200 text/html; charset=utf-8
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/images/banner1.svg
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/images/banner2.svg
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 5.6471ms 404
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 6.5811ms 404
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/css/site.min.css
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 0.2811ms 404
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/js/site.min.js
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/images/banner3.svg
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 0.178ms 404
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 0.2342ms 404
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/css/site.min.css
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 0.1173ms 404
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/js/site.min.js
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 0.2539ms 404
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/favicon.ico
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 0.3253ms 404
Thus, we can implement logging in ASP.NET Core MVC application.