Skip to content

Feat/logging sampling statistics #74

Merged
GeWuYou merged 3 commits into
mainfrom
feat/logging-sampling-statistics-
Mar 5, 2026
Merged

Feat/logging sampling statistics #74
GeWuYou merged 3 commits into
mainfrom
feat/logging-sampling-statistics-

Conversation

@GeWuYou
Copy link
Copy Markdown
Owner

@GeWuYou GeWuYou commented Mar 5, 2026

Summary by Sourcery

添加日志统计附加器和采样过滤器,以支持日志指标采集和限频日志记录。

新特性:

  • 引入线程安全的 StatisticsAppender,用于跟踪日志计数、错误指标、运行时间,并生成文本报告。
  • 引入线程安全的 SamplingFilter,根据可配置的采样率和时间窗口,以每个 logger 为粒度对日志进行采样。

测试:

  • 增加单元测试,覆盖 StatisticsAppender 的计数、错误率、运行时间、重置行为、报告生成、空状态、并发性以及刷新行为。
  • 增加单元测试,覆盖 SamplingFilter 的采样行为、时间窗口重置、按 logger 区分状态、参数校验以及并发性。
Original summary in English

Summary by Sourcery

Add logging statistics appender and sampling filter to support log metric collection and rate-limited logging.

New Features:

  • Introduce a thread-safe StatisticsAppender that tracks log counts, error metrics, uptime, and generates textual reports.
  • Introduce a thread-safe SamplingFilter that samples logs per-logger based on a configurable rate and time window.

Tests:

  • Add unit tests covering StatisticsAppender counting, error rate, uptime, reset behavior, reporting, empty state, concurrency, and flushing.
  • Add unit tests covering SamplingFilter sampling behavior, time-window reset, per-logger state separation, parameter validation, and concurrency.

GeWuYou added 2 commits March 5, 2026 14:11
- 实现 SamplingFilter 类用于限制高频日志输出,支持线程安全的采样控制
- 添加 StatisticsAppender 类用于收集日志指标,包括总数量、错误率、级别分布等统计信息
- 提供时间窗口内的采样机制,可配置采样率和时间窗口参数
- 实现完整的统计报告生成功能,支持按级别和日志记录器分类展示
- 添加线程安全的数据结构确保并发环境下的数据一致性
- 提供统计重置和数据查询接口便于监控和调试
- 为SamplingFilter添加完整的单元测试覆盖采样率、时间窗口、线程安全等功能
- 为StatisticsAppender添加全面的单元测试验证统计计算、错误率跟踪、报告生成功能
- 测试各个日志级别和记录器的独立状态维护
- 验证多线程环境下的数据一致性
- 包含边界条件和异常情况的测试用例
@deepsource-io
Copy link
Copy Markdown

deepsource-io Bot commented Mar 5, 2026

DeepSource Code Review

We reviewed changes in c9617fb...88e07f1 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
C# Mar 5, 2026 8:03a.m. Review ↗
Secrets Mar 5, 2026 8:03a.m. Review ↗

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 5, 2026

Reviewer's Guide

引入了一个线程安全的日志统计追加器,用于跟踪日志数量与错误率,并新增按 logger 维度的采样过滤器以限制高频日志,同时为这两个组件提供了完备的单元测试。

使用 SamplingFilter 和 StatisticsAppender 进行日志处理的时序图

sequenceDiagram
    participant Logger
    participant SamplingFilter
    participant StatisticsAppender

    Logger->>SamplingFilter: ShouldLog(logEntry)
    SamplingFilter->>SamplingFilter: GetOrAdd SamplingState for logger
    SamplingFilter->>SamplingFilter: SamplingState.ShouldLog(sampleRate, timeWindow)
    alt should log
        SamplingFilter-->>Logger: true
        Logger->>StatisticsAppender: Append(logEntry)
        StatisticsAppender->>StatisticsAppender: Increment _totalCount
        StatisticsAppender->>StatisticsAppender: Update _levelCounts
        StatisticsAppender->>StatisticsAppender: Update _loggerCounts
        alt level >= Error
            StatisticsAppender->>StatisticsAppender: Increment _errorCount
        end
    else filtered out
        SamplingFilter-->>Logger: false
    end
Loading

新的日志采样与统计组件的类图

