Setting logging scope globally in ASP.Net Core
Asked Answered
D

1

13

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
Dogmatize answered 13/1, 2020 at 16:25 Comment(11)
"but the logging doesn't appear on the "Microsoft.AspNetCore.Hosting.Diagnostics" log messages" -- Isn't that covered from the first bit of code?Birdt
@chrispratt not in my testing sadly.Dogmatize
logging doesn't appear on the "Microsoft.AspNetCore.Hosting.Diagnostics" log messages Do you mean when the log is produced by Microsoft.AspNetCore.Hosting.Diagnostics, the message defined in BeginScope did not shows up? Can you paste the logs contain the good case and bad case?Terylene
@Terylene Please see my edit, containing the log message where it is missing.Dogmatize
Could you share what is the version of asp.net core?Terylene
@Terylene ASP.Net Core 3.0.Dogmatize
Microservice.GetBaseLoggingFields(Constants.Application.ServiceName) What's the Microservice.GetBaseLoggingFields method here? Is it customer own method? and what's Constants.Application.ServiceName? is it also custom defined?Terylene
@Terylene Those are just a copy-paste from the real application. I've removed them now, as the above is intended to be an MVCE.Dogmatize
@Dogmatize did you figure out a way of doing global scope, I am also looking for options of doing something similarDeepset
Did you try changing logging to Trace? You can also get those values manually from your context request object in .NET and manually log the values you need that way. I would roll your own log class to capture those on every request and log them as information log levels.Ratter
If someone uses serilog, consider to use LogContext as described in #57461079Nabonidus
M
3

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.

Mesarch answered 20/11, 2023 at 22:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.