一、为什么要把Logger和Configuration放在一起讲?
Microsoft.Extensions.Logging 和 Microsoft.Extensions.Configuration 两个命名空间的源码,你会发现一个惊人的事实:- Logger
走的是 Factory → Provider → Worker 套路 - Configuration
走的是 Builder → Source → Provider 套路
两者核心设计思想完全同源——编排者收集来源 → 来源生产执行者 → 执行者真正干活 → 门面聚合统一对外。
理解了其中一套,另一套就是换皮。
但它们的执行细节差异很大:
【核心结论】Logger 的多 Provider 是"并行广播"——每条日志分发给所有 Provider;Configuration 的多 Provider 是"优先级覆盖"——后注册的覆盖先注册的。
这个差异是本文的核心对比点。
🔹二、ILogger 日志组件:源码全链路
2.1 上帝视角:日志系统的六个关键动作
对应 PPT 专题5的 6 个要点,日志系统从注册到输出完整链路如下:
① AddLogging() → DI 注册 LoggerFactory(单例) + ILogger<T>(单例)
② AddConsole() → 向 ILoggingBuilder 添加 ILoggerProvider
③ CreateLogger() → 缓存 + 组装聚合 Logger
④ Log() → 聚合 Logger 遍历所有子 Logger 输出
⑤ LogInformation() → 扩展方法,本质调用 Logger.Log()
⑥ Filter → Options 配置 → RefreshFilters() → 级别过滤
2.2 AddLogging():DI 容器注册了什么?
入口在 LoggingServiceCollectionExtensions.AddLogging(),主机 BuildCommon() 默认调用。

逐行解读:
💡知识点:ILogger<T> 是单例,同一个 T 在整个应用生命周期内拿到的是同一个 Logger 实例。Logger 内部只持有 Provider 引用数组,本身无状态,单例安全。
LoggingBuilder 的实现极其简单——它只是 IServiceCollection 的包装器:
AddConsole()、AddDebug() 都是往 Services 里注册 ILoggerProvider。2.3 LoggerFactory.CreateLogger():缓存 + 组装 Provider
这是日志系统最核心的方法。LoggerFactory 是单例,每次 CreateLogger(categoryName) 都走缓存逻辑。

三个关键设计点:
1. 双检锁缓存:lock(_sync) + Dictionary.TryGetValue,保证线程安全且高性能。同一个 categoryName(通常是类全名)永远返回同一个 Logger 实例。
2. 异常隔离:ShouldCatchProviderExceptions 控制是否捕获 Provider 创建异常。默认 true——一个 Provider 挂了,不影响其他 Provider。
3. Filter 应用:ApplyFilters() 根据 LoggerFilterOptions.Rules 为每个 Provider 选择匹配的过滤规则。
2.4 聚合 Logger:遍历所有子 Logger 输出
业务代码注入的 ILogger<T>,底层拿到的是一个聚合 Logger。它本身不写日志,只做分发。

2.5 LoggerExtensions:LogInformation 到底调了什么?
日常写的 logger.LogInformation("xxx"),不是 ILogger 接口的方法,而是 LoggerExtensions 静态类的扩展方法。

六个级别的方法全部是语法糖,最终调用的都是 ILogger.Log(),日志级别只是一个枚举参数。
【核心结论】ILogger 接口只有一个 Log() 方法,扩展性全靠参数。六个日志级别方法全是语法糖。
2.6 Filter 日志过滤:Options + RefreshFilters 全链路
日志过滤是面试高频考点。完整链路:
appsettings.json 配置 → Options 绑定 LoggerFilterOptions → LoggerFactory 构造时 RefreshFilters() → ApplyFilters 为每个 Provider 选择规则 → 打日志时 IsEnabled() 判断级别
LoggerFilterOptions 源码:


这段配置会被解析为多条 LoggerFilterRule:
LoggerFactory 构造函数中监听 Options 变更:


2.7 自定义 Logger Provider:四件套标准封装
扩展日志系统,标准做法是实现四个文件:






[ProviderAlias("CustomConsole")] 特性不能忘!没有它,appsettings.json 中的 Filter 规则无法按 Provider 名匹配。三、IConfiguration 配置组件:源码全链路
3.1 上帝视角:配置系统的五个关键动作
① Build() → BuildCommon 内部 new ConfigurationBuilder()
② AddXxx() → 向 Builder 添加 IConfigurationSource(只登记参数)
③ Build() → 遍历 Source 调用 Build() 得到 Provider
④ 构造 Root → 遍历 Provider 执行 Load(),注册 ChangeToken
⑤ 读取/写入 → 读取倒序遍历,写入全量赋值
3.2 ConfigurationBuilder.Build():编排者触发构建

3.3 IConfigurationSource:极简工厂接口

3.4 IConfigurationProvider:真正干活的执行者


3.5 ConfigurationRoot:门面 + 倒序遍历(灵魂代码)


3.6 JSON 扁平化解析

{ "ConnectionStrings": { "Read": ["a", "b"] } } 被解析为 ConnectionStrings:Read:0=a、ConnectionStrings:Read:1=b。这就是 config["ConnectionStrings:Read:0"] 能读取数组元素的原因。3.7 自定义 Configuration Provider:四件套标准封装




四、架构对比:两套同源设计的执行差异
4.1 最大的执行差异:并行广播 vs 优先级覆盖
Logger:并行广播


注册 appsettings.json + 环境变量 + 命令行 → 读取时只有最高优先级的生效。
【核心结论】为什么不同?因为业务场景不同:日志需要"全量记录"(不能漏掉任何一个介质),配置需要"单一权威"(同一个 key 只有一个最终值)。
4.2 五维对比总表

📌文末总结
两套框架共享同一个设计哲学:编排者只收集不执行 → 来源与执行分离 → 门面聚合统一对外 → 扩展友好开闭原则 → 异常隔离。
理解了这套套路,你就能快速读懂 dotnet/runtime 中任何 AddXxx() 扩展方法,自定义日志介质和配置源,在业务开发中复用这套模式。
Logger 是"广播台"——一条消息,所有频道都播;Configuration 是"优先队列"——一个 Key,最后一个说了算。
夜雨聆风