GeWuYou 1ba771e13a feat(logging): 实现结构化日志记录和异步日志输出功能
- 将 AbstractLogger 实现从 ILogger 扩展为 IStructuredLogger 接口
- 添加通用日志方法 Log(LogLevel, string, params object[]) 支持格式化参数
- 实现结构化日志方法支持属性键值对记录
- 添加 ConsoleAppender、FileAppender 和 AsyncLogAppender 日志输出器
- 实现 CompositeFilter 过滤器和 DefaultLogFormatter、JsonLogFormatter 格式化器
- 在 ConsoleLogger 和 GodotLogger 中使用预缓存的日志级别字符串提升性能
- 使用 ANSI 颜色代码替代 ConsoleColor 实现跨平台日志着色
- 在 ConsoleLoggerFactoryProvider 和 GodotLoggerFactoryProvider 中添加日志工厂缓存
- 优化 FileStorage 中目录遍历使用 OfType<string>() 类型转换
- 添加 LogContext 支持异步流中的结构化属性传递
2026-02-26 19:57:42 +08:00

124 lines
3.5 KiB
C#

namespace GFramework.Core.Abstractions.logging;
/// <summary>
/// 日志上下文,用于在异步流中传递结构化属性
/// </summary>
public sealed class LogContext : IDisposable
{
private static readonly AsyncLocal<Dictionary<string, object?>?> _context = new();
private readonly bool _hadPreviousValue;
private readonly string _key;
private readonly object? _previousValue;
private LogContext(string key, object? value)
{
_key = key;
var current = _context.Value;
if (current != null && current.TryGetValue(key, out var prev))
{
_previousValue = prev;
_hadPreviousValue = true;
}
EnsureContext();
_context.Value![key] = value;
}
/// <summary>
/// 获取当前上下文中的所有属性
/// </summary>
public static IReadOnlyDictionary<string, object?> Current
{
get
{
var context = _context.Value;
return context ??
(IReadOnlyDictionary<string, object?>)new Dictionary<string, object?>(StringComparer.Ordinal);
}
}
/// <summary>
/// 释放上下文,恢复之前的值
/// </summary>
public void Dispose()
{
var current = _context.Value;
if (current == null) return;
if (_hadPreviousValue)
{
current[_key] = _previousValue;
}
else
{
current.Remove(_key);
if (current.Count == 0)
{
_context.Value = null;
}
}
}
/// <summary>
/// 向当前上下文添加一个属性
/// </summary>
/// <param name="key">属性键</param>
/// <param name="value">属性值</param>
/// <returns>可释放的上下文对象,释放时会恢复之前的值</returns>
public static IDisposable Push(string key, object? value)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("Key cannot be null or whitespace.", nameof(key));
return new LogContext(key, value);
}
/// <summary>
/// 向当前上下文添加多个属性
/// </summary>
/// <param name="properties">属性键值对</param>
/// <returns>可释放的上下文对象,释放时会恢复之前的值</returns>
public static IDisposable PushProperties(params (string Key, object? Value)[] properties)
{
if (properties == null || properties.Length == 0)
throw new ArgumentException("Properties cannot be null or empty.", nameof(properties));
return new CompositeDisposable(properties.Select(p => Push(p.Key, p.Value)).ToArray());
}
/// <summary>
/// 清除当前上下文中的所有属性
/// </summary>
public static void Clear()
{
_context.Value = null;
}
private static void EnsureContext()
{
_context.Value ??= new Dictionary<string, object?>(StringComparer.Ordinal);
}
/// <summary>
/// 组合多个可释放对象
/// </summary>
private sealed class CompositeDisposable : IDisposable
{
private readonly IDisposable[] _disposables;
public CompositeDisposable(IDisposable[] disposables)
{
_disposables = disposables;
}
public void Dispose()
{
// 按相反顺序释放
for (int i = _disposables.Length - 1; i >= 0; i--)
{
_disposables[i].Dispose();
}
}
}
}