Blazor Server中ILoggerProvider配置注入异常求助
我遇到了一个诡异的问题:自定义的BlobLoggerProvider接收的BlobLoggerOptions所有属性均为null,但另一个FileLoggerProvider却能正常读取配置。更奇怪的是,如果把BlobLoggerProvider的参数换成FileLoggerOptions,哪怕它带有[ProviderAlias("Blob")]特性,居然会读取到appSettings.json里"File"节点的配置。
以下是相关代码:
临时硬编码的BlobLoggerProvider构造函数(为保证运行)
public BlobLoggerProvider(BlobLoggerOptions options) { Options = options; // 传入的options所有属性都是null,临时硬编码赋值 Options = new BlobLoggerOptions { Path = "log/{yyyy-MM-dd}.txt", Interval = "day", AzureStorage = "UseDevelopmentStorage=true" }; Interval = GetInterval(Options.Interval); Start(); }
正常运行的FileLoggerProvider
[ProviderAlias("File")] public class FileLoggerProvider : LoggerProviderBase { protected FileLoggerOptions? Options; public FileLoggerProvider(IOptionsMonitor<FileLoggerOptions> options) : this(options.CurrentValue) { SettingsChangeToken = options.OnChange(opt => { Options = opt; }); } public FileLoggerProvider(FileLoggerOptions options) { Options = options; Interval = GetInterval(options.Interval); Start(); } }
异常的BlobLoggerProvider
[ProviderAlias("Blob")] public class BlobLoggerProvider : LoggerProviderBase { protected BlobLoggerOptions Options; public BlobLoggerProvider(IOptionsMonitor<BlobLoggerOptions> options) : this(options.CurrentValue) { SettingsChangeToken = options.OnChange(opt => { Options = opt; }); } public BlobLoggerProvider(BlobLoggerOptions options) { Options = options; Start(); } }
appSettings.json配置节点
"Blob": { "Path": "logs/{yyyy-MM-dd}.txt", "Interval": "Day", "AzureStorage": "UseDevelopmentStorage=true" }, "File": { "Path": "C:/temp/{yyyy-MM-dd}.txt", "Interval": "day" },
配置类与Setup实现
public class FileLoggerOptions { public string? Path { get; set; } public string? Interval { get; set; } } internal class FileLoggerOptionsSetup : ConfigureFromConfigurationOptions<FileLoggerOptions> { public FileLoggerOptionsSetup(ILoggerProviderConfiguration<FileLoggerProvider> providerConfiguration) : base(providerConfiguration.Configuration) { } } public class BlobLoggerOptions { public string? Path { get; set; } public string? Interval { get; set; } public string? AzureStorage { get; set; } } internal class BlobLoggerOptionsSetup : ConfigureFromConfigurationOptions<BlobLoggerOptions> { // 此处为问题核心 public BlobLoggerOptionsSetup(ILoggerProviderConfiguration<BlobLoggerOptions> providerConfiguration) : base(providerConfiguration.Configuration) { } }
服务注册代码
public static class LoggingBuilderExtensions { public static ILoggingBuilder AddFileLogger(this ILoggingBuilder builder) { builder.AddConfiguration(); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, FileLoggerProvider>()); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<FileLoggerOptions>, FileLoggerOptionsSetup>()); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<FileLoggerOptions>, LoggerProviderOptionsChangeTokenSource<FileLoggerOptions, FileLoggerProvider>>()); return builder; } public static ILoggingBuilder AddAzureBlobLogger(this ILoggingBuilder builder) { builder.AddConfiguration(); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, BlobLoggerProvider>()); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<BlobLoggerOptions>, BlobLoggerOptionsSetup>()); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<BlobLoggerOptions>, LoggerProviderOptionsChangeTokenSource<BlobLoggerOptions, BlobLoggerProvider>>()); return builder; } }
问题原因与修复方案
核心问题
BlobLoggerOptionsSetup的构造函数泛型参数使用错误:ILoggerProviderConfiguration<T>中的T必须是LoggerProvider类型,而非Options类型。该接口的作用是通过LoggerProvider上的[ProviderAlias]特性匹配appSettings中的配置节点,你错误地传入了Options类型,导致框架无法找到对应的配置节点,返回null。
对比正常的FileLoggerOptionsSetup,它使用ILoggerProviderConfiguration<FileLoggerProvider>,才能正确匹配"File"节点的配置。
修复步骤
修改BlobLoggerOptionsSetup的构造函数,将泛型参数从BlobLoggerOptions改为BlobLoggerProvider:
internal class BlobLoggerOptionsSetup : ConfigureFromConfigurationOptions<BlobLoggerOptions> { // 替换泛型参数为BlobLoggerProvider public BlobLoggerOptionsSetup(ILoggerProviderConfiguration<BlobLoggerProvider> providerConfiguration) : base(providerConfiguration.Configuration) { } }
修改后,框架会通过BlobLoggerProvider上的[ProviderAlias("Blob")]特性,正确匹配appSettings中的"Blob"节点,BlobLoggerOptions就能正常绑定配置值。
额外说明
为什么换成FileLoggerOptions能读取到"File"的配置?因为FileLoggerOptionsSetup的构造参数是ILoggerProviderConfiguration<FileLoggerProvider>,会固定匹配"File"节点,和注册时关联的LoggerProvider无关,才会出现这种交叉读取的诡异情况。
内容的提问来源于stack exchange,提问作者David Thielen

