自定义日志配置被最后配置条目覆盖问题排查
问题现象
实现了Logger1和Logger2两个独立的自定义日志组件,各自拥有专属配置段、ILoggerProvider及ILogger实现。但在DI容器中添加到ILoggingBuilder后,出现配置覆盖问题:最后添加的Logger2配置会覆盖Logger1的配置,导致Logger1的日志级别读取错误(无效值默认转为Trace级别)。注释Logger2扩展中的AddConfiguration代码后,Logger1可正常工作。
相关代码片段
Appsettings配置
{ "Logging": { "LogLevel": { "Default": "Warning" }, "Logger1": { "LogLevel": { "Default": "Information" }, "BenchmarkConfig": { "Enable": true, "LogThreshold": { "Critical": "00:00:15", "Information": "00:00:00" } } }, "Logger2": { "LogLevel": { "Default": "Critical" } } } }
DI注入代码
protected override void LoggingHook(IServiceCollection services, IConfiguration configuration) { base.LoggingHook(services, configuration); _ = services.AddLogging(config => _ = config.AddLogger1(configuration)); _ = services.AddLogging(config => _ = config.AddLogger2(configuration)); }
Logger1实现代码
public static class Logger1Extensions { public static ILoggingBuilder AddLogger1(this ILoggingBuilder builder, IConfiguration configuration) { var configurationSection = configuration.GetSection(Logger1.Section); _ = builder.AddConfiguration(configurationSection); builder.Services.AddTransient<InitializedLogMessage>(); builder.Services.AddSingleton<ILogger1Config>(configurationSection.Get<Logger1Config>()); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, Logger1Provider>()); return builder; } } public interface ILogger1Config { LogLevel LogLevel { get; set; } Logger1BenchmarkConfig BenchmarkConfig { get; set; } } public class Logger1 : ILogger { private readonly ILambdaLogger _lambdaLogger; private readonly ILogger1Config _loggerConfig; public const string Section = "Logging:Logger1"; public const string SectionLogLevel = Section + ":LogLevel"; public const string SectionLogLevelDefault = SectionLogLevel + ":Default"; public Logger1(ILambdaLogger logger, ILogger1Config loggerConfig, InitializedLogMessage logMessage) { _lambdaLogger = logger; _loggerConfig = loggerConfig; LogMessage = logMessage; } private InitializedLogMessage LogMessage { get; } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { if (IsEnabled(logLevel)) { _lambdaLogger?.LogLine($"{GetScopePrefix()}[{logLevel}] {formatter(state, exception)}"); } } public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; }
Logger2实现代码
public static class Logger2Extensions { public static ILoggingBuilder AddLogger2Generic<T>(this ILoggingBuilder builder, IConfiguration configuration) where T : class, ILoggerProvider { IConfigurationSection section = configuration.GetSection("Logging:Logger2"); builder.AddConfiguration((IConfiguration) section); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, T>()); return builder; } public static ILoggingBuilder AddLogger2(this ILoggingBuilder builder, IConfiguration configuration) { return builder.AddLogger2Generic<Logger2Provider>(configuration); } } public class Logger2 : ILogger { private IRest _rest { get; set; } public Logger2(IRest rest) { _rest = rest; } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { if (IsEnabled(logLevel)) { _rest.log(); } } public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; }
问题原因
ILoggingBuilder.AddConfiguration方法是将配置段合并到全局日志配置体系中,而非为单个LoggerProvider隔离配置。当两次调用该方法时,后续配置段中的同名节点(如LogLevel:Default)会覆盖之前的配置,导致全局日志配置被最后添加的Logger配置覆盖。
另外,自定义Logger若依赖全局日志配置判断级别,会读取到被覆盖后的无效值,触发LogLevel枚举的默认转换逻辑(无效值转为Trace级别)。
解决方案
核心思路是让每个自定义LoggerProvider读取专属配置段,不依赖全局日志配置的合并逻辑,具体步骤如下:
1. 移除全局配置合并代码
删除两个Logger扩展中的builder.AddConfiguration调用,避免自定义配置污染全局日志配置:
修改Logger1Extensions
public static class Logger1Extensions { public static ILoggingBuilder AddLogger1(this ILoggingBuilder builder, IConfiguration configuration) { var configurationSection = configuration.GetSection(Logger1.Section); // 移除全局配置合并 // _ = builder.AddConfiguration(configurationSection); builder.Services.AddTransient<InitializedLogMessage>(); builder.Services.AddSingleton<ILogger1Config>(configurationSection.Get<Logger1Config>()); // 注入专属配置段给Provider builder.Services.AddSingleton(configurationSection); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, Logger1Provider>()); return builder; } }
修改Logger2Extensions
public static class Logger2Extensions { public static ILoggingBuilder AddLogger2Generic<T>(this ILoggingBuilder builder, IConfiguration configuration) where T : class, ILoggerProvider { IConfigurationSection section = configuration.GetSection("Logging:Logger2"); // 移除全局配置合并 // builder.AddConfiguration((IConfiguration) section); // 注入专属配置段给Provider builder.Services.AddSingleton(section); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, T>()); return builder; } public static ILoggingBuilder AddLogger2(this ILoggingBuilder builder, IConfiguration configuration) { return builder.AddLogger2Generic<Logger2Provider>(configuration); } }
2. 让LoggerProvider读取专属LogLevel
为每个LoggerProvider添加专属配置读取逻辑,维护独立的LogLevel映射:
Logger1Provider实现
public class Logger1Provider : ILoggerProvider { private readonly IConfigurationSection _configSection; private readonly IServiceProvider _serviceProvider; private readonly Dictionary<string, LogLevel> _logLevels; public Logger1Provider(IConfigurationSection configSection, IServiceProvider serviceProvider) { _configSection = configSection; _serviceProvider = serviceProvider; // 读取专属LogLevel配置 _logLevels = _configSection.GetSection("LogLevel").Get<Dictionary<string, LogLevel>>() ?? new Dictionary<string, LogLevel>(); } public ILogger CreateLogger(string categoryName) { // 匹配类别对应的LogLevel,无匹配则用Default var targetLevel = _logLevels.TryGetValue(categoryName, out var level) ? level : _logLevels.GetValueOrDefault("Default", LogLevel.Information); var lambdaLogger = _serviceProvider.GetRequiredService<ILambdaLogger>(); var loggerConfig = _serviceProvider.GetRequiredService<ILogger1Config>(); var logMessage = _serviceProvider.GetRequiredService<InitializedLogMessage>(); return new Logger1(lambdaLogger, loggerConfig, logMessage) { CurrentLogLevel = targetLevel }; } public void Dispose() { } }
Logger2Provider实现
public class Logger2Provider : ILoggerProvider { private readonly IConfigurationSection _configSection; private readonly IServiceProvider _serviceProvider; private readonly Dictionary<string, LogLevel> _logLevels; public Logger2Provider(IConfigurationSection configSection, IServiceProvider serviceProvider) { _configSection = configSection; _serviceProvider = serviceProvider; // 读取专属LogLevel配置 _logLevels = _configSection.GetSection("LogLevel").Get<Dictionary<string, LogLevel>>() ?? new Dictionary<string, LogLevel>(); } public ILogger CreateLogger(string categoryName) { // 匹配类别对应的LogLevel,无匹配则用Default var targetLevel = _logLevels.TryGetValue(categoryName, out var level) ? level : _logLevels.GetValueOrDefault("Default", LogLevel.Critical); var rest = _serviceProvider.GetRequiredService<IRest>(); return new Logger2(rest) { CurrentLogLevel = targetLevel }; } public void Dispose() { } }
3. 修改Logger的级别判断逻辑
更新两个Logger的IsEnabled方法,使用专属的LogLevel判断:
修改Logger1
public class Logger1 : ILogger { private readonly ILambdaLogger _lambdaLogger; private readonly ILogger1Config _loggerConfig; // 添加专属级别属性 public LogLevel CurrentLogLevel { get; set; } = LogLevel.Information; public const string Section = "Logging:Logger1"; public const string SectionLogLevel = Section + ":LogLevel"; public const string SectionLogLevelDefault = SectionLogLevel + ":Default"; public Logger1(ILambdaLogger logger, ILogger1Config loggerConfig, InitializedLogMessage logMessage) { _lambdaLogger = logger; _loggerConfig = loggerConfig; LogMessage = logMessage; } private InitializedLogMessage LogMessage { get; } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { if (IsEnabled(logLevel)) { _lambdaLogger?.LogLine($"{GetScopePrefix()}[{logLevel}] {formatter(state, exception)}"); } } public bool IsEnabled(LogLevel logLevel) { return logLevel != LogLevel.None && logLevel >= CurrentLogLevel; } }
修改Logger2
public class Logger2 : ILogger { private IRest _rest { get; set; } // 添加专属级别属性 public LogLevel CurrentLogLevel { get; set; } = LogLevel.Critical; public Logger2(IRest rest) { _rest = rest; } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { if (IsEnabled(logLevel)) { _rest.log(); } } public bool IsEnabled(LogLevel logLevel) { return logLevel != LogLevel.None && logLevel >= CurrentLogLevel; } }
4. 简化DI注入代码
将两次AddLogging调用合并为一次,避免重复构建LoggingBuilder:
protected override void LoggingHook(IServiceCollection services, IConfiguration configuration) { base.LoggingHook(services, configuration); _ = services.AddLogging(config => { config.AddLogger1(configuration); config.AddLogger2(configuration); }); }
内容的提问来源于stack exchange,提问作者Gumdro

