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:
- Start with an empty ASP.NET 6 template. Configure logging so that scopes are included in console output
- Add an ASP.NET Middleware that establishes the logging scope
- Add a code that also establishes the logging scope in GlobalRequestFilters & GlobalRequestFiltersAsync
- In the Service that handles request, write some log message
- 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 …