classDiagram
    direction LR

    class LogEntry {
    }

    class LogLevel {
    }

    class ILogAppender {
        <<interface>>
        +void Append(LogEntry entry)
        +void Flush()
    }

    class ILogFilter {
        <<interface>>
        +bool ShouldLog(LogEntry entry)
    }

    class StatisticsAppender {
        -ConcurrentDictionary~LogLevel,long~ _levelCounts
        -ConcurrentDictionary~string,long~ _loggerCounts
        -long _errorCount
        -long _totalCount
        -DateTime _startTime
        +long TotalCount
        +long ErrorCount
        +DateTime StartTime
        +TimeSpan Uptime
        +double ErrorRate
        +void Append(LogEntry entry)
        +void Flush()
        +long GetCountByLevel(LogLevel level)
        +long GetCountByLogger(string loggerName)
        +IReadOnlyDictionary~LogLevel,long~ GetLevelCounts()
        +IReadOnlyDictionary~string,long~ GetLoggerCounts()
        +void Reset()
        +string GenerateReport()
    }

    class SamplingFilter {
        -int _sampleRate
        -TimeSpan _timeWindow
        -ConcurrentDictionary~string,SamplingState~ _samplingStates
        +SamplingFilter(int sampleRate, TimeSpan timeWindow)
        +bool ShouldLog(LogEntry entry)
    }

    class SamplingState {
        -object _lock
        -long _count
        -DateTime _windowStart
        +bool ShouldLog(int sampleRate, TimeSpan timeWindow)
    }

    ILogAppender <|.. StatisticsAppender
    ILogFilter <|.. SamplingFilter
    SamplingFilter o-- SamplingState
    StatisticsAppender ..> LogEntry
    StatisticsAppender ..> LogLevel
    SamplingFilter ..> LogEntry
    SamplingState ..> TimeSpan
    SamplingState ..> DateTime
Loading

SamplingState.ShouldLog 判定逻辑的流程图

flowchart TD
    A_Start[Start ShouldLog] --> B_CheckWindow{now - _windowStart >= timeWindow?}
    B_CheckWindow -- Yes --> C_ResetWindow[_windowStart = now\n_count = 0]
    B_CheckWindow -- No --> D_KeepWindow[Keep current window]
    C_ResetWindow --> E_Increment[_count++]
    D_KeepWindow --> E_Increment
    E_Increment --> F_Mod{_count % sampleRate == 1?}
    F_Mod -- Yes --> G_ReturnTrue[Return true]
    F_Mod -- No --> H_ReturnFalse[Return false]
    G_ReturnTrue --> I_End[End]
    H_ReturnFalse --> I_End[End]
Loading

文件级改动

Change Details Files
新增一个线程安全的统计追加器,用于聚合日志数量、错误指标并生成文本报告。
  • 将 StatisticsAppender 实现为一个 ILogAppender,使用并发数据结构和 Interlocked 操作跟踪总日志数、错误日志数、按级别统计和按 logger 统计。
  • 暴露 TotalCount、ErrorCount、ErrorRate、StartTime、Uptime 等指标,以及按级别 / logger 查询计数和获取所有计数快照的方法。
  • 提供 Reset 和 Flush 操作,其中 Reset 会清空所有计数器并重置运行时间,GenerateReport 会生成包含 top logger 在内的人类可读指标概要。
GFramework.Core/logging/appenders/StatisticsAppender.cs
新增单元测试,用于验证 StatisticsAppender 行为及线程安全性。
  • 验证总日志数、错误数、错误率、按级别和按 logger 的计数、运行时间追踪、重置语义以及报告内容。
  • 确保在没有任何日志追加时,空状态行为正确。
  • 在并发场景下对 Append 和计数器进行压力测试,并断言 Flush 为 no-op 且不会抛出异常。
GFramework.Core.Tests/logging/StatisticsAppenderTests.cs
引入按 logger、基于时间窗口的采样过滤器以减少高频日志。
  • 将 SamplingFilter 实现为一个 ILogFilter,在并发字典中维护按 logger 的采样状态,并在每个 logger 的内部状态上使用锁保护。
  • 在可配置的 timeWindow 内应用 sampleRate 策略(每 N 条日志记录 1 条),并基于时间窗口重置计数器。
  • 校验 sampleRate 和 timeWindow 构造参数,对非法值抛出 ArgumentException。
