feat(pkg): add Service Discovery metrics via MetricsFactory#2196
Conversation
Add a MetricsRecorder interface (decoupled from OTel), a no-op default, and an orNop nil-guard in pkg/servicediscovery, to be satisfied by a MetricsFactory-backed recorder and wired into the SD call sites in follow-up tasks. X-Lerian-Ref: 0x1
Implement MetricsRecorder over lib-observability MetricsFactory, emitting sd_register_total, sd_deregister_total{result}, sd_resolve_total{service,result} and sd_resolve_duration_milliseconds{service}. A nil factory yields the no-op recorder; record/build errors are logged at Warn and never propagated.
X-Lerian-Ref: 0x1
sd_resolve_duration_milliseconds declared unit ms but relied on the factory's name-based bucket auto-selection, which returns seconds-scaled DefaultLatencyBuckets for duration metrics, sending ms values to the overflow bucket. Set explicit ms bucket boundaries. Strengthen the recorder tests to observe emitted metric names, values, labels and histogram bounds via a manual reader, and track stub invocations per method. X-Lerian-Ref: 0x1
NewRunnable takes a MetricsRecorder; Run records one register-initiated on RegisterAsync and a deregister{result} outcome on shutdown. Call sites pass nil for now (guarded by orNop) until the bootstraps thread the real recorder.
X-Lerian-Ref: 0x1
Refactor ResolveAuthHost to pass an empty fallback to the registry so a consul hit, a static fallback and an error are distinguishable, and record sd_resolve_total{service,result} plus sd_resolve_duration_milliseconds{service}. The returned auth host is byte-identical to prior behavior. Call sites pass nil until the bootstraps thread the real recorder.
X-Lerian-Ref: 0x1
Build the Service Discovery metrics recorder gated on SD-enabled (no-op when disabled) and thread it through wireServiceDiscovery/ResolveAuthHost and NewRunnable, so register/deregister/resolve metrics flow only when discovery is on. Extract metricsFactory before the SD wiring. X-Lerian-Ref: 0x1
Build the Service Discovery metrics recorder gated on SD-enabled (no-op when disabled) and thread it through wireServiceDiscovery/ResolveAuthHost and NewRunnable, so register/deregister/resolve metrics flow only when discovery is on. Move telemetry init above the SD wiring, preserving the SD-before-streaming ordering. X-Lerian-Ref: 0x1
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds service-discovery metrics primitives, records resolve and lifecycle outcomes, and threads a recorder through CRM and Ledger bootstrap wiring into the discovery runnable. ChangesService discovery metrics instrumentation
✨ Finishing Touches✨ Simplify code
Comment |
Lerian Library Version Check
0 outdated | 5 current | 0 skipped | 0 unknown |
🔍 PR Validation Summary✅ PR Mergeable — no blocking failures
|
🔒 Security Scan Results —
|
| Stage | Status | Blocking? |
|---|---|---|
| Filesystem Scan | ✅ Clean | — |
| Docker Image Scan | ✅ Clean | — |
| Docker Hub Health Score | ✅ Clean | — |
| Pre-release Version Check | 🟡 No (advisory) |
Trivy
Filesystem Scan
✅ No vulnerabilities or secrets found.
Docker Image Scan
✅ No vulnerabilities found.
Docker Hub Health Score Compliance
✅ Policies — 4/4 met
| Policy | Status |
|---|---|
| Default non-root user | ✅ Passed |
| No fixable critical/high CVEs | ✅ Passed |
| No high-profile vulnerabilities | ✅ Passed |
| No AGPL v3 licenses | ✅ Passed |
Pre-release Version Check
🚫 Found 1 unstable version pin(s). Only stable releases (x.y.z) and SHA-based pins are allowed.
| File | Line | Content |
|---|---|---|
./go.mod |
152 | github.com/hashicorp/hcl v1.0.1-vault-7 // indirect |
Replace pre-release suffixes (
-alpha,-beta,-rc,-dev, etc.) with stable releases.
📊 Unit Test Coverage Report:
|
| Metric | Value |
|---|---|
| Overall Coverage | 85.3% ✅ PASS |
| Threshold | 80% |
Coverage by Package
| Package | Coverage |
|---|---|
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/http/in |
86.2% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/mongodb/onboarding |
66.7% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/mongodb/transaction |
66.7% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/account |
100.0% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/accounttype |
66.7% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/asset |
100.0% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/assetrate |
100.0% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/balance |
97.5% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/ledger |
100.0% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/operation |
90.7% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/operationroute |
100.0% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/organization |
100.0% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/portfolio |
100.0% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/segment |
100.0% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/transaction |
96.6% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/postgres/transactionroute |
100.0% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/rabbitmq |
91.5% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/adapters/redis/transaction/balance |
99.0% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/services/command |
85.9% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/services/query |
93.2% |
github.com/LerianStudio/midaz/v3/components/ledger/internal/services |
0.0% |
Generated by Go PR Analysis workflow
📊 Unit Test Coverage Report:
|
| Metric | Value |
|---|---|
| Overall Coverage | 89.2% ✅ PASS |
| Threshold | 80% |
Coverage by Package
| Package | Coverage |
|---|---|
github.com/LerianStudio/midaz/v3/components/crm/internal/adapters/http/in |
88.5% |
github.com/LerianStudio/midaz/v3/components/crm/internal/adapters/mongodb/alias |
73.5% |
github.com/LerianStudio/midaz/v3/components/crm/internal/adapters/mongodb/audit |
71.4% |
github.com/LerianStudio/midaz/v3/components/crm/internal/adapters/mongodb/encryption |
100.0% |
github.com/LerianStudio/midaz/v3/components/crm/internal/adapters/mongodb/holder |
78.7% |
github.com/LerianStudio/midaz/v3/components/crm/internal/services/encryption |
94.5% |
github.com/LerianStudio/midaz/v3/components/crm/internal/services |
97.0% |
Generated by Go PR Analysis workflow
🔒 Security Scan Results —
|
| Stage | Status | Blocking? |
|---|---|---|
| Filesystem Scan | ✅ Clean | — |
| Docker Image Scan | ✅ Clean | — |
| Docker Hub Health Score | ✅ Clean | — |
| Pre-release Version Check | 🟡 No (advisory) |
Trivy
Filesystem Scan
✅ No vulnerabilities or secrets found.
Docker Image Scan
✅ No vulnerabilities found.
Docker Hub Health Score Compliance
✅ Policies — 4/4 met
| Policy | Status |
|---|---|
| Default non-root user | ✅ Passed |
| No fixable critical/high CVEs | ✅ Passed |
| No high-profile vulnerabilities | ✅ Passed |
| No AGPL v3 licenses | ✅ Passed |
Pre-release Version Check
🚫 Found 1 unstable version pin(s). Only stable releases (x.y.z) and SHA-based pins are allowed.
| File | Line | Content |
|---|---|---|
./go.mod |
152 | github.com/hashicorp/hcl v1.0.1-vault-7 // indirect |
Replace pre-release suffixes (
-alpha,-beta,-rc,-dev, etc.) with stable releases.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@components/crm/internal/bootstrap/config.go`:
- Around line 312-328: The comment above wireServiceDiscovery is describing
internal behavior of NewMetricsFactoryRecorder rather than the contract of this
function. Rewrite the comment to focus only on what wireServiceDiscovery
guarantees about metricsRecorder selection based on discovery enabled/disabled,
and remove the parenthetical about nil factory fallback; keep the reference to
wireServiceDiscovery and pkgsd.NewMetricsFactoryRecorder limited to the
observable behavior at this call site.
In `@components/ledger/internal/bootstrap/config.go`:
- Around line 856-872: The comment in wireServiceDiscovery should not describe
the internal behavior of NewMetricsFactoryRecorder or other downstream
dependencies. Rewrite it to focus only on what this function does: build the
manager, choose a no-op recorder when discovery is disabled, and use the metrics
factory path when enabled. Keep the reference to pkgsd.BuildManager and
pkgsd.NewMetricsFactoryRecorder, but remove the call-graph-style parenthetical
about a nil factory degrading to no-op.
In `@pkg/servicediscovery/metrics_factory_test.go`:
- Around line 386-403: The test in TestSDResolveDurationBuckets is duplicating
the bucket slice literal that already exists as sdResolveDurationMsBuckets;
update the assertion to compare sdResolveDurationMs.Buckets against that shared
package variable instead of a re-declared literal. Keep the counter-descriptor
nil-bucket checks as-is, and use the existing symbol sdResolveDurationMsBuckets
so the expected values stay in sync automatically.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: 9ad86564-928e-42e0-b948-37af6f0a0be6
📒 Files selected for processing (14)
components/crm/internal/bootstrap/config.gocomponents/crm/internal/bootstrap/service.gocomponents/crm/internal/bootstrap/service_discovery_test.gocomponents/ledger/internal/bootstrap/config.gocomponents/ledger/internal/bootstrap/service.gocomponents/ledger/internal/bootstrap/service_discovery_test.gopkg/servicediscovery/auth_host_resolver.gopkg/servicediscovery/auth_host_resolver_test.gopkg/servicediscovery/metrics.gopkg/servicediscovery/metrics_factory.gopkg/servicediscovery/metrics_factory_test.gopkg/servicediscovery/metrics_test.gopkg/servicediscovery/runnable.gopkg/servicediscovery/runnable_test.go
The wireServiceDiscovery comment described NewMetricsFactoryRecorder's internal nil-factory fallback; keep the comment to the function's own contract per the repo comment guidelines. X-Lerian-Ref: 0x1
Description
Adds Service Discovery metrics via the existing
lib-observabilityMetricsFactory, in the sharedpkg/servicediscovery, wired into both theledgerandcrmbootstraps. Follow-up to the SD adoption (#2191).No-op when Service Discovery is disabled (
SD_ENABLED=false, the default) — zero overhead, no metric registration. Emission is a side-channel: recording failures are logged atWarnand never affect the request/boot path.Metrics emitted (bounded label cardinality — no host/address/error-detail labels):
sd_register_totalsd_deregister_totalresult(ok|error)sd_resolve_totalservice,result(resolved|fallback|error)sd_resolve_duration_millisecondsserviceDesign notes:
MetricsRecorderinterface (decoupled from OTel) + aNopMetricsRecorderdefault keep the SD call sites unit-testable and zero-cost when disabled; aMetricsFactory-backed recorder satisfies it.sd_register_totalcounts registration initiations only —RegisterAsyncis fire-and-forget with no success hook, so a successresultwould be dishonest.ResolveAuthHostnow passes an empty fallback to the registry so a consul hit, a static fallback, and an error are distinguishable (resolved/fallback/error). The returned auth host is byte-identical to prior behavior.*duration*metrics, which would otherwise send ms values to the overflow bucket.Type of Change
feat: New feature or capabilityBreaking Changes
None. Opt-in / no-op by default; the auth-host resolution result is byte-identical to prior behavior.
Testing
make testpassesmake test-intpasses (except 1 pre-existing, unrelated failure:TestIntegration_AuditRepo_Create_BuildsAllSixIndexesincrm/mongodb/audit, a mongo-driver-v2 bson-decode issue — verified failing identically on cleandevelop; that package is not in this diff)make lintpasses (golangci-lint v2.4.0, 0 issues)go vet/go mod verifycleanTest evidence / Actions run: Full Ring dev-cycle — Gate 0 (TDD RED→GREEN, coverage 88.9–91.1%), Gate 8 (10 reviewers incl. obs), Gate 9 (acceptance).
Architectural Checklist
panic()in production paths%winternal/bootstrapRelated Issues
Closes #