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 ) { _applicationStartedRegistration = ApplicationLifetime . ApplicationStarted . Register ( state = > { ( ( ConsoleLifetime ) state ) . OnApplicationStarted ( ) ; } , this ) ; _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 ) { 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 ) { 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