You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义日志配置被最后配置条目覆盖问题排查

自定义日志配置被覆盖问题排查与解决

问题现象

实现了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 23:27:45