GFramework.Core/logging/filters/SamplingFilter.cs
新增单元测试,覆盖采样过滤器的行为、参数校验和并发特性。
  • 针对给定采样率,在顺序调用中测试确定性的采样决策。
  • 验证采样窗口在配置的时间窗口过期后重置,并且状态按 logger 名字隔离。
  • 断言非法构造参数会抛出异常,并运行并发采样测试,在多线程场景下近似验证预期通过率。
GFramework.Core.Tests/logging/SamplingFilterTests.cs

Tips and commands

Interacting with Sourcery

  • 触发新的代码评审: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub issue: 在评审评论下回复,请 Sourcery 从该评论创建一个 issue。你也可以直接回复 @sourcery-ai issue 从该评论创建 issue。
  • 生成 pull request 标题: 在 pull request 标题任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 描述任意位置写上 @sourcery-ai summary,即可在该位置生成 PR 摘要。你也可以评论 @sourcery-ai summary 来在任意时间(重新)生成摘要。
  • 生成评审者指南: 在 pull request 中评论 @sourcery-ai guide,可在任意时间(重新)生成评审者指南。
  • 一次性解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,即可将所有 Sourcery 评论标记为已解决。如果你已经处理完所有评论并且不想再看到它们,这会很有用。
  • 忽略所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss,即可忽略所有已有的 Sourcery 评审。特别适合你想从头开始一次新的评审时——别忘了再评论 @sourcery-ai review 触发新评审!

Customizing Your Experience

访问你的 dashboard 以:

  • 启用或禁用评审特性,例如 Sourcery 自动生成的 pull request 摘要、评审者指南等。
  • 更改评审语言。
  • 添加、删除或编辑自定义评审指令。
  • 调整其他评审设置。

Getting Help

Original review guide in English

Reviewer's Guide

Introduces a thread-safe logging statistics appender that tracks counts and error rates, plus a per-logger sampling filter to limit high-frequency logs, along with comprehensive unit tests for both components.

Sequence diagram for log processing with SamplingFilter and StatisticsAppender

sequenceDiagram
    participant Logger
    participant SamplingFilter
    participant StatisticsAppender

    Logger->>SamplingFilter: ShouldLog(logEntry)
    SamplingFilter->>SamplingFilter: GetOrAdd SamplingState for logger
    SamplingFilter->>SamplingFilter: SamplingState.ShouldLog(sampleRate, timeWindow)
    alt should log
        SamplingFilter-->>Logger: true
        Logger->>StatisticsAppender: Append(logEntry)
        StatisticsAppender->>StatisticsAppender: Increment _totalCount
        StatisticsAppender->>StatisticsAppender: Update _levelCounts
        StatisticsAppender->>StatisticsAppender: Update _loggerCounts
        alt level >= Error
            StatisticsAppender->>StatisticsAppender: Increment _errorCount
        end
    else filtered out
        SamplingFilter-->>Logger: false
    end
Loading

Class diagram for new logging sampling and statistics components

classDiagram
    direction LR

    class LogEntry {
    }

    class LogLevel {
    }

    class ILogAppender {
        <<interface>>
        +void Append(LogEntry entry)
        +void Flush()
    }

    class ILogFilter {
        <<interface>>
        +bool ShouldLog(LogEntry entry)
    }

    class StatisticsAppender {
        -ConcurrentDictionary~LogLevel,long~ _levelCounts
        -ConcurrentDictionary~string,long~ _loggerCounts
        -long _errorCount
        -long _totalCount
        -DateTime _startTime
        +long TotalCount
        +long ErrorCount
        +DateTime StartTime
        +TimeSpan Uptime
        +double ErrorRate
        +void Append(LogEntry entry)
        +void Flush()
        +long GetCountByLevel(LogLevel level)
        +long GetCountByLogger(string loggerName)
        +IReadOnlyDictionary~LogLevel,long~ GetLevelCounts()
        +IReadOnlyDictionary~string,long~ GetLoggerCounts()
        +void Reset()
        +string GenerateReport()
    }

    class SamplingFilter {
        -int _sampleRate
        -TimeSpan _timeWindow
        -ConcurrentDictionary~string,SamplingState~ _samplingStates
        +SamplingFilter(int sampleRate, TimeSpan timeWindow)
        +bool ShouldLog(LogEntry entry)
    }

    class SamplingState {
        -object _lock
        -long _count
        -DateTime _windowStart
        +bool ShouldLog(int sampleRate, TimeSpan timeWindow)
    }

    ILogAppender <|.. StatisticsAppender
    ILogFilter <|.. SamplingFilter
    SamplingFilter o-- SamplingState
    StatisticsAppender ..> LogEntry
    StatisticsAppender ..> LogLevel
    SamplingFilter ..> LogEntry
    SamplingState ..> TimeSpan
    SamplingState ..> DateTime
