MS Ext Logging: establishing scope in GlobalRequestFilter(Async) have no effects

I’m using Microsoft.Extensions.Logging, leveraging structured logging. I would like to establish logging scope for all the requests handled by ServiceStack.

The issue being, the scope established on GlobalRequestFilters(Async) seems to be not preserved when the service is executed. On the other hand, scopes established in a Middleware (that is registered before ServiceStack) are preserved.

Simple repro steps:

  1. Start with an empty ASP.NET 6 template. Configure logging so that scopes are included in console output
  2. Add an ASP.NET Middleware that establishes the logging scope
  3. Add a code that also establishes the logging scope in GlobalRequestFilters & GlobalRequestFiltersAsync
  4. In the Service that handles request, write some log message
  5. Only the scope from Middleware is present in the log entry.

Repro code & result:

using ServiceStack;

var builder = WebApplication.CreateBuilder(args);
// include scopes in console output
builder.Logging.AddSimpleConsole(x => x.IncludeScopes = true);

builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<LogScopeMiddleware>();

var app = builder.Build();
var appHost = new AppHost(app.Services.Resolve<ILogger<AppHost>>());

// middleware that establishes the scope with 'ScopeFromMiddleware' prop
app.UseMiddleware<LogScopeMiddleware>();
app.UseServiceStack(appHost);

app.Run();


class AppHost : AppHostBase
{
    private readonly ILogger<AppHost> _logger;
    public AppHost(ILogger<AppHost> logger): base("TestApp", typeof(AppHost).Assembly)
    {
        _logger = logger;
    }

    public override void Configure(Funq.Container container)
    {
        // establishes the scopes with ScopeFromRequestFilter/Async
GlobalRequestFilters.Add((request, response, dto) => request.Resolve<ILogger<AppHost>>().BeginScope(new {ScopeFromRequestFilter = "hey!"}));
        GlobalRequestFiltersAsync.Add(async (request, response, dto) => _logger.BeginScope(new { ScopeFromRequestFilterAsync = "hey!" }));
    }
}

public class LogScopeMiddleware : IMiddleware
{
    readonly ILogger<LogScopeMiddleware> _logger;

    public LogScopeMiddleware(ILogger<LogScopeMiddleware> logger)
    {
        _logger = logger;
    }

    public Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        _logger.BeginScope(new { ScopeFromMiddleware = "I'm here" });
        return next(context);
    }
}

[Route("/mydto", "GET")]
public class MyDto{}

public class MyService : Service
{
    public ILogger<MyService> Logger { get; set; }

    public string Get(MyDto dto)
    {
        Logger.LogInformation("Service here. Check scopes!");
        return "ok";
    }
}

the output:

info: MyService[0]
      => SpanId:6f02816c7c849124, TraceId:ccee2ca7155a5c8b4e65bdd6dd9fe411, ParentId:0000000000000000 => ConnectionId:0HMT8E1BE2PVF => RequestPath:/mydto RequestId:0HMT8E1BE2PVF:00000003 => { ScopeFromMiddleware = I'm here }
      Service here. Check scopes!

Only ScopeFromMiddleware is present; both scopes established by Global Filters are missing …

You’re describing the current behavior, dependencies resolved from ServiceStack’s IOC resolve Application Service dependencies in ASP .NET Core IOC.

To resolve Request Scoped dependencies you can use Request.TryResolve<T>() from within your Service.

I might be missing the point, but …

First, the GlobalRequestFilter in the repro code does call request.Resolve<T> so it should resolve logger from the ASP.NET container and not Funq? I just checked and Resolve calls TryResolve underneath.

Secondly, Microsoft Logger instances are registered as singletons by default. As far as I know , the scope relies on the AsyncLocal that is bound to an instance of LoggerFactory (and that is also a singleton in IoC of course).
So, as long as you use the same LoggerFactory, the scope should be preserved, no matter whether you used the same Logger instance or not, and that’s the beauty of it - I can enrich any log messages from 3rd party lib as long as it uses “my” instance of LoggerFactory

and besides - but it doesn’t really matter here - I thought with HttpContextAccessor registred, the request-scoped dependencies would not become singletons in Funq? ServiceStack's IOC

HttpContextAccessor changes the NetCoreContainerAdapter to resolve from the HttpContext.RequestServices if it’s available. This just affects dependencies in ASP .NET Core’s IOC, not dependencies registered in ServiceStack’s Funq IOC.

If you resolve from IRequest.TryResolve<T> it will resolve from HttpContext.RequestServices.GetService<T>() which resolves from the Http Request Scope.

Don’t use IResolver.Resolve<T>() to resolve RequestServices dependencies which is an extension method on the base IResolver which resolves from AppHost’s IOC. But I can see how this isn’t expected so I’ve updated the IResolver.Resolve<T>() extension method to detect if the target is an IRequest and to use its implementation instead in this commit.

This change is available from the latest v6.10.1+ that’s now available on Pre Release Packages.