Using Shared Logging Levels with .NET Aspire

Overview

I recently was consolidating quite a few pieces of a microservices application into a single Aspire solution (which I highly recommend, by the way, and if you need guidance for sure check out the official docs but I also have a Pluralsight course that can help).

While doing the setup, I came up with a technique to define some shared logging levels that can be applied to your ASP.NET Core projects that I found pretty helpful and wanted to share it here in this short post.

Just Show Me the Code!

A sample of code that shows the approach in this article is in the GitHub repo tied to my Pluralsight course -- it uses the Serilog-based approach for environment variables.

Logging Levels in ASP.NET

For ASP.NET applications, you often configure log levels for applications with values in an appsettings.json file with content that might look like this:

1"Logging": {
2  "LogLevel": {
3    "Default": "Information",
4    "System": "Information",
5    "Microsoft": "Warning",
6    "Microsoft.Hosting": "Information",
7    "Duende": "Warning"
8  }
9}
What About Serilog?

In Serilog, the same settings are possible but the appsettings.json syntax is a little different:

 1"Serilog": {
 2  "MinimumLevel": {
 3    "Default": "Information",
 4    "Override": {
 5      "System": "Warning",
 6      "Microsoft": "Warning",
 7      "Microsoft.Hosting": "Information",
 8      "Duende": "Warning"
 9    }
10  }
11}

This configuration exists for each of your ASP.NET applications - and if you have a microservices-based one like I did, this might be 10 or more different applications.

If you need to use different log levels, like more Debug levels, you would end up needing to change content in many different appsettings.json files.

AppHost Modification in .NET Aspire

Two .NET features come to our assistance here:

  • the chained / override feature where environment variables override appsettings files
  • the ability to easily set environment variables for ASP.NET projects in the Aspire AppHost

You can set an environment variable on a Project in the AppHost with code such as this:

1var api = builder.AddProject<Projects.CatalogApi>("catalog")
2    .WithEnvironment("SomeEnvVar", "some value");

The environment-variable based name for the logging level setup from above for the Default log level would be:

Logging__LogLevel__Default

Note the double-underscores that separate the levels of the json tree.

The Code - An Extension Method and Single-Line AppHost Updates Per Project

Based on the above, we can define an extension method that looks like this - and I've got this defined in the AppHost project:

 1internal static class LoggingHelper
 2{
 3    internal static IResourceBuilder<T> WithSharedLoggingLevels<T>(this IResourceBuilder<T> builder) 
 4        where T : IResourceWithEnvironment
 5    {
 6        var dict = new Dictionary<string, string>
 7        {
 8            { "Default", "Information" },
 9            { "System", "Warning" },
10            { "Microsoft", "Warning" },
11            { "Microsoft.Hosting", "Information" },
12            { "Duende": "Warning" }
13        };
14
15        foreach (var item in dict.Keys)
16        {
17            builder = builder.WithEnvironment($"Logging__LogLevel__{item}", dict[item]);
18        }
19        return builder;
20    }
21}

And then on any project we add to the AppHost, we can simply invoke the method:

1var api = builder.AddProject<Projects.CatalogApi>("catalog")
2    .WithSharedLoggingLevels();

EF Core -- See Queries in Logs

If you add the following settings, you can see the queries that you're executing in the log entries:

1{ "Microsoft.EntityFrameworkCore.Database.Command", "Information" },
2{ "Microsoft.EntityFrameworkCore.Query", "Information" },
3{ "Microsoft.EntityFrameworkCore.Update", "Information" },

These will enable you to see the queries that EF Core executes the database even if the trace information does not include it.

Implications & Limitations

Implemented Approach: Only for Local Development

Because I implemented the extension method in the AppHost project, it only applies to local development. If you wanted this kind of behavior more generally in any deployed environment, you could add a similar (but not the same) method in the ServiceDefaults project and reference shared configuration in some other way -- shared file, hard-coded values, or whatever (even a database!).

Overriding an Individual ASP.NET Project

The implemented approach here will use the shared logging level in the AppHost project instead of any configured values in the local appsettings.json file for the projects due to the fact that environment variables take a higher priority in the default configuration hierarchy.

The easiest way to set a custom level of logging for an individual ASP.NET project in your Aspire solution is to simply comment out the WithSharedLoggingLevels() call for that project, and then update the appsettings.json file for the project with whatever settings you need.