Loading

Flow diagram for SamplingState.ShouldLog decision logic

flowchart TD
    A_Start[Start ShouldLog] --> B_CheckWindow{now - _windowStart >= timeWindow?}
    B_CheckWindow -- Yes --> C_ResetWindow[_windowStart = now\n_count = 0]
    B_CheckWindow -- No --> D_KeepWindow[Keep current window]
    C_ResetWindow --> E_Increment[_count++]
    D_KeepWindow --> E_Increment
    E_Increment --> F_Mod{_count % sampleRate == 1?}
    F_Mod -- Yes --> G_ReturnTrue[Return true]
    F_Mod -- No --> H_ReturnFalse[Return false]
    G_ReturnTrue --> I_End[End]
    H_ReturnFalse --> I_End[End]
Loading

File-Level Changes

Change Details Files
Add a thread-safe statistics appender that aggregates log counts, error metrics, and generates a textual report.
  • Implement StatisticsAppender as an ILogAppender that tracks total logs, error logs, per-level counts, and per-logger counts using concurrent data structures and interlocked operations.
  • Expose metrics such as TotalCount, ErrorCount, ErrorRate, StartTime, Uptime, and methods to query counts by level/logger and to get snapshots of all counts.
  • Provide Reset and Flush operations where Reset clears all counters and restarts uptime, and GenerateReport builds a human-readable metrics summary including top loggers.
GFramework.Core/logging/appenders/StatisticsAppender.cs
Add unit tests validating StatisticsAppender behavior and thread safety.
  • Verify counting of total logs, errors, rates, per-level and per-logger counts, uptime tracking, reset semantics, and report contents.
  • Ensure empty-state behavior is correct when no logs have been appended.
  • Stress-test Append and counters under concurrent usage and assert Flush is a no-op that does not throw.
GFramework.Core.Tests/logging/StatisticsAppenderTests.cs
Introduce a per-logger, time-window-based sampling filter to reduce high-frequency logging.
  • Implement SamplingFilter as an ILogFilter that keeps per-logger sampling state in a concurrent dictionary with an inner lock-protected state per logger.
  • Apply a sampleRate policy (log every Nth entry) within a configurable timeWindow, with window-based counter reset.
  • Validate constructor arguments for sampleRate and timeWindow and throw ArgumentException on invalid values.
GFramework.Core/logging/filters/SamplingFilter.cs
Add unit tests covering sampling filter behavior, validation, and concurrency characteristics.
  • Test deterministic sampling decisions for a given rate across sequential calls.
  • Verify that sampling windows reset after the configured time window elapses and that state is isolated per logger name.
  • Assert invalid constructor parameters throw, and run a concurrent sampling test to approximate the expected pass rate across many threads.
GFramework.Core.Tests/logging/SamplingFilterTests.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并给出了一些高层次的反馈:

  • StatisticsAppenderSamplingFilter 的测试都依赖 Thread.SleepDateTime.UtcNow 来做对时间敏感的断言,这在环境较慢或资源竞争激烈时容易变得不稳定;建议将时间抽象到一个接口或可注入的时钟后面,这样在测试中可以以确定性的方式推进时间,而不是依赖睡眠。
  • StatisticsAppender.Reset 中,_startTime 字段在没有任何同步的情况下被更新,而此时 StartTime/Uptime 可能在并发读取,这在 32 位运行时上可能导致“撕裂读”;建议将 ticks 存储在一个 long 中,并使用 Interlocked 或其他同步机制来实现原子更新。
给 AI 代理的提示
Please address the comments from this code review:

