GFramework/GFramework.Core/Logging/ConfigurableLoggerFactory.cs
GeWuYou 2c2df5de29 fix(review-followup): 修复失败路径清理与日志契约
- 修复 Godot 模块在附加流程失败时的登记时机,确保后续销毁仍可感知半安装模块
- 更新 ConfigurableLoggerFactory 的 name 空值校验与 minLevel XML 契约,并用可观察行为替换脆弱的反射测试
- 补充 WeakTypePairCache 热路径注释,并新增 Godot 模块安装顺序回归测试
2026-04-18 20:45:37 +08:00

101 lines
3.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Logging.Appenders;
namespace GFramework.Core.Logging;
/// <summary>
/// 可配置的 Logger 工厂。
/// </summary>
internal sealed class ConfigurableLoggerFactory : ILoggerFactory, IDisposable
{
private readonly ILogAppender[] _appenders;
private readonly LoggingConfiguration _config;
private int _disposed;
/// <summary>
/// 初始化一个基于日志配置创建输出管线的工厂实例。
/// </summary>
/// <param name="config">日志配置。</param>
public ConfigurableLoggerFactory(LoggingConfiguration config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
// 反序列化输入可能显式把集合写成 null这里统一归一化为可安全枚举的空集合。
_config.Appenders ??= [];
_config.LoggerLevels ??= new Dictionary<string, LogLevel>(StringComparer.Ordinal);
_appenders = _config.Appenders.Select(LoggingConfigurationLoader.CreateAppender).ToArray();
}
/// <summary>
/// 释放内部 Appender 持有的资源。
/// </summary>
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, 1) != 0)
{
return;
}
foreach (var appender in _appenders)
{
switch (appender)
{
case AsyncLogAppender asyncLogAppender:
asyncLogAppender.Dispose();
break;
case IDisposable disposable:
disposable.Dispose();
break;
}
}
}
/// <summary>
/// 为指定名称创建日志记录器,并应用最匹配的命名空间级别配置。
/// </summary>
/// <param name="name">日志记录器名称。</param>
/// <param name="minLevel">调用方要求的最小日志级别下限;在未命中命名空间覆盖时生效。</param>
/// <returns>可写入日志的记录器实例。</returns>
/// <remarks>
/// 当配置文件与调用方同时提供默认级别时,会取两者中更严格的那一个;
/// 若命中更具体的命名空间级别覆盖,则以该覆盖配置为准,即使其低于调用方传入的默认下限。
/// </remarks>
public ILogger GetLogger(string name, LogLevel minLevel = LogLevel.Info)
{
ArgumentNullException.ThrowIfNull(name);
var effectiveLevel = _config.MinLevel > minLevel ? _config.MinLevel : minLevel;
var bestMatchLength = -1;
foreach (var kvp in _config.LoggerLevels)
{
var isExactMatch = string.Equals(name, kvp.Key, StringComparison.Ordinal);
if (isExactMatch)
{
effectiveLevel = kvp.Value;
break;
}
var isPrefixMatch = name.StartsWith(kvp.Key + ".", StringComparison.Ordinal);
if (isPrefixMatch && kvp.Key.Length > bestMatchLength)
{
// 多个命名空间前缀都能命中时,最长前缀代表最具体的配置。
bestMatchLength = kvp.Key.Length;
effectiveLevel = kvp.Value;
}
}
if (_appenders.Length == 0)
{
return new ConsoleLogger(name, effectiveLevel);
}
if (_appenders.Length == 1 && _appenders[0] is ConsoleAppender)
{
return new ConsoleLogger(name, effectiveLevel);
}
return new CompositeLogger(name, effectiveLevel, _appenders);
}
}