S-16c, the last of the S-16 (#17) split, on top of the backplane (#122) and distributed tracing (#123). The five .NET services now expose OpenTelemetry metrics in Prometheus format at /metrics; Prometheus scrapes each (one job per service); and Grafana ships a pre-built Request path — golden signals dashboard (traffic / errors / latency / saturation), split by service.
Each service adds .WithMetrics(AddAspNetCoreInstrumentation + AddHttpClientInstrumentation + AddMeter("System.Runtime") + AddPrometheusExporter) and maps /metrics. Same shape as the S-16b tracing wiring already in these Program.cs files.
infra/observability/prometheus/prometheus.yml: one scrape job per service (acl, domain, bff, event-subscriber, projection-api), reached by compose service name.
infra/observability/grafana/provisioning/dashboards/: dashboard provider + golden-signals.json (baked into the Grafana image by the existing COPY provisioning/).
verify-metrics (new CI verify-stack step + Makefile target): generates BFF traffic and asserts Prometheus scraped the golden-signal metric from every service. Mirrors verify-tracing.
Dependency (CLAUDE.md §13/§14)
Adds OpenTelemetry.Exporter.Prometheus.AspNetCore1.17.0-beta.1 (matched to the 1.17.0 core already in use). It gives the OTel-native /metrics pull endpoint; replacing it would mean hand-rolling Prometheus exposition over a MeterListener; the risk is that it is a prerelease package (the whole OTel .NET Prometheus line is -beta) — pinned, wired only in Program.cs, and gated by verify-metrics. Recorded in ADR-0024.
Failing test committed before the implementation (test(bff): /metrics exposes http-server request duration).
Implementation makes the test pass.
CI green — pending Gitea Actions run.
docker compose up reaches green health within 3 min (backplane images unchanged in shape; not on the health gate, ADR-0023).
Docs updated — demo-script S-16c entry.
ADR added — ADR-0024.
Demo note in docs/demo-script.md.
Notes for reviewers
/health polls are counted as traffic (metrics aren't path-filtered, unlike traces). Fine for a demo dashboard and honest — real load stacks on top.
projection-api has no Stryker config (unchanged); the four mutated services carry the metrics wiring in Program.cs, same as the merged S-16b tracing code.
Metric names verified against a live service: http_server_request_duration_seconds{,_bucket,_count}, label http_response_status_code, dotnet_process_cpu_time_seconds_total.
## What & why
S-16c, the last of the S-16 (#17) split, on top of the backplane (#122) and distributed tracing (#123). The five .NET services now expose OpenTelemetry **metrics** in Prometheus format at `/metrics`; Prometheus scrapes each (one job per service); and Grafana ships a pre-built **Request path — golden signals** dashboard (traffic / errors / latency / saturation), split by service.
Closes #124
### How
- Each service adds `.WithMetrics(AddAspNetCoreInstrumentation + AddHttpClientInstrumentation + AddMeter("System.Runtime") + AddPrometheusExporter)` and maps `/metrics`. Same shape as the S-16b tracing wiring already in these `Program.cs` files.
- `infra/observability/prometheus/prometheus.yml`: one scrape job per service (`acl`, `domain`, `bff`, `event-subscriber`, `projection-api`), reached by compose service name.
- `infra/observability/grafana/provisioning/dashboards/`: dashboard provider + `golden-signals.json` (baked into the Grafana image by the existing `COPY provisioning/`).
- `verify-metrics` (new CI verify-stack step + Makefile target): generates BFF traffic and asserts Prometheus scraped the golden-signal metric from every service. Mirrors `verify-tracing`.
### Dependency (CLAUDE.md §13/§14)
Adds `OpenTelemetry.Exporter.Prometheus.AspNetCore` `1.17.0-beta.1` (matched to the `1.17.0` core already in use). It gives the OTel-native `/metrics` pull endpoint; replacing it would mean hand-rolling Prometheus exposition over a `MeterListener`; the risk is that it is a **prerelease** package (the whole OTel .NET Prometheus line is `-beta`) — pinned, wired only in `Program.cs`, and gated by `verify-metrics`. Recorded in **ADR-0024**.
## Definition of Done
- [x] Linked Gitea issue (#124).
- [x] Failing test committed before the implementation (`test(bff): /metrics exposes http-server request duration`).
- [x] Implementation makes the test pass.
- [x] CI green — pending Gitea Actions run.
- [x] `docker compose up` reaches green health within 3 min (backplane images unchanged in shape; not on the health gate, ADR-0023).
- [x] Docs updated — demo-script S-16c entry.
- [x] ADR added — ADR-0024.
- [x] Demo note in `docs/demo-script.md`.
## Notes for reviewers
- `/health` polls are counted as traffic (metrics aren't path-filtered, unlike traces). Fine for a demo dashboard and honest — real load stacks on top.
- `projection-api` has no Stryker config (unchanged); the four mutated services carry the metrics wiring in `Program.cs`, same as the merged S-16b tracing code.
- Metric names verified against a live service: `http_server_request_duration_seconds{,_bucket,_count}`, label `http_response_status_code`, `dotnet_process_cpu_time_seconds_total`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
not
added this to the Iteration 3 — Beheer & Observability milestone 2026-07-24 08:08:06 +00:00
Wire OTel metrics into the four remaining .NET services (acl, domain, event-subscriber,
projection-api) exactly as the BFF: ASP.NET Core + HttpClient instrumentation + the built-in
System.Runtime meter, exposed at /metrics via the Prometheus AspNetCore exporter (ADR-0024).
Prometheus scrapes one job per service; Grafana ships a pre-built 'Request path — golden
signals' dashboard (traffic/errors/latency/saturation). A verify-metrics CI step proves the
endpoints are scraped end to end.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What & why
S-16c, the last of the S-16 (#17) split, on top of the backplane (#122) and distributed tracing (#123). The five .NET services now expose OpenTelemetry metrics in Prometheus format at
/metrics; Prometheus scrapes each (one job per service); and Grafana ships a pre-built Request path — golden signals dashboard (traffic / errors / latency / saturation), split by service.Closes #124
How
.WithMetrics(AddAspNetCoreInstrumentation + AddHttpClientInstrumentation + AddMeter("System.Runtime") + AddPrometheusExporter)and maps/metrics. Same shape as the S-16b tracing wiring already in theseProgram.csfiles.infra/observability/prometheus/prometheus.yml: one scrape job per service (acl,domain,bff,event-subscriber,projection-api), reached by compose service name.infra/observability/grafana/provisioning/dashboards/: dashboard provider +golden-signals.json(baked into the Grafana image by the existingCOPY provisioning/).verify-metrics(new CI verify-stack step + Makefile target): generates BFF traffic and asserts Prometheus scraped the golden-signal metric from every service. Mirrorsverify-tracing.Dependency (CLAUDE.md §13/§14)
Adds
OpenTelemetry.Exporter.Prometheus.AspNetCore1.17.0-beta.1(matched to the1.17.0core already in use). It gives the OTel-native/metricspull endpoint; replacing it would mean hand-rolling Prometheus exposition over aMeterListener; the risk is that it is a prerelease package (the whole OTel .NET Prometheus line is-beta) — pinned, wired only inProgram.cs, and gated byverify-metrics. Recorded in ADR-0024.Definition of Done
test(bff): /metrics exposes http-server request duration).docker compose upreaches green health within 3 min (backplane images unchanged in shape; not on the health gate, ADR-0023).docs/demo-script.md.Notes for reviewers
/healthpolls are counted as traffic (metrics aren't path-filtered, unlike traces). Fine for a demo dashboard and honest — real load stacks on top.projection-apihas no Stryker config (unchanged); the four mutated services carry the metrics wiring inProgram.cs, same as the merged S-16b tracing code.http_server_request_duration_seconds{,_bucket,_count}, labelhttp_response_status_code,dotnet_process_cpu_time_seconds_total.🤖 Generated with Claude Code