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.serviceProviderfield 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.BuildServiceProvidermultiple 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
IHttpContextAccessorclass, as it allows accessing the currentHttpContext:This can be wired up as follows: