mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
feat(logging): 扩展ILogAppender接口并实现时间提供者注入
- 将ILogAppender接口继承IDisposable以支持资源释放 - 添加ITimeProvider和SystemTimeProvider接口及实现类 - 创建FakeTimeProvider用于测试时间控制 - 修改SamplingFilter支持时间提供者注入和最大日志记录器数量限制 - 为SamplingFilter添加过期状态清理功能 - 修改StatisticsAppender使用时间提供者并实现IDisposable - 更新相关单元测试以使用FakeTimeProvider进行精确时间控制 - 在测试类中为模拟追加器添加Dispose方法实现
This commit is contained in:
parent
cd407dc93c
commit
6c8b670343
@ -3,7 +3,7 @@ namespace GFramework.Core.Abstractions.logging;
|
||||
/// <summary>
|
||||
/// 日志输出器接口,负责将日志条目写入特定目标
|
||||
/// </summary>
|
||||
public interface ILogAppender
|
||||
public interface ILogAppender : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 追加日志条目
|
||||
|
||||
12
GFramework.Core.Abstractions/time/ITimeProvider.cs
Normal file
12
GFramework.Core.Abstractions/time/ITimeProvider.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace GFramework.Core.Abstractions.time;
|
||||
|
||||
/// <summary>
|
||||
/// 时间提供者接口,用于抽象时间获取以支持测试
|
||||
/// </summary>
|
||||
public interface ITimeProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前 UTC 时间
|
||||
/// </summary>
|
||||
DateTime UtcNow { get; }
|
||||
}
|
||||
@ -184,6 +184,10 @@ public class AsyncLogAppenderTests
|
||||
public void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class SlowAppender : ILogAppender
|
||||
@ -203,6 +207,10 @@ public class AsyncLogAppenderTests
|
||||
public void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class ThrowingAppender : ILogAppender
|
||||
@ -215,5 +223,9 @@ public class AsyncLogAppenderTests
|
||||
public void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -160,6 +160,10 @@ public class CompositeLoggerTests
|
||||
{
|
||||
FlushCalled = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class TestDisposableAppender : ILogAppender, IDisposable
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using GFramework.Core.logging.filters;
|
||||
using GFramework.Core.Tests.time;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GFramework.Core.Tests.logging;
|
||||
@ -10,9 +11,10 @@ public class SamplingFilterTests
|
||||
[Test]
|
||||
public void SamplingFilter_Should_Sample_Logs_By_Rate()
|
||||
{
|
||||
var filter = new SamplingFilter(sampleRate: 3, timeWindow: TimeSpan.FromMinutes(1));
|
||||
var timeProvider = new FakeTimeProvider();
|
||||
var filter = new SamplingFilter(sampleRate: 3, timeWindow: TimeSpan.FromMinutes(1), timeProvider: timeProvider);
|
||||
var entry = new LogEntry(
|
||||
DateTime.UtcNow,
|
||||
timeProvider.UtcNow,
|
||||
LogLevel.Info,
|
||||
"TestLogger",
|
||||
"Test message",
|
||||
@ -34,9 +36,11 @@ public class SamplingFilterTests
|
||||
[Test]
|
||||
public void SamplingFilter_Should_Reset_After_Time_Window()
|
||||
{
|
||||
var filter = new SamplingFilter(sampleRate: 2, timeWindow: TimeSpan.FromMilliseconds(100));
|
||||
var timeProvider = new FakeTimeProvider();
|
||||
var filter = new SamplingFilter(sampleRate: 2, timeWindow: TimeSpan.FromMilliseconds(100),
|
||||
timeProvider: timeProvider);
|
||||
var entry = new LogEntry(
|
||||
DateTime.UtcNow,
|
||||
timeProvider.UtcNow,
|
||||
LogLevel.Info,
|
||||
"TestLogger",
|
||||
"Test message",
|
||||
@ -48,8 +52,8 @@ public class SamplingFilterTests
|
||||
Assert.That(filter.ShouldLog(entry), Is.True); // 1st
|
||||
Assert.That(filter.ShouldLog(entry), Is.False); // 2nd
|
||||
|
||||
// 等待时间窗口过期
|
||||
Thread.Sleep(150);
|
||||
// 前进时间超过窗口
|
||||
timeProvider.Advance(TimeSpan.FromMilliseconds(150));
|
||||
|
||||
// 新窗口应该重置计数
|
||||
Assert.That(filter.ShouldLog(entry), Is.True); // 1st in new window
|
||||
@ -59,10 +63,11 @@ public class SamplingFilterTests
|
||||
[Test]
|
||||
public void SamplingFilter_Should_Maintain_Separate_State_Per_Logger()
|
||||
{
|
||||
var filter = new SamplingFilter(sampleRate: 2, timeWindow: TimeSpan.FromMinutes(1));
|
||||
var timeProvider = new FakeTimeProvider();
|
||||
var filter = new SamplingFilter(sampleRate: 2, timeWindow: TimeSpan.FromMinutes(1), timeProvider: timeProvider);
|
||||
|
||||
var entry1 = new LogEntry(DateTime.UtcNow, LogLevel.Info, "Logger1", "Message", null, null);
|
||||
var entry2 = new LogEntry(DateTime.UtcNow, LogLevel.Info, "Logger2", "Message", null, null);
|
||||
var entry1 = new LogEntry(timeProvider.UtcNow, LogLevel.Info, "Logger1", "Message", null, null);
|
||||
var entry2 = new LogEntry(timeProvider.UtcNow, LogLevel.Info, "Logger2", "Message", null, null);
|
||||
|
||||
// Logger1 的第一条
|
||||
Assert.That(filter.ShouldLog(entry1), Is.True);
|
||||
@ -77,6 +82,47 @@ public class SamplingFilterTests
|
||||
Assert.That(filter.ShouldLog(entry2), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SamplingFilter_Should_Use_Shared_State_When_Max_Loggers_Exceeded()
|
||||
{
|
||||
var timeProvider = new FakeTimeProvider();
|
||||
var filter = new SamplingFilter(sampleRate: 2, timeWindow: TimeSpan.FromMinutes(1), maxLoggers: 2,
|
||||
timeProvider: timeProvider);
|
||||
|
||||
var entry1 = new LogEntry(timeProvider.UtcNow, LogLevel.Info, "Logger1", "Message", null, null);
|
||||
var entry2 = new LogEntry(timeProvider.UtcNow, LogLevel.Info, "Logger2", "Message", null, null);
|
||||
var entry3 = new LogEntry(timeProvider.UtcNow, LogLevel.Info, "Logger3", "Message", null, null);
|
||||
|
||||
// Logger1 和 Logger2 各自独立
|
||||
Assert.That(filter.ShouldLog(entry1), Is.True); // Logger1: 1st
|
||||
Assert.That(filter.ShouldLog(entry2), Is.True); // Logger2: 1st
|
||||
|
||||
// Logger3 超过限制,使用共享状态
|
||||
Assert.That(filter.ShouldLog(entry3), Is.True); // Shared: 1st
|
||||
Assert.That(filter.ShouldLog(entry3), Is.False); // Shared: 2nd
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SamplingFilter_Should_Cleanup_Stale_States()
|
||||
{
|
||||
var timeProvider = new FakeTimeProvider();
|
||||
var filter = new SamplingFilter(sampleRate: 2, timeWindow: TimeSpan.FromMinutes(1), timeProvider: timeProvider);
|
||||
|
||||
var entry = new LogEntry(timeProvider.UtcNow, LogLevel.Info, "TestLogger", "Message", null, null);
|
||||
|
||||
// 使用过滤器
|
||||
filter.ShouldLog(entry);
|
||||
|
||||
// 前进时间
|
||||
timeProvider.Advance(TimeSpan.FromHours(2));
|
||||
|
||||
// 清理过期状态
|
||||
filter.CleanupStaleStates(TimeSpan.FromHours(1));
|
||||
|
||||
// 验证状态已被清理(通过再次使用应该重新开始计数)
|
||||
Assert.That(filter.ShouldLog(entry), Is.True); // 应该是新的第一条
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SamplingFilter_Should_Throw_When_SampleRate_Is_Invalid()
|
||||
{
|
||||
@ -102,11 +148,27 @@ public class SamplingFilterTests
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SamplingFilter_Should_Throw_When_MaxLoggers_Is_Invalid()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
new SamplingFilter(sampleRate: 2, timeWindow: TimeSpan.FromMinutes(1), maxLoggers: 0);
|
||||
});
|
||||
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
new SamplingFilter(sampleRate: 2, timeWindow: TimeSpan.FromMinutes(1), maxLoggers: -1);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SamplingFilter_Should_Be_Thread_Safe()
|
||||
{
|
||||
var filter = new SamplingFilter(sampleRate: 10, timeWindow: TimeSpan.FromMinutes(1));
|
||||
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", "Message", null, null);
|
||||
var timeProvider = new FakeTimeProvider();
|
||||
var filter = new SamplingFilter(sampleRate: 10, timeWindow: TimeSpan.FromMinutes(1),
|
||||
timeProvider: timeProvider);
|
||||
var entry = new LogEntry(timeProvider.UtcNow, LogLevel.Info, "TestLogger", "Message", null, null);
|
||||
|
||||
var passedCount = 0;
|
||||
var tasks = new List<Task>();
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using GFramework.Core.logging.appenders;
|
||||
using GFramework.Core.Tests.time;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GFramework.Core.Tests.logging;
|
||||
@ -10,14 +11,17 @@ public class StatisticsAppenderTests
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_appender = new StatisticsAppender();
|
||||
_timeProvider = new FakeTimeProvider();
|
||||
_appender = new StatisticsAppender(_timeProvider);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_appender.Dispose();
|
||||
}
|
||||
|
||||
private FakeTimeProvider _timeProvider = null!;
|
||||
private StatisticsAppender _appender = null!;
|
||||
|
||||
[Test]
|
||||
@ -86,9 +90,9 @@ public class StatisticsAppenderTests
|
||||
public void StatisticsAppender_Should_Track_Uptime()
|
||||
{
|
||||
var startTime = _appender.StartTime;
|
||||
Thread.Sleep(100);
|
||||
_timeProvider.Advance(TimeSpan.FromSeconds(100));
|
||||
|
||||
Assert.That(_appender.Uptime, Is.GreaterThanOrEqualTo(TimeSpan.FromMilliseconds(100)));
|
||||
Assert.That(_appender.Uptime, Is.EqualTo(TimeSpan.FromSeconds(100)));
|
||||
Assert.That(_appender.StartTime, Is.EqualTo(startTime));
|
||||
}
|
||||
|
||||
@ -102,7 +106,7 @@ public class StatisticsAppenderTests
|
||||
Assert.That(_appender.ErrorCount, Is.EqualTo(1));
|
||||
|
||||
var oldStartTime = _appender.StartTime;
|
||||
Thread.Sleep(10);
|
||||
_timeProvider.Advance(TimeSpan.FromSeconds(10));
|
||||
_appender.Reset();
|
||||
|
||||
Assert.That(_appender.TotalCount, Is.EqualTo(0));
|
||||
@ -172,10 +176,10 @@ public class StatisticsAppenderTests
|
||||
Assert.DoesNotThrow(() => _appender.Flush());
|
||||
}
|
||||
|
||||
private static LogEntry CreateLogEntry(LogLevel level, string loggerName, string message)
|
||||
private LogEntry CreateLogEntry(LogLevel level, string loggerName, string message)
|
||||
{
|
||||
return new LogEntry(
|
||||
DateTime.UtcNow,
|
||||
_timeProvider.UtcNow,
|
||||
level,
|
||||
loggerName,
|
||||
message,
|
||||
|
||||
41
GFramework.Core.Tests/time/FakeTimeProvider.cs
Normal file
41
GFramework.Core.Tests/time/FakeTimeProvider.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using GFramework.Core.Abstractions.time;
|
||||
|
||||
namespace GFramework.Core.Tests.time;
|
||||
|
||||
/// <summary>
|
||||
/// 可控制的时间提供者,用于测试
|
||||
/// </summary>
|
||||
public sealed class FakeTimeProvider : ITimeProvider
|
||||
{
|
||||
private DateTime _currentTime;
|
||||
|
||||
/// <summary>
|
||||
/// 创建可控制的时间提供者
|
||||
/// </summary>
|
||||
/// <param name="initialTime">初始时间,默认为 2024-01-01 00:00:00 UTC</param>
|
||||
public FakeTimeProvider(DateTime? initialTime = null)
|
||||
{
|
||||
_currentTime = initialTime ?? new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前时间
|
||||
/// </summary>
|
||||
public DateTime UtcNow => _currentTime;
|
||||
|
||||
/// <summary>
|
||||
/// 前进指定的时间
|
||||
/// </summary>
|
||||
public void Advance(TimeSpan duration)
|
||||
{
|
||||
_currentTime = _currentTime.Add(duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置当前时间
|
||||
/// </summary>
|
||||
public void SetTime(DateTime time)
|
||||
{
|
||||
_currentTime = time;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using GFramework.Core.Abstractions.time;
|
||||
using GFramework.Core.time;
|
||||
|
||||
namespace GFramework.Core.logging.appenders;
|
||||
|
||||
@ -12,10 +14,21 @@ public sealed class StatisticsAppender : ILogAppender
|
||||
{
|
||||
private readonly ConcurrentDictionary<LogLevel, long> _levelCounts = new();
|
||||
private readonly ConcurrentDictionary<string, long> _loggerCounts = new();
|
||||
private readonly ITimeProvider _timeProvider;
|
||||
private long _errorCount;
|
||||
private DateTime _startTime = DateTime.UtcNow;
|
||||
private long _startTimeTicks;
|
||||
private long _totalCount;
|
||||
|
||||
/// <summary>
|
||||
/// 创建日志统计 Appender
|
||||
/// </summary>
|
||||
/// <param name="timeProvider">时间提供者,默认使用系统时间</param>
|
||||
public StatisticsAppender(ITimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? new SystemTimeProvider();
|
||||
_startTimeTicks = _timeProvider.UtcNow.Ticks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取总日志数量
|
||||
/// </summary>
|
||||
@ -29,12 +42,12 @@ public sealed class StatisticsAppender : ILogAppender
|
||||
/// <summary>
|
||||
/// 获取统计开始时间
|
||||
/// </summary>
|
||||
public DateTime StartTime => _startTime;
|
||||
public DateTime StartTime => new(Interlocked.Read(ref _startTimeTicks), DateTimeKind.Utc);
|
||||
|
||||
/// <summary>
|
||||
/// 获取运行时长
|
||||
/// </summary>
|
||||
public TimeSpan Uptime => DateTime.UtcNow - _startTime;
|
||||
public TimeSpan Uptime => _timeProvider.UtcNow - StartTime;
|
||||
|
||||
/// <summary>
|
||||
/// 获取错误率(错误数 / 总数)
|
||||
@ -77,6 +90,14 @@ public sealed class StatisticsAppender : ILogAppender
|
||||
// 统计 Appender 不需要刷新
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// 无需释放资源
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定级别的日志数量
|
||||
/// </summary>
|
||||
@ -118,7 +139,7 @@ public sealed class StatisticsAppender : ILogAppender
|
||||
Interlocked.Exchange(ref _errorCount, 0);
|
||||
_levelCounts.Clear();
|
||||
_loggerCounts.Clear();
|
||||
_startTime = DateTime.UtcNow;
|
||||
Interlocked.Exchange(ref _startTimeTicks, _timeProvider.UtcNow.Ticks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -127,8 +148,11 @@ public sealed class StatisticsAppender : ILogAppender
|
||||
public string GenerateReport()
|
||||
{
|
||||
var report = new StringBuilder();
|
||||
var startTime = StartTime;
|
||||
var now = _timeProvider.UtcNow;
|
||||
|
||||
report.AppendLine("=== 日志统计报告 ===");
|
||||
report.AppendLine($"统计时间: {_startTime:yyyy-MM-dd HH:mm:ss} - {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}");
|
||||
report.AppendLine($"统计时间: {startTime:yyyy-MM-dd HH:mm:ss} - {now:yyyy-MM-dd HH:mm:ss}");
|
||||
report.AppendLine($"运行时长: {Uptime}");
|
||||
report.AppendLine($"总日志数: {TotalCount}");
|
||||
report.AppendLine($"错误日志数: {ErrorCount}");
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
using System.Collections.Concurrent;
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using GFramework.Core.Abstractions.time;
|
||||
using GFramework.Core.time;
|
||||
|
||||
namespace GFramework.Core.logging.filters;
|
||||
|
||||
@ -9,8 +11,11 @@ namespace GFramework.Core.logging.filters;
|
||||
/// </summary>
|
||||
public sealed class SamplingFilter : ILogFilter
|
||||
{
|
||||
private const int DefaultMaxLoggers = 1000;
|
||||
private readonly int _maxLoggers;
|
||||
private readonly int _sampleRate;
|
||||
private readonly ConcurrentDictionary<string, SamplingState> _samplingStates = new();
|
||||
private readonly ITimeProvider _timeProvider;
|
||||
private readonly TimeSpan _timeWindow;
|
||||
|
||||
/// <summary>
|
||||
@ -18,7 +23,13 @@ public sealed class SamplingFilter : ILogFilter
|
||||
/// </summary>
|
||||
/// <param name="sampleRate">采样率(每 N 条日志保留 1 条)</param>
|
||||
/// <param name="timeWindow">时间窗口(在此时间内应用采样)</param>
|
||||
public SamplingFilter(int sampleRate, TimeSpan timeWindow)
|
||||
/// <param name="maxLoggers">最大日志记录器数量,超过后使用共享状态</param>
|
||||
/// <param name="timeProvider">时间提供者,默认使用系统时间</param>
|
||||
public SamplingFilter(
|
||||
int sampleRate,
|
||||
TimeSpan timeWindow,
|
||||
int maxLoggers = DefaultMaxLoggers,
|
||||
ITimeProvider? timeProvider = null)
|
||||
{
|
||||
if (sampleRate <= 0)
|
||||
throw new ArgumentException("Sample rate must be greater than 0", nameof(sampleRate));
|
||||
@ -26,8 +37,13 @@ public sealed class SamplingFilter : ILogFilter
|
||||
if (timeWindow <= TimeSpan.Zero)
|
||||
throw new ArgumentException("Time window must be greater than zero", nameof(timeWindow));
|
||||
|
||||
if (maxLoggers <= 0)
|
||||
throw new ArgumentException("Max loggers must be greater than 0", nameof(maxLoggers));
|
||||
|
||||
_sampleRate = sampleRate;
|
||||
_timeWindow = timeWindow;
|
||||
_maxLoggers = maxLoggers;
|
||||
_timeProvider = timeProvider ?? new SystemTimeProvider();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -35,32 +51,71 @@ public sealed class SamplingFilter : ILogFilter
|
||||
/// </summary>
|
||||
public bool ShouldLog(LogEntry entry)
|
||||
{
|
||||
// 为每个日志记录器维护独立的采样状态
|
||||
var key = entry.LoggerName;
|
||||
var state = _samplingStates.GetOrAdd(key, _ => new SamplingState());
|
||||
// 如果超过最大日志记录器数量,使用共享状态
|
||||
var key = _samplingStates.Count >= _maxLoggers ? "*" : entry.LoggerName;
|
||||
|
||||
var state = _samplingStates.GetOrAdd(key, _ => new SamplingState(_timeProvider));
|
||||
|
||||
return state.ShouldLog(_sampleRate, _timeWindow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理过期的采样状态
|
||||
/// </summary>
|
||||
public void CleanupStaleStates(TimeSpan staleThreshold)
|
||||
{
|
||||
var now = _timeProvider.UtcNow;
|
||||
var keysToRemove = new List<string>();
|
||||
|
||||
foreach (var kvp in _samplingStates)
|
||||
{
|
||||
if (kvp.Key == "*") continue; // 不清理共享状态
|
||||
|
||||
if (kvp.Value.IsStale(now, staleThreshold))
|
||||
{
|
||||
keysToRemove.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var key in keysToRemove)
|
||||
{
|
||||
_samplingStates.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 采样状态
|
||||
/// </summary>
|
||||
private sealed class SamplingState
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly ITimeProvider _timeProvider;
|
||||
private long _count;
|
||||
private DateTime _windowStart = DateTime.UtcNow;
|
||||
private long _lastAccessTicks;
|
||||
private long _windowStartTicks;
|
||||
|
||||
public SamplingState(ITimeProvider timeProvider)
|
||||
{
|
||||
_timeProvider = timeProvider;
|
||||
var now = timeProvider.UtcNow.Ticks;
|
||||
_windowStartTicks = now;
|
||||
_lastAccessTicks = now;
|
||||
}
|
||||
|
||||
public bool ShouldLog(int sampleRate, TimeSpan timeWindow)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var now = _timeProvider.UtcNow;
|
||||
var nowTicks = now.Ticks;
|
||||
Interlocked.Exchange(ref _lastAccessTicks, nowTicks);
|
||||
|
||||
var windowStart = new DateTime(Interlocked.Read(ref _windowStartTicks), DateTimeKind.Utc);
|
||||
|
||||
// 检查是否需要重置时间窗口
|
||||
if (now - _windowStart >= timeWindow)
|
||||
if (now - windowStart >= timeWindow)
|
||||
{
|
||||
_windowStart = now;
|
||||
Interlocked.Exchange(ref _windowStartTicks, nowTicks);
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
@ -70,5 +125,11 @@ public sealed class SamplingFilter : ILogFilter
|
||||
return _count % sampleRate == 1;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsStale(DateTime now, TimeSpan staleThreshold)
|
||||
{
|
||||
var lastAccess = new DateTime(Interlocked.Read(ref _lastAccessTicks), DateTimeKind.Utc);
|
||||
return now - lastAccess > staleThreshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
GFramework.Core/time/SystemTimeProvider.cs
Normal file
14
GFramework.Core/time/SystemTimeProvider.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using GFramework.Core.Abstractions.time;
|
||||
|
||||
namespace GFramework.Core.time;
|
||||
|
||||
/// <summary>
|
||||
/// 系统时间提供者,返回真实的系统时间
|
||||
/// </summary>
|
||||
public sealed class SystemTimeProvider : ITimeProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前 UTC 时间
|
||||
/// </summary>
|
||||
public DateTime UtcNow => DateTime.UtcNow;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user