在我的自定义中
ConsoleFormatter
我需要从添加到 DI 的服务中获取值。该值保存在 SCOPED 服务中,因为它会为每个新请求生成一个新值,但应在整个请求中保留该值。
问题是我不知道如何访问
ConsoleFormatter
中的DI服务,我下面尝试的是将服务容器放入选项参数中。我的想法是,.NET 服务是一个单例,这样我就可以在日志记录时请求我的服务并获得我的作用域服务。
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();
}
在我的启动中,我添加一个任务来获取 CustomFormnaytterOptions
services.AddLogging(opt => opt.AddCustomConsoleFormatter(options =>
{
var serviceProvider = services.BuildServiceProvider();
options.serviceProvider = serviceProvider;
}));
但是当我从服务请求 IdService 时,我得到一个带有空值的服务,对我来说这可能是因为:
我不明白的一件事是.net 中的选项编码模式(ConsoleFormatterOptions 使用)似乎是内置功能。我是否应该以某种方式尝试使选项无效,以便它从启动时重新运行任务?
您的解决方案存在几个问题,导致其无法正常工作:
CustomConsoleFormatterOptions.serviceProvider
字段设置为 services.BuildServiceProvider()
将导致您的格式化程序获取所有 DI 注册的副本。这不会让您访问请求 IIdService
,也不会让您访问活动范围或 HTTP 请求。BuildServiceProvider
是一种有问题的做法,这就是为什么.NET Core中有一个代码分析器会警告您这一点。IServiceProvider
,它将成为“根”服务提供商,并且不会让您访问范围内的服务。解决方案是注入
IHttpContextAccessor
类,因为它允许访问当前的 HttpContext
:
public sealed class CustomLoggingFormatter : ConsoleFormatter
{
private readonly IHttpContextAccessor accessor;
public CustomLoggingFormatter(IHttpContextAccessor accessor)
: base(nameof(SebLoggingFormatter))
{
this.accessor = accessor;
}
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;
// Pull scoped service from request
var idService =
this.accessor.HttpContext.RequestServices.GetService<IIdService>();
var requestId = idService.RequestId;
}
}
可以按如下方式接线:
builder.AddHttpAccessor();
builder.AddConsole(
options => options.FormatterName = nameof(CustomLoggingFormatter))
.AddConsoleFormatter<
CustomLoggingFormatter, CustomConsoleFormatterOptions>();