In my custom ConsoleFormatter
I need to get a value from a service added to the DI. This value is saved in a SCOPED service because it generates a new value for every new request but should keep the value through the request.
The problem is that I don't know how to access a DI service in the ConsoleFormatter
, what I've tried below is to put the services container into the options parameter. My thoughts here is that .NET Services is a singleton, and by this I can request my service at the logging time and get my scoped service.
public static class ConsoleLoggerExtensions
{
public static ILoggingBuilder AddCustomConsoleFormatter(
this ILoggingBuilder builder, Action<CustomConsoleFormatterOptions> options)
{
return builder.AddConsole(
options => options.FormatterName = nameof(CustomLoggingFormatter))
.AddConsoleFormatter<
CustomLoggingFormatter, CustomConsoleFormatterOptions>(
options);
}
}
public sealed class CustomConsoleFormatterOptions : ConsoleFormatterOptions
{
public IServiceProvider serviceProvider { get; set; }
}
public sealed class CustomLoggingFormatter : ConsoleFormatter, IDisposable
{
static long RowIndex = 1;
readonly IDisposable _optionsReloadToken;
CustomConsoleFormatterOptions _formatterOptions;
public CustomLoggingFormatter(
IOptionsMonitor<CustomConsoleFormatterOptions> options)
: base(nameof(SebLoggingFormatter))
{
(_optionsReloadToken, _formatterOptions) =
(options.OnChange(ReloadLoggerOptions), options.CurrentValue);
}
public override void Write<TState>(in LogEntry<TState> logEntry,
IExternalScopeProvider scopeProvider, TextWriter textWriter)
{
string message =
logEntry.Formatter?.Invoke(logEntry.State, logEntry.Exception);
if (message is null)
return;
var idService =
_formatterOptions.serviceProvider.GetService<IIdService>();
var requestId = idService.RequestId;
}
void ReloadLoggerOptions(CustomConsoleFormatterOptions options) =>
_formatterOptions = options;
public void Dispose() =>
_optionsReloadToken?.Dispose();
}
And in my startup I add a Task to get the CustomFormnaytterOptions
services.AddLogging(opt => opt.AddCustomConsoleFormatter(options =>
{
var serviceProvider = services.BuildServiceProvider();
options.serviceProvider = serviceProvider;
}));
But when I request the IdService from the services I get back a service with an empty Value, and for me that might be coz:
- No http-context (IdService is picking or creating the Id by the headers in the current request)
- The .net Services I use here is something old from the time at startup (=not a singleton) and therefor not related to the current request
One thing I don't understand is this Option-coding-pattern that (ConsoleFormatterOptions uses) in .net, seems to be a built in feature. Should I in some way try to invalidate the options so it re-runs the task from startup?
There are several problems with your solution that prevent it from working:
CustomConsoleFormatterOptions.serviceProvider
field toservices.BuildServiceProvider()
will cause your formatter it to get a copy of all DI registrations. This won't give you access to the requestsIIdService
, and won't get you access to the active scope or HTTP request.BuildServiceProvider
multiple times is a problematic practice, which is why there is a code analyzer in .NET Core that will warn you about this.IServiceProvider
, it will be the 'root' service provider, and won't give you access to scoped services.The solution is to inject the
IHttpContextAccessor
class, as it allows accessing the currentHttpContext
:This can be wired up as follows: