mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-26 06:16:43 +08:00
feat(logging): 添加日志采样过滤器和统计追加器功能
- 实现 SamplingFilter 类用于限制高频日志输出,支持线程安全的采样控制 - 添加 StatisticsAppender 类用于收集日志指标,包括总数量、错误率、级别分布等统计信息 - 提供时间窗口内的采样机制,可配置采样率和时间窗口参数 - 实现完整的统计报告生成功能,支持按级别和日志记录器分类展示 - 添加线程安全的数据结构确保并发环境下的数据一致性 - 提供统计重置和数据查询接口便于监控和调试
This commit is contained in:
parent
c9617fba8a
commit
c5ed053f2c
163
GFramework.Core/logging/appenders/StatisticsAppender.cs
Normal file
163
GFramework.Core/logging/appenders/StatisticsAppender.cs
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Text;
|
||||||
|
using GFramework.Core.Abstractions.logging;
|
||||||
|
|
||||||
|
namespace GFramework.Core.logging.appenders;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志统计 Appender,用于收集日志指标
|
||||||
|
/// 线程安全:所有方法都是线程安全的
|
||||||
|
/// </summary>
|
||||||
|
public sealed class StatisticsAppender : ILogAppender
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<LogLevel, long> _levelCounts = new();
|
||||||
|
private readonly ConcurrentDictionary<string, long> _loggerCounts = new();
|
||||||
|
private long _errorCount;
|
||||||
|
private DateTime _startTime = DateTime.UtcNow;
|
||||||
|
private long _totalCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取总日志数量
|
||||||
|
/// </summary>
|
||||||
|
public long TotalCount => Interlocked.Read(ref _totalCount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取错误日志数量(Error + Fatal)
|
||||||
|
/// </summary>
|
||||||
|
public long ErrorCount => Interlocked.Read(ref _errorCount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取统计开始时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime StartTime => _startTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取运行时长
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan Uptime => DateTime.UtcNow - _startTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取错误率(错误数 / 总数)
|
||||||
|
/// </summary>
|
||||||
|
public double ErrorRate
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var total = TotalCount;
|
||||||
|
return total == 0 ? 0 : (double)ErrorCount / total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 追加日志条目
|
||||||
|
/// </summary>
|
||||||
|
public void Append(LogEntry entry)
|
||||||
|
{
|
||||||
|
// 增加总计数
|
||||||
|
Interlocked.Increment(ref _totalCount);
|
||||||
|
|
||||||
|
// 增加级别计数
|
||||||
|
_levelCounts.AddOrUpdate(entry.Level, 1, (_, count) => count + 1);
|
||||||
|
|
||||||
|
// 增加日志记录器计数
|
||||||
|
_loggerCounts.AddOrUpdate(entry.LoggerName, 1, (_, count) => count + 1);
|
||||||
|
|
||||||
|
// 如果是错误级别,增加错误计数
|
||||||
|
if (entry.Level >= LogLevel.Error)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref _errorCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 刷新缓冲区(此 Appender 无需刷新)
|
||||||
|
/// </summary>
|
||||||
|
public void Flush()
|
||||||
|
{
|
||||||
|
// 统计 Appender 不需要刷新
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定级别的日志数量
|
||||||
|
/// </summary>
|
||||||
|
public long GetCountByLevel(LogLevel level)
|
||||||
|
{
|
||||||
|
return _levelCounts.TryGetValue(level, out var count) ? count : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定日志记录器的日志数量
|
||||||
|
/// </summary>
|
||||||
|
public long GetCountByLogger(string loggerName)
|
||||||
|
{
|
||||||
|
return _loggerCounts.TryGetValue(loggerName, out var count) ? count : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取所有级别的日志数量
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<LogLevel, long> GetLevelCounts()
|
||||||
|
{
|
||||||
|
return new Dictionary<LogLevel, long>(_levelCounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取所有日志记录器的日志数量
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, long> GetLoggerCounts()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, long>(_loggerCounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重置所有统计数据
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _totalCount, 0);
|
||||||
|
Interlocked.Exchange(ref _errorCount, 0);
|
||||||
|
_levelCounts.Clear();
|
||||||
|
_loggerCounts.Clear();
|
||||||
|
_startTime = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成统计报告
|
||||||
|
/// </summary>
|
||||||
|
public string GenerateReport()
|
||||||
|
{
|
||||||
|
var report = new StringBuilder();
|
||||||
|
report.AppendLine("=== 日志统计报告 ===");
|
||||||
|
report.AppendLine($"统计时间: {_startTime:yyyy-MM-dd HH:mm:ss} - {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}");
|
||||||
|
report.AppendLine($"运行时长: {Uptime}");
|
||||||
|
report.AppendLine($"总日志数: {TotalCount}");
|
||||||
|
report.AppendLine($"错误日志数: {ErrorCount}");
|
||||||
|
report.AppendLine($"错误率: {ErrorRate:P2}");
|
||||||
|
report.AppendLine();
|
||||||
|
|
||||||
|
report.AppendLine("按级别统计:");
|
||||||
|
foreach (var level in Enum.GetValues<LogLevel>())
|
||||||
|
{
|
||||||
|
var count = GetCountByLevel(level);
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
var percentage = (double)count / TotalCount;
|
||||||
|
report.AppendLine($" {level}: {count} ({percentage:P2})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
report.AppendLine();
|
||||||
|
report.AppendLine("按日志记录器统计 (Top 10):");
|
||||||
|
var topLoggers = _loggerCounts
|
||||||
|
.OrderByDescending(kvp => kvp.Value)
|
||||||
|
.Take(10);
|
||||||
|
|
||||||
|
foreach (var (logger, count) in topLoggers)
|
||||||
|
{
|
||||||
|
var percentage = (double)count / TotalCount;
|
||||||
|
report.AppendLine($" {logger}: {count} ({percentage:P2})");
|
||||||
|
}
|
||||||
|
|
||||||
|
return report.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
74
GFramework.Core/logging/filters/SamplingFilter.cs
Normal file
74
GFramework.Core/logging/filters/SamplingFilter.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using GFramework.Core.Abstractions.logging;
|
||||||
|
|
||||||
|
namespace GFramework.Core.logging.filters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志采样过滤器,用于限制高频日志的输出
|
||||||
|
/// 线程安全:所有方法都是线程安全的
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SamplingFilter : ILogFilter
|
||||||
|
{
|
||||||
|
private readonly int _sampleRate;
|
||||||
|
private readonly ConcurrentDictionary<string, SamplingState> _samplingStates = new();
|
||||||
|
private readonly TimeSpan _timeWindow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建日志采样过滤器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sampleRate">采样率(每 N 条日志保留 1 条)</param>
|
||||||
|
/// <param name="timeWindow">时间窗口(在此时间内应用采样)</param>
|
||||||
|
public SamplingFilter(int sampleRate, TimeSpan timeWindow)
|
||||||
|
{
|
||||||
|
if (sampleRate <= 0)
|
||||||
|
throw new ArgumentException("Sample rate must be greater than 0", nameof(sampleRate));
|
||||||
|
|
||||||
|
if (timeWindow <= TimeSpan.Zero)
|
||||||
|
throw new ArgumentException("Time window must be greater than zero", nameof(timeWindow));
|
||||||
|
|
||||||
|
_sampleRate = sampleRate;
|
||||||
|
_timeWindow = timeWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断是否应该记录该日志条目
|
||||||
|
/// </summary>
|
||||||
|
public bool ShouldLog(LogEntry entry)
|
||||||
|
{
|
||||||
|
// 为每个日志记录器维护独立的采样状态
|
||||||
|
var key = entry.LoggerName;
|
||||||
|
var state = _samplingStates.GetOrAdd(key, _ => new SamplingState());
|
||||||
|
|
||||||
|
return state.ShouldLog(_sampleRate, _timeWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 采样状态
|
||||||
|
/// </summary>
|
||||||
|
private sealed class SamplingState
|
||||||
|
{
|
||||||
|
private readonly object _lock = new();
|
||||||
|
private long _count;
|
||||||
|
private DateTime _windowStart = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public bool ShouldLog(int sampleRate, TimeSpan timeWindow)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
|
// 检查是否需要重置时间窗口
|
||||||
|
if (now - _windowStart >= timeWindow)
|
||||||
|
{
|
||||||
|
_windowStart = now;
|
||||||
|
_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_count++;
|
||||||
|
|
||||||
|
// 每 N 条保留 1 条
|
||||||
|
return _count % sampleRate == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user