Being able to quickly throw together a console application is a good way to automate a repetitive task.
Sometimes these tasks might take some time to run and you might even have them set up to start automatically from time to time.
It is therefore important that you have logging configured in them so that if something goes wrong you can go back and see why.
When you use one of the available templates to create an ASP.NET Core project they are configured with logging enabled, but when you create a console application that is not the case.
This blog post will walk you through setting up logging for .Net Core Console applications.
UPDATE 2018/02/15: If you are looking for a simple way to just have a log file then you can do that easily with Serilog:
First install Serilog’s file sink:
$ dotnet add package Serilog.Sinks.File
And then in Program.cs’ Main:
Log.Logger = new LoggerConfiguration()
.WriteTo.File("consoleapp.log")
.CreateLogger();
Log.Information("Hello, world!");
However, if you want to use the same mechanism available in ASP.NET Core so that you can for example use a class library that relies on .Net Core’s new logging facilities or if you want to take advantage of dependency injection read on.
Logging in .Net Core
.Net Core introduced a new logging framework. It’s available through the Microsoft.Extensions.Logging
nuget package.
What? A new logging framework you might be wondering. There’s already NLog, Log4Net, Serilog, etc. Why do we need yet another one?
Is this a case of there’s “too many competing standards so lets create our own” so now there’s too many plus one.
In this case this really isn’t that bad. The way we use a logging framework is pretty much the same no matter which logging framework we pick.
Also, the .Net Core logging framework is just a sort of wrapper on other logging frameworks. The way it works is that you use a common interface for logging in your code and then you add a specific logging framework as a provider to .Net Core’s logging framework.
.Net Core comes with some of these providers out of the box, for example the nuget package Microsoft.Extensions.Logging.Console
will add one such provider that outputs the logs to console.
There’s providers for most logging frameworks, for example Serilog, NLog, Log4Net, etc.
Logging and Dependency Injection
The .Net Core logging framework was designed to be used with dependency injection. For example, if you want to add logging to a class named MyClass
you need to add an ILogger<MyClass>
parameter to the class’ constructor:
private readonly ILogger _logger;
MyClass(ILogger<MyClass> logger)
{
_logger = logger;
}
For this to work we need to setup dependency injection on the console application since it isn’t available when you create a new project.
Thankfully this is simple to do.
First thing you need to do is to add the nuget package Microsoft.Extensions.DependencyInjection
:
$ dotnet add package Microsoft.Extensions.DependencyInjection
After that in Program.cs
:
static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
var serviceProvider = serviceCollection.BuildServiceProvider();
//...
}
private static void ConfigureServices(IServiceCollection services)
{
//we will configure logging here
}
Here we are creating a ServiceCollection
which is where we register our mappings (for example .AddTransient<IMyInterface, MyClassThatImplementsInterface>()
).
We also get an instance of a ServiceProvider
by calling the BuildServiceProvider
method on the ServiceCollection
. The service provider is the class that contains the GetService<Type>
method. That method allows us to request a type to be resolved using the dependencies we’ve registered in the ServiceCollection
.
Now we just need to configure logging.
Configuring logging
The easiest way to setup logging is to use it with the console provider. To do that first install the logging nuget pacakge:
$ dotnet add package Microsoft.Extensions.Logging
And then the console provider
$ dotnet add package Microsoft.Extensions.Logging.Console
You then have to register logging in the ConfigureServices
method and use the console provider:
private static void ConfigureServices(IServiceCollection services)
{
services.AddLogging(configure => configure.AddConsole())
.AddTransient<MyClass>();
}
In the snippet above MyClass
is an hypothetical class where you want to use the logger. You have to inject the logger in its constructor:
private readonly ILogger _logger;
MyClass(ILogger<MyClass> logger)
{
_logger = logger;
}
void SomeMethod()
{
_logger.LogInfo("Hello");
}
Then you can get an instance of it with the logger in Program.cs by using the ServiceProvider
:
static void Main(string[] args)
{
//...
var serviceProvider = serviceCollection.BuildServiceProvider();
var myClass = serviceProvider.GetService<MyClass>();
myClass.SomeMethod();
}
If you want to use the logger in Program.cs
itself you can get an instance of it by doing:
var logger = serviceProvider.GetService<ILogger<Program>>();
Also, you can specify configuration parameters when you are registering logging in ConfigureServices
, for example if you only want Information or more serious log entries:
private static void ConfigureServices(IServiceCollection services)
{
services.AddLogging(configure => configure.AddConsole())
.Configure<LoggerFilterOptions>(options => options.MinLevel = LogLevel.Information)
.AddTransient<MyClass>();
}
An example using Serilog
Although writing the logs to the console makes a good example you probably want to have those logs in a file on disk.
For that we can use another provider. In this example I’ll use Serilog. The reason I picked Serilog is because you can configure it in code (i.e. without having to create a configuration file).
First thing we need to do is add the following nuget packages:
$ dotnet add package Serilog.AspNetCore
$ dotnet add package Serilog.Sinks.File
You might be wondering why are we installing something for Asp.Net Core if this post is about console applications. Turns out that nuget package is just poorly named. If you think about it why would a logging framework have a dependency on a web framework?
We now need to configure Serilog to write to a file, for example “consoleapp.log”. In your Program.cs’s Main method add:
static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.File("consoleapp.log")
.CreateLogger();
...
And in ConfigureServices
:
private static void ConfigureServices(IServiceCollection services)
{
services.AddLogging(configure => configure.AddSerilog())
...
For reference here’s a full example:
class Program
{
static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.File("consoleapp.log")
.CreateLogger();
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
var serviceProvider = serviceCollection.BuildServiceProvider();
serviceProvider.GetService<MyClass>().SomeMethod(); //in MyClass' constructor get
//the logger by adding a parameter (ILogger<MyClass>)
var logger = serviceProvider.GetService<ILogger<Program>>();
logger.LogInformation("Log in Program.cs");
}
private static void ConfigureServices(IServiceCollection services)
{
services.AddLogging(configure => configure.AddSerilog())
.AddTransient<MyClass>();
}
}
Using .Net Core’s ConfigurationBuilder to control the logging level
At times you might want to change how much logging your application does. Ideally you’ll want to do this without having to redeploy the application.
Thankfully .Net Core has a easy to use API for configuration. We can leverage it here to change the logging level.
As an example, lets make the necessary changes so that if there’s an environment variable named VERBOSE
with value “true” then we change the log level to Trace
otherwise we leave it at Error
.
First thing we need to do is add the following nuget packages:
$ dotnet add package Microsoft.Extensions.Configuration
$ dotnet add package Microsoft.Extensions.Configuration.EnvironmentVariables
After doing this we can setup ConfigurationBuilder
to get values from environment variables. The ConfigurationBuilder
class is the class we need to use to build an instance of IConfiguration
. IConfiguration
will be where we’ll have access to the configuration values.
Here’s how that looks in Program.cs
:
static void Main(string[] args)
{
IConfiguration configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
...
We can now pass the IConfiguration
instance to the ConfigureServices
method so that we can use it to decide how we want to filter the log events:
private static void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddLogging(configure => configure.AddSerilog())
.AddTransient<MyClass>();
if (configuration["LOG_LEVEL"] == "true")
{
services.Configure<LoggerFilterOptions>(options => options.MinLevel = LogLevel.Trace);
}
else
{
services.Configure<LoggerFilterOptions>(options => options.MinLevel = LogLevel.Error);
}
}
For reference here’s the full example:
class Program
{
static void Main(string[] args)
{
IConfiguration configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
Log.Logger = new LoggerConfiguration()
.WriteTo.File("consoleapp.log")
.CreateLogger();
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection, configuration);
var serviceProvider = serviceCollection.BuildServiceProvider();
serviceProvider.GetService<MyClass>().SomeMethod(); //in MyClass' constructor get
//the logger by adding a parameter (ILogger<MyClass>)
var logger = serviceProvider.GetService<ILogger<Program>>();
logger.LogInformation("Log in Program.cs");
}
private static void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddLogging(configure => configure.AddSerilog())
.AddTransient<MyClass>();
if (configuration["LOG_LEVEL"] == "true")
{
services.Configure<LoggerFilterOptions>(options => options.MinLevel = LogLevel.Trace);
}
else
{
services.Configure<LoggerFilterOptions>(options => options.MinLevel = LogLevel.Error);
}
}
}
You can as easily have the configuration options come from a json file (add the Microsoft.Extensions.Configuration.Json nuget package). Just remember that if you go with environment variables stick with your OS’ rules for naming them, for example LOG-LEVEL
is not a valid environment variable name in Linux.