## Overall Comments
- Both `StatisticsAppender` and `SamplingFilter` tests rely on `Thread.Sleep` and `DateTime.UtcNow` for timing-sensitive assertions, which can be flaky under slow or contended environments; consider abstracting time behind an interface or injectable clock so you can deterministically advance time in tests instead of sleeping.
- In `StatisticsAppender.Reset` the `_startTime` field is updated without any synchronization while being concurrently read by `StartTime`/`Uptime`, which can lead to torn reads on 32‑bit runtimes; consider storing ticks in a `long` and using `Interlocked` or another synchronization mechanism for atomic updates.

## Individual Comments

### Comment 1
<location path="GFramework.Core/logging/filters/SamplingFilter.cs" line_range="13-22" />
<code_context>
+    private readonly ConcurrentDictionary<string, SamplingState> _samplingStates = new();
</code_context>
<issue_to_address>
**issue (bug_risk):** Consider how to prevent unbounded growth of per-logger sampling state.

Keying `_samplingStates` by `LoggerName` means each distinct name creates a `SamplingState` that is never reclaimed. In environments with dynamically generated logger names (per-tenant/request or with arbitrary tags), this can cause unbounded memory growth. If that’s a possibility, consider adding eviction or bounding logic (e.g., track last-used time and periodically prune, use an LRU policy, or fall back to a shared state once a maximum number of loggers is reached).
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得我们的评审有帮助,请考虑分享 ✨
请帮我变得更有用!请对每条评论点击 👍 或 👎,我会根据这些反馈改进之后的评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • Both StatisticsAppender and SamplingFilter tests rely on Thread.Sleep and DateTime.UtcNow for timing-sensitive assertions, which can be flaky under slow or contended environments; consider abstracting time behind an interface or injectable clock so you can deterministically advance time in tests instead of sleeping.
  • In StatisticsAppender.Reset the _startTime field is updated without any synchronization while being concurrently read by StartTime/Uptime, which can lead to torn reads on 32‑bit runtimes; consider storing ticks in a long and using Interlocked or another synchronization mechanism for atomic updates.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Both `StatisticsAppender` and `SamplingFilter` tests rely on `Thread.Sleep` and `DateTime.UtcNow` for timing-sensitive assertions, which can be flaky under slow or contended environments; consider abstracting time behind an interface or injectable clock so you can deterministically advance time in tests instead of sleeping.
- In `StatisticsAppender.Reset` the `_startTime` field is updated without any synchronization while being concurrently read by `StartTime`/`Uptime`, which can lead to torn reads on 32‑bit runtimes; consider storing ticks in a `long` and using `Interlocked` or another synchronization mechanism for atomic updates.

## Individual Comments

### Comment 1
<location path="GFramework.Core/logging/filters/SamplingFilter.cs" line_range="13-22" />
<code_context>
+    private readonly ConcurrentDictionary<string, SamplingState> _samplingStates = new();
</code_context>
<issue_to_address>
**issue (bug_risk):** Consider how to prevent unbounded growth of per-logger sampling state.

Keying `_samplingStates` by `LoggerName` means each distinct name creates a `SamplingState` that is never reclaimed. In environments with dynamically generated logger names (per-tenant/request or with arbitrary tags), this can cause unbounded memory growth. If that’s a possibility, consider adding eviction or bounding logic (e.g., track last-used time and periodically prune, use an LRU policy, or fall back to a shared state once a maximum number of loggers is reached).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread GFramework.Core/logging/filters/SamplingFilter.cs
- 将ILogAppender接口继承IDisposable以支持资源释放
- 添加ITimeProvider和SystemTimeProvider接口及实现类
- 创建FakeTimeProvider用于测试时间控制
- 修改SamplingFilter支持时间提供者注入和最大日志记录器数量限制
- 为SamplingFilter添加过期状态清理功能
- 修改StatisticsAppender使用时间提供者并实现IDisposable
- 更新相关单元测试以使用FakeTimeProvider进行精确时间控制
- 在测试类中为模拟追加器添加Dispose方法实现
@GeWuYou GeWuYou merged commit ae8c3e4 into main Mar 5, 2026
8 checks passed
@GeWuYou GeWuYou deleted the feat/logging-sampling-statistics- branch March 5, 2026 08:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant