Skip to content

feat(observability): add structured tags to all 10 counters (#35)#38

Merged
blehnen merged 9 commits into
mainfrom
feat/35-counter-tags
May 4, 2026
Merged

feat(observability): add structured tags to all 10 counters (#35)#38
blehnen merged 9 commits into
mainfrom
feat/35-counter-tags

Conversation

@blehnen

@blehnen blehnen commented May 4, 2026

Copy link
Copy Markdown
Owner

Closes #35.

Summary

  • All 10 counters on Meter "FrigateRelay" now emit structured tags so dashboards can pivot by subscription, camera, action, validator, reason, and component. Tag matrix matches the inventory in Add structured tags (camera/action/subscription) to FrigateRelay meters #35 with one structural amendment captured in .shipyard/phases/13/CONTEXT-13.md D5: events.received is tagged with camera + label only (the increment site is pre-subscription-match; tagging by subscription would have changed the counter's semantic meaning).
  • Tag construction is centralized via 10 new internal static void Increment* helpers on DispatcherDiagnostics so every call site is a one-liner and a future tag drift becomes a single-file edit. The eviction callback in ChannelActionDispatcher consumes the full evicted DispatchItem (no callback-shape change). Per-counter XML doc-comments list the tag set + cardinality rule + the "if you add a counter here, update docs/observability.md" reminder.
  • New tests/FrigateRelay.Host.Tests/Observability/CounterTagMatrixTests.cs adds 10 MeterListener-based tag-presence tests (one per counter). Each test asserts the expected tag KEYS, that VALUES match the test-scenario sentinels, and that event_id is absent — a per-test cardinality-bomb tripwire so a future contributor adding an 11th counter test cannot silently omit the rule.
  • Cross-phase change: the Phase-9 test ErrorsUnhandled_*_Untagged (which asserted errors.unhandled carried no tags) is renamed _TaggedWithComponent and updated to assert the new component=EventPump tag. Operators gain per-subsystem error triage; Phase 9's deliberately-tagless decision is intentionally overruled.
  • CHANGELOG [Unreleased] ### Added entry. docs/observability.md is referenced as forthcoming (lands in PR Document metrics + add sample Grafana dashboard (docs/observability.md) #36 / Wave 2 of Phase 13).

What this is NOT

Test plan

  • CI green on ubuntu-latest and windows-latest (Linux + Windows warnings-as-errors gate).
  • dotnet build FrigateRelay.sln -c Release reports 0 warnings, 0 errors locally.
  • bash .github/scripts/run-tests.sh --skip-integration reports 239 tests, 0 failures (up from 229 baseline = +10 from CounterTagMatrixTests).
  • git grep '"event_id"' src/FrigateRelay.Host/Dispatch/ returns empty (CONTEXT-13 hard rule: event_id is the cardinality-bomb tag and must never appear at an increment site).
  • git grep -nE 'App\.Metrics|OpenTracing|Jaeger\.' src/ still empty (architectural invariant unchanged).
  • Spot-check that aggregate Prometheus queries that don't filter on tags still return the same totals as v1.0.x scrapers — metric names persist, only series cardinality grows.

Follow-ups

  • ID-29 (filed): eviction-callback log captures stale plugin.Name from loop variable. Pre-existing closure-capture pattern surfaced by this refactor; counter tags themselves are correct. One-line fix in a follow-up PR.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Implemented structured telemetry with component-specific tags across all diagnostic counters for improved observability and monitoring.
    • Added component tag to unhandled error metrics to better categorize failures by subsystem.
  • Documentation

    • Advanced project roadmap to Phase 13: v1.1 observability and structural cleanup planning.
    • Updated project scope with v1.1 observability goals and verification criteria.
  • Tests

    • Added comprehensive tag validation tests to ensure metrics emit correct structured tags and prevent high-cardinality tagging.

blehnen and others added 8 commits May 4, 2026 09:05
Brainstorm output for the 6 open GitHub issues. v1.1 scope = #34/#35/#36
(refactor + structured tags + observability docs); #13/#14/#23 deferred
to v1.2. Three sequential PRs against main: #35#36 (hard-ordered),
#34 in any slot (independent).

PROJECT.md gains a "Post-v1.0 Scope — v1.1" section. ROADMAP.md gains
Phase 13 (matches Phase 11/12 style; 60 lines). HISTORY.md records the
brainstorm dialogue. STATE.json restored from HEAD (working tree had a
stale Phase 4 snapshot) and forward-rolled to phase 13 / ready.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 plans / 18 tasks across 3 waves:
- Wave 1 (PR #35): PLAN-1.1 helper-method refactor + PLAN-1.2 MeterListener tests
- Wave 2 (PR #36): PLAN-2.1 docs/dashboard + PLAN-2.2 compose stacks/Makefile
  + PLAN-2.3 drift test/RELEASING/CHANGELOG
- Wave 3 (PR #34): PLAN-3.1 BlueIrisUrlTemplate collapse + canonical-set test

Discussion captured 4 design decisions (D1-D4) + 4 OQ resolutions
(D5-D8 + OQ-2/4) in CONTEXT-13.md. RESEARCH.md (336 lines) documented
every counter call site, OTLP wiring, template-class shapes. Architect
generated all plans first dispatch. Verifier completeness PASS_WITH_NOTES
(3 caveats addressed inline); critique READY.

Plan artifacts in .shipyard/phases/13/ are intentionally local-only
per .shipyard/.gitignore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…th XML doc-comments

Adds one static helper per counter that builds the tag array internally,
so all tag key strings are declared only in DispatcherDiagnostics.cs (D1).
Updates each counter field doc-comment with canonical tags, cardinality
rule, and drift hint to docs/observability.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…atcherDiagnostics helpers

Replaces three inline .Add() calls with IncrementEventsReceived,
IncrementEventsMatched, and IncrementErrorsUnhandled. EventPump now
holds no tag-string literals; IncrementErrorsUnhandled adds the
previously missing "component" tag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…DispatcherDiagnostics helpers

PLAN-1.1 Task 2. Replaces inline KeyValuePair[] arrays at every
counter increment site (events.matched, actions.dispatched/succeeded/failed,
validators.passed/rejected, dispatch.drops, dispatch.exhausted) with calls
to the per-counter helpers introduced in 14ec2f5. EventPump migrated in
d53c4a1.

The eviction callback's Drops.Add becomes IncrementDrops(evicted, "channel_full")
per CONTEXT-13 OQ-2 — the callback already has the full DispatchItem in scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ase 9 D9 → Phase 13 #35)

The Phase-9-era test ErrorsUnhandled_*_Untagged asserted errors.unhandled
emitted with no tags ("intentionally tagless (D3/D9)"). Phase 13 issue #35
explicitly tags the counter with `component` so operators can triage
unhandled errors by failing subsystem (EventPump vs dispatcher vs plugin).
Renamed to _TaggedWithComponent and assertion updated.

User confirmed Phase 13 wins over the prior Phase 9 design intent during
build-time decision check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-presence tests

One test per counter in DispatcherDiagnostics; each directly invokes the
Increment* helper with sentinel values and asserts exact tag key set +
tag values. Forbidden tag `event_id` tripwire included on every test.
Closes the tag-matrix acceptance criteria for Phase 13 PLAN-1.2 Task 1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Documents that all 10 Meter "FrigateRelay" counters now emit structured
tags and that errors.unhandled gains a component tag replacing the
deliberately tagless Phase 9 behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@blehnen has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 41 minutes and 31 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 23485f8a-60a8-4f98-b8be-5b217af92304

📥 Commits

Reviewing files that changed from the base of the PR and between 6928164 and 4f2b874.

📒 Files selected for processing (2)
  • .shipyard/ROADMAP.md
  • tests/FrigateRelay.Host.Tests/Observability/CounterIncrementTests.cs
📝 Walkthrough

Walkthrough

Phase 13 advances Shipyard to v1.1 with observability focus. Structured tags are added to all FrigateRelay counters via new DispatcherDiagnostics helper methods. Existing increment sites are refactored to use helpers, centralizing tag construction. Counter tag matrix tests enforce tagging contracts.

Changes

Observability Infrastructure & Tagging

Layer / File(s) Summary
Planning & Versioning
.shipyard/HISTORY.md, .shipyard/PROJECT.md, .shipyard/ROADMAP.md, .shipyard/STATE.json, .shipyard/STATE.json.sha256
Shipyard advanced from Phase 12 to Phase 13. New v1.1 scope documented: structured counter tags, observability docs/dashboard/compose stack, and BlueIrisUrlTemplate allowlist consolidation. Phase 13 open questions captured (per-counter tag ownership, Makefile verify-observability, Grafana version, error-message contracts).
Changelog
CHANGELOG.md
Unreleased section updated to note 10 counters now instrumented with structured tags via DispatcherDiagnostics.Increment* helpers, event_id tag forbidden via CI grep, and errors.unhandled now carries component tag.
Helper Method Infrastructure
src/FrigateRelay.Host/Dispatch/DispatcherDiagnostics.cs
Added 10 internal static increment helpers encapsulating tag construction for each counter (e.g., IncrementActionsDispatched, IncrementErrorsUnhandled). Each helper derives tags from EventContext/DispatchItem and enforces no event_id. Updated XML docs with counter inventory, tag keys, and cardinality rules.
Dispatcher Integration
src/FrigateRelay.Host/Dispatch/ChannelActionDispatcher.cs
Updated 5 increment sites to call DispatcherDiagnostics helpers instead of manually constructing TagList: eviction → IncrementDrops(item, "channel_full"); enqueue → IncrementActionsDispatched(item); validators passed/rejected → IncrementValidatorsPassed/Rejected(item, name); success/exhausted/failure → IncrementActionsSucceeded/Exhausted/Failed(item).
Event Pump Integration
src/FrigateRelay.Host/EventPump.cs
Updated 3 increment sites to use helpers: received → IncrementEventsReceived(context); matched → IncrementEventsMatched(context, subName); unhandled error → IncrementErrorsUnhandled("EventPump").
Test Coverage
tests/FrigateRelay.Host.Tests/Observability/CounterIncrementTests.cs
Updated existing test method ErrorsUnhandled_Increments_OnPumpFault_Once_UntaggedErrorsUnhandled_Increments_OnPumpFault_Once_TaggedWithComponent to assert component tag is present with value "EventPump".
Tag Matrix Validation
tests/FrigateRelay.Host.Tests/Observability/CounterTagMatrixTests.cs
Added CounterTagMatrixTests suite with 10 test methods (one per counter) using MeterListener to capture and assert exact tag key sets, verify sentinel tag values, and forbid event_id. Includes helper methods for listener setup, tag extraction, and fixture construction.

Sequence Diagram

sequenceDiagram
    participant EventPump
    participant ChannelActionDispatcher
    participant DispatcherDiagnostics
    participant Meter as Counter/Meter<br/>(FrigateRelay)

    EventPump->>DispatcherDiagnostics: IncrementEventsReceived(context)
    DispatcherDiagnostics->>Meter: Add(1, tags: camera, label)
    
    EventPump->>DispatcherDiagnostics: IncrementEventsMatched(context, sub)
    DispatcherDiagnostics->>Meter: Add(1, tags: subscription, camera, label)
    
    EventPump->>ChannelActionDispatcher: EnqueueAsync(event)
    ChannelActionDispatcher->>DispatcherDiagnostics: IncrementActionsDispatched(item)
    DispatcherDiagnostics->>Meter: Add(1, tags: subscription, camera, action)
    
    ChannelActionDispatcher->>ChannelActionDispatcher: Validate(item)
    ChannelActionDispatcher->>DispatcherDiagnostics: IncrementValidatorsPassed/Rejected(item, name)
    DispatcherDiagnostics->>Meter: Add(1, tags: subscription, camera, validator, action)
    
    ChannelActionDispatcher->>ChannelActionDispatcher: ExecuteAsync(plugin)
    alt Success
        ChannelActionDispatcher->>DispatcherDiagnostics: IncrementActionsSucceeded(item)
        DispatcherDiagnostics->>Meter: Add(1, tags: subscription, camera, action)
    else Exhausted
        ChannelActionDispatcher->>DispatcherDiagnostics: IncrementExhausted(item)
        DispatcherDiagnostics->>Meter: Add(1, tags: subscription, camera, action)
    else Failed
        ChannelActionDispatcher->>DispatcherDiagnostics: IncrementActionsFailed(item)
        DispatcherDiagnostics->>Meter: Add(1, tags: subscription, camera, action)
    end
    
    alt Channel Full
        ChannelActionDispatcher->>DispatcherDiagnostics: IncrementDrops(item, "channel_full")
        DispatcherDiagnostics->>Meter: Add(1, tags: subscription, camera, reason)
    end
    
    EventPump->>DispatcherDiagnostics: IncrementErrorsUnhandled("EventPump")
    DispatcherDiagnostics->>Meter: Add(1, tags: component)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐰 Hops with glee at metrics so fine,
Tags flow through each counter's line,
Camera, label, action unite,
No cardinality blight!
Observability shines so bright.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main change: adding structured tags to all 10 counters in the observability domain, which aligns with issue #35.
Description check ✅ Passed The PR description is comprehensive and follows the template structure with summary, type of change (new feature), detailed checklist items, test plan, and follow-ups, addressing all major aspects of the change.
Linked Issues check ✅ Passed All acceptance criteria from issue #35 are met: every increment site uses helper methods passing structured tags; 10 MeterListener tests assert tag presence and absence of event_id; XML doc-comments document tags and cardinality rules; and git grep verification is part of the test plan.
Out of Scope Changes check ✅ Passed All code changes align with issue #35 scope: adding tags to 10 counters, centralizing tag construction, and adding validation tests. Documentation-only updates to HISTORY/ROADMAP/PROJECT are ancillary to PR delivery and do not introduce out-of-scope features.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 41 minutes and 31 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/FrigateRelay.Host.Tests/Observability/CounterIncrementTests.cs (1)

202-207: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stale section comment contradicts the updated test.

The section header at line 203 still reads "with NO tags" but the test was renamed and the assertion now verifies component = "EventPump". Anyone reading the comment before the assertion body will be misled.

✏️ Proposed fix
-    // Test 8: errors.unhandled increments ONCE with NO tags when IEventSource throws
+    // Test 8: errors.unhandled increments ONCE with component="EventPump" tag when IEventSource throws
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/FrigateRelay.Host.Tests/Observability/CounterIncrementTests.cs` around
lines 202 - 207, Update the stale test section header comment that currently
says "with NO tags" to reflect the test's new behavior: change the header to
indicate the error is tagged with the component (e.g., "errors.unhandled
increments ONCE when IEventSource throws, TAGGED with component 'EventPump'") or
remove the misleading phrase; ensure this comment sits immediately above the
test method ErrorsUnhandled_Increments_OnPumpFault_Once_TaggedWithComponent so
it accurately describes the assertion that the counter is tagged with component
= "EventPump".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.shipyard/ROADMAP.md:
- Around line 415-417: Update the "Phase Count" section to reflect the new Phase
13: change the line that reads "**12 phases.** Within the 8–14 range requested.
Phases 1–2 parallelizable after the `.sln` exists; Phases 3–12 sequential." to
state "**13 phases.** Within the 8–15 range requested. Phases 1–2 parallelizable
after the `.sln` exists; Phases 3–13 sequential." so the total count and the
sequential phase range both match the added phase.

---

Outside diff comments:
In `@tests/FrigateRelay.Host.Tests/Observability/CounterIncrementTests.cs`:
- Around line 202-207: Update the stale test section header comment that
currently says "with NO tags" to reflect the test's new behavior: change the
header to indicate the error is tagged with the component (e.g.,
"errors.unhandled increments ONCE when IEventSource throws, TAGGED with
component 'EventPump'") or remove the misleading phrase; ensure this comment
sits immediately above the test method
ErrorsUnhandled_Increments_OnPumpFault_Once_TaggedWithComponent so it accurately
describes the assertion that the counter is tagged with component = "EventPump".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3412a27e-f740-41f8-aa2a-05225f166766

📥 Commits

Reviewing files that changed from the base of the PR and between 085283b and 6928164.

📒 Files selected for processing (11)
  • .shipyard/HISTORY.md
  • .shipyard/PROJECT.md
  • .shipyard/ROADMAP.md
  • .shipyard/STATE.json
  • .shipyard/STATE.json.sha256
  • CHANGELOG.md
  • src/FrigateRelay.Host/Dispatch/ChannelActionDispatcher.cs
  • src/FrigateRelay.Host/Dispatch/DispatcherDiagnostics.cs
  • src/FrigateRelay.Host/EventPump.cs
  • tests/FrigateRelay.Host.Tests/Observability/CounterIncrementTests.cs
  • tests/FrigateRelay.Host.Tests/Observability/CounterTagMatrixTests.cs

Comment thread .shipyard/ROADMAP.md Outdated
- ROADMAP.md "Phase Count" updated 12 → 13 to reflect the new
  v1.1 Observability + Cleanup phase (Phase 13).
- CounterIncrementTests.cs Test 8 section header updated to match
  the renamed `_TaggedWithComponent` test and its component=EventPump
  assertion (Phase 13 #35 supersedes Phase 9's tagless behavior).

Both stale-text findings; no behavioral or test-coverage changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@blehnen
blehnen merged commit b110bb3 into main May 4, 2026
13 checks passed
@blehnen
blehnen deleted the feat/35-counter-tags branch May 4, 2026 19:29
blehnen added a commit that referenced this pull request May 4, 2026
PR #38 (issue #35 counter tags) merged as b110bb3. Local main rebased
clean. Wave 2 worktree branched at .worktrees/feat-36-observability-docs
off origin/main; baseline 239/239 tests, 0 warnings.

Next: dispatch builders for PLAN-2.1 / 2.2 / 2.3 (PR #36 scope).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blehnen added a commit that referenced this pull request May 4, 2026
PR #38 (#35), PR #39 (#36), PR #40 (#34) all on origin/main.
242/242 tests, 0 warnings, all CodeRabbit findings addressed.

CHANGELOG [Unreleased] aggregates all three PRs' bullets — ready for
operator-cut v1.1.0 per RELEASING.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

Add structured tags (camera/action/subscription) to FrigateRelay meters

1 participant