In this post I describe a small change to the way ASP.NET Core logs messages on startup in ASP.NET Core 3.0. Instead of logging messages directly to the console, ASP.NET Core now uses the logging infrastructure properly, producing structured logs.
Annoying unstructured logs in ASP.NET Core 2.x
When you start your application in ASP.NET Core 2.x, by default ASP.NET Core will output some useful information about your application to the console, such as the current environment, the content root path, and the URLs Kestrel is listening on:
Using launch settings from C:\repos\andrewlock\blog-examples\suppress-console-messages\Properties\launchSettings.json...
Hosting environment: Development
Content root path: C:\repos\andrewlock\blog-examples\suppress-console-messages
Now listening on: https://localhost:5001
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
This message, written by the WebHostBuilder, gives you a handy overview of your app, but it's written directly to the console, not through the ASP.NET Core Logging infrastructure provided by Microsoft.Extensions.Logging and used by the rest of the application.
This has two main downsides:
- This useful information is only written to the console, so it won't be written to any of your other logging infrastructure.
- The messages written to the console are unstructured and they will be in a different format to any other logs written to the console. They don't even have a log level, or a source.
The last point is especially annoying, as it's common when running in Docker to write structured logs to the standard output (Console), and have another process read these logs and send them to a central location, using fluentd for example.
Luckily, in ASP.NET Core 2.1 there was a way to disable these messages with an environment variable, as I showed in a previous post. The only downside is that the messages are completely disabled, so that handy information isn't logged at all:
Luckily, a small change in ASP.NET Core 3.0 gives us the best of both worlds!
Proper logging in ASP.NET Core 3.0
If you start an ASP.NET Core 3.0 application using dotnet run
, you'll notice a subtle difference in the log messages written to the console:
info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\repos\andrewlock\blog-examples\suppress-console-messages
The startup messages are now written using structured logging! But the change isn't as simple as using Logger
instead of Console
. In ASP.NET Core 2.x, it was the WebHost
that was responsible for logging these messages. In ASP.NET Core 3.0, these messages are logged by the ConsoleLifetime
- the default IHostLifetime
registered by the generic host.
I described the role of IHostLifetime
(and the ConsoleLifetime
in particular) in my previous post, but in summary this class is responsible for listening for the Ctrl+C
key press in the console, and starting the shutdown procedure.
The ConsoleLifetime
also registers callbacks during its WaitForStartAsync()
method, that are invoked when the ApplicationLifetime.ApplicationStarted
event is triggered, and also when the ApplicationLifetime.ApplicationStopping
event is triggered:
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
if (!Options.SuppressStatusMessages)
{
// Register the callbacks for ApplicationStarted
_applicationStartedRegistration = ApplicationLifetime.ApplicationStarted.Register(state =>
{
((ConsoleLifetime)state).OnApplicationStarted();
},
this);
// Register the callbacks for ApplicationStopping
_applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(state =>
{
((ConsoleLifetime)state).OnApplicationStopping();
},
this);
}
// ...
return Task.CompletedTask;
}
These callbacks run the OnApplicationStarted()
and OnApplicationStopping()
methods (shown below) which simply write to the logging infrastructure:
private void OnApplicationStarted()
{
Logger.LogInformation("Application started. Press Ctrl+C to shut down.");
Logger.LogInformation("Hosting environment: {envName}", Environment.EnvironmentName);
Logger.LogInformation("Content root path: {contentRoot}", Environment.ContentRootPath);
}
private void OnApplicationStopping()
{
Logger.LogInformation("Application is shutting down...");
}
The SystemdLifetime
and WindowsServiceLifetime
implementations use the same approach to write log files using the standard logging infrastructure, though the exact messages vary slightly.
Suppressing the startup messages with the ConsoleLifetime
One slightly surprising change caused by the startup messages been created by the ConsoleLifetime
, is that you can no longer suppress the messages in the ways I described in my previous post. Setting ASPNETCORE_SUPPRESSSTATUSMESSAGES
apparently has no effect - the messages will continue to be logged whether the environment variable is set or not!
As I've already pointed out, this isn't really a big issue now, seeing as the messages are logged properly using the Microsoft.Extensions.Logging infrastructure. But if those messages offend you for some reason, and you really want to get rid of them, you can configure the ConsoleLifetimeOptions
in Startup.cs:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// ... other configuration
services.Configure<ConsoleLifetimeOptions>(opts => opts.SuppressStatusMessages = true);
}
}
You could even set the SuppressStatusMessages
field based on the presence of the ASPNETCORE_SUPPRESSSTATUSMESSAGES
environment variable if you want:
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) => Configuration = configuration;
public void ConfigureServices(IServiceCollection services)
{
// ... other configuration
services.Configure<ConsoleLifetimeOptions>(opts
=> opts.SuppressStatusMessages = Configuration["SuppressStatusMessages"] != null);
}
}
If you do chose to suppress the messages, note that Kestrel will still log the URLs it's listening on; there's no way to suppress those:
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://0.0.0.0:5000
info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://0.0.0.0:5001
Summary
In this post I showed how the annoyingly-unstructured logs that were written on app startup in ASP.NET Core 2.x apps are now written using structured logging in 3.0. This ensures the logs are written to all your configured loggers, as well as having a standard format when written to the console. I described the role of IHostLifetime
in the log messages, and showed how you could configure the ConsoleLifetimeOptions
to suppress the status messages if you wish.