RB.
RB.

Reputation: 37172

Setting logging scope globally in ASP.Net Core

I want to get certain key bits of information (service-name, service-version, host-name, etc) logged on every log message in an ASP.Net Core service.

I have the following code:

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (host.Services.GetRequiredService<ILogger<Program>>().BeginScope("ServiceName: {0}", "bob-service"))
        {
            host.Run();
        }
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(builder =>
            {
                builder.AddConsole(o => o.IncludeScopes = true);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

From this I get the following logging:

info: Microsoft.Hosting.Lifetime[0]
      => ServiceName: bob-service
      Now listening on: http://localhost:5002

info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[102]
      => ConnectionId:0HLSO8706NGBN => RequestPath:/ RequestId:0HLSO8706NGBN:00000001, SpanId:|aa5794ba-408ad22a63064421., TraceId:aa5794ba-408ad22a63064421, ParentId: => Middleware_Scope => /Index
      Executed handler method OnGet, returned result .

Note how the ServiceName only appears on the host-level logging. The request-level logging does not include it.

Is there a mechanism for setting scope on every log-message that comes out of ASP.Net Core.

I've tried adding a piece of middleware to do this, and it mostly works, but the logging doesn't appear on the "Microsoft.AspNetCore.Hosting.Diagnostics" log messages (although it appears on everything else as far as I can tell).

app
    .Use(async (context, next) =>
    {
        // Middleware to add scoped values to log messages.
        var logger = context.RequestServices.GetRequiredService<ILogger<Startup>>();
        using (logger.BeginScope("ServiceName: {0}", "bob-service")))
        {
            await next();
        }
    });

EDIT

Even when using the middleware described above, log messages from the Microsoft.AspNetCore.Hosting.Diagnostics logger do not contain the scoped fields. For example, note how in the second log message, the Middleware_Scope scoped message does not appear.

info: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[6]
      => ConnectionId:0HLSOTDVELPJA => RequestPath:/css/site.css RequestId:0HLSOTDVELPJA:00000001, SpanId:|2fbd556d-44cc7c919266ccaf., TraceId:2fbd556d-44cc7c919266ccaf, ParentId: => Middleware_Scope
      The file /css/site.css was not modified

info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      => ConnectionId:0HLSOTDVELPJD => RequestPath:/js/site.js RequestId:0HLSOTDVELPJD:00000001, SpanId:|2fbd5570-44cc7c919266ccaf., TraceId:2fbd5570-44cc7c919266ccaf, ParentId:
      Request finished in 29.8413ms 304 application/javascript

Upvotes: 13

Views: 6478

Answers (1)

alexbrina
alexbrina

Reputation: 191

I don't know if it is right, but I came to set scopes globally using ILoggerProvider instead of ILogger<T>, and setting my own scope provider for it, something like this:

    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();
        
        // ... other logging configurations, like ClearProviders() and AddJsonConsole(o => o.IncludeScopes = true);

        var provider = host.Services.GetService<ILoggerProvider>()
        if (provider is ISupportExternalScope scopedProvider)
        {
            var scopes = new LoggerExternalScopeProvider();
            scopes.Push("ServiceName: bob-service");
            scopedProvider.SetScopeProvider(scopes);
        }

        host.Run();
    }

You may have to use GetServices<ILoggerProvider> and take care of a list if you have more than one provider.

Upvotes: 3

Related Questions