Summary for nr-site-registry: instrument the backend so each API/GraphQL operation reports success vs failure, expose Prometheus-format metrics, and visualize them in Sysdig Monitor.
Related: Sysdig team setup (platform) · Operational metrics overview
- See per-operation health: successes, failures, and volume over time.
- Use Sysdig for dashboards (org standard on BC Gov OpenShift).
- Start in test; extend to prod after validation.
- Out of scope for v1: alerts, ticket automation, full SLO program.
| Layer | What it does |
|---|---|
| Application | Increments counters on each GraphQL operation (success/failure). |
/metrics endpoint |
Exposes counters in Prometheus text format. |
| Platform scrape | Sysdig/Prometheus pulls /metrics from backend pods (often via pod annotations). |
| Sysdig dashboards | PromQL panels: rates, totals, breakdown by operation. |
Frontend → GraphQL → NestJS backend
↓ (plugin records outcome)
GET /metrics
↓ (scrape every ~15–60s)
Sysdig Monitor → Dashboards
SysdigTeam CR (in <license-plate>-tools) grants access to Sysdig. It does not create application metrics. App metrics require code + scrape + dashboards.
- Sysdig team exists and is Ready in
<license-plate>-tools(e.g.c6a6e5-tools,e38158-tools). - Teammates use the same email in the CR as their SSO login to Sysdig.
- Access to test app namespace (e.g.
c6a6e5-test) for deploy and dashboard filters.
oc api-resources | grep -i sysdig
oc get sysdig-teams -n <license-plate>-tools
oc get sysdig-team <name> -n <license-plate>-tools -o yamlResource name on cluster is usually sysdig-teams (not sysdigteam). Kind in YAML: SysdigTeam, apiVersion: ops.gov.bc.ca/v1alpha1.
Stack: NestJS 10, GraphQL (Apollo Federation), backend/.
cd backend && npm install prom-clientFive metric families (prefix site_registry_):
| Metric | Type | Labels | Purpose |
|---|---|---|---|
site_registry_graphql_operations_total |
Counter | operation, outcome, error_class |
Success/failure per GraphQL operation |
site_registry_graphql_operation_duration_seconds |
Histogram | operation, outcome |
GraphQL latency per operation |
site_registry_http_requests_total |
Counter | method, route, status, outcome, error_class |
HTTP requests (excludes /metrics) |
site_registry_http_request_duration_seconds |
Histogram | method, route, outcome |
HTTP request latency |
site_registry_auth_failures_total |
Counter | reason, guard |
Wire 401/403 only |
GraphQL labels:
operation: GraphQLoperationName(e.g.searchSites), oranonymousif missing.outcome:successorfailure(GraphQL errors,success: false, or payloadhttpStatusCode≥ 400).error_class:naon success;client,server, orunknownon failure.
Auth labels: reason = unauthorized (401) or forbidden (403); guard = http today.
Avoid high-cardinality labels (user IDs, raw IDs in paths).
| File | Role |
|---|---|
backend/src/app/metrics/operational-metrics.service.ts |
Defines counters; recordGraphql(operation, outcome, errorClass) |
backend/src/app/metrics/graphql-metrics.plugin.ts |
Apollo @Plugin() — records on willSendResponse |
backend/src/app/metrics/metrics.module.ts |
Providers + exports |
backend/src/main.ts |
Register GET /metrics |
backend/src/app.module.ts |
Import MetricsModule |
- GraphQL can return HTTP 200 with
"errors": [...]— ingress HTTP metrics look healthy while the operation failed. - One Apollo plugin covers all operations consistently.
- No changes needed in individual resolver files for basic success/failure.
Hook: requestDidStart → willSendResponse → read operationName, check response.body.singleResult.errors, increment counter.
After NestFactory.create, register on the Express instance:
http.get('/metrics', async (_req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});Auth: Keycloak guards must not block the scraper. Use @Unprotected() on a metrics route or exclude /metrics from guards.
npm run start:dev
curl http://localhost:4007/metrics | grep site_registry
# Run GraphQL operations, then curl again — counters should increaseAdd pod annotations on the backend deployment (charts/app/templates/backend/templates/deployment.yaml):
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: {{ .Values.backend.service.targetPort | quote }}
prometheus.io/path: "/metrics"The chart sets PORT and containerPort from the same backend.service.targetPort. Confirm the live port on the pod (test has used 3000; local dev often 4007; charts/app/values.yaml may list another value per environment).
Deploy to test via the normal pipeline.
Helm release in test is typically nr-site-registry-test. Backend resources use the backend component name.
| Resource | Example name (test) |
|---|---|
| Namespace | c6a6e5-test (replace <license-plate>-test) |
| Deployment | nr-site-registry-test-backend |
| Pod | nr-site-registry-test-backend-<replicaset-hash>-<id> |
| Labels | app.kubernetes.io/name=backend, app.kubernetes.io/instance=nr-site-registry-test |
oc project c6a6e5-test
# List backend pods (pick one in Running, READY 1/1)
oc get pods -l app.kubernetes.io/name=backend,app.kubernetes.io/instance=nr-site-registry-test
# Example pod name (yours will differ after redeploy):
# nr-site-registry-test-backend-7f4dc577df-7fkhfBefore port-forward, read PORT and prometheus.io/port from the running pod (do not assume 8080):
POD=nr-site-registry-test-backend-7f4dc577df-7fkhf # replace with your pod name
oc describe pod "$POD" | grep -E 'prometheus.io|^\s+PORT:|containerPort|Liveness|Readiness'Expected on a metrics-enabled test deploy:
prometheus.io/scrape: trueprometheus.io/path: /metricsprometheus.io/port: 3000(must matchPORTenv and container port)PORT: 3000, probes on:3000
Use the container port from the previous step as the remote port (3000 in test). Pick any free local port (e.g. 13000) to avoid clashing with other apps on 8080/3000:
Terminal 1 (leave running):
oc project c6a6e5-test
oc port-forward pod/"$POD" -c nr-site-registry-test-backend 13000:3000You should see Forwarding from 127.0.0.1:13000 -> 3000. If you see connection refused on 8080, the app is not listening there — use 3000 (or whatever describe shows).
Terminal 2:
# Metric families (may be HELP/TYPE only until traffic exists)
curl -s http://127.0.0.1:13000/metrics | grep -E '^# (HELP|TYPE) site_registry'
# Full check with headers
curl -v http://127.0.0.1:13000/metrics | head -30Pass: HTTP/1.1 200 OK, Content-Type: text/plain, and five site_registry_* HELP lines.
- Use test Site Registry in the browser (search, open a site, logged in if possible).
- Re-run:
curl -s http://127.0.0.1:13000/metrics | grep site_registry_graphql_operations_total
# Sample lines only (no comments)
curl -s http://127.0.0.1:13000/metrics | grep -v '^#' | grep site_registryPass: Named operations appear, e.g. operation="searchSites",outcome="success". Low or no operation="anonymous" after real UI use.
Note: Many outcome="failure",error_class="client" lines are common when the API returns success: false (permissions, not logged in, empty cart). That is application classification, not broken metrics. Use Sysdig rate() and filter error_class for server/unknown when judging system health.
oc exec "$POD" -c nr-site-registry-test-backend -- \
sh -c 'wget -qO- http://127.0.0.1:3000/metrics 2>&1 | head -20'If this works but local port-forward fails, fix local port mapping or a stale pod name.
oc get svc -n c6a6e5-test | grep backend
oc port-forward -n c6a6e5-test svc/nr-site-registry-test-backend 13000:3000
curl -s http://127.0.0.1:13000/metrics | grep site_registryIf metrics work on the pod but not in Sysdig after ~30–60 minutes, ask the Teams channel OpenShift-howto-sysdig whether additional scrape config (e.g. ServiceMonitor) is required.
Prerequisite: Part 2 pod verification succeeds (curl → 200, counters after UI traffic).
- Log in to Sysdig Monitor (platform SSO).
- Open Explore / Metrics (Prometheus).
- Search:
site_registry_graphql_operations_total. - Filter by namespace
c6a6e5-test(or your<license-plate>-test), workloadnr-site-registry-test-backend, cluster.
If the metric does not appear, fix scrape/code before building dashboards.
- Note the time; run search + open a site in test UI.
- In Explore, run:
sum(rate(site_registry_graphql_operations_total{kube_namespace_name="c6a6e5-test"}[5m])) by (operation)
Pass: Non-zero rates for searchSites, findSiteBySiteId, or findSiteBySiteIdLoggedInUser around that time.
Replace namespace with yours (e.g. c6a6e5-test):
Failures (24h) — stat panel
sum(increase(site_registry_graphql_operations_total{outcome="failure", kube_namespace_name="c6a6e5-test"}[24h]))
Successes (24h) — stat panel
sum(increase(site_registry_graphql_operations_total{outcome="success", kube_namespace_name="c6a6e5-test"}[24h]))
Failure rate by operation — time series
sum(rate(site_registry_graphql_operations_total{outcome="failure", kube_namespace_name="c6a6e5-test"}[5m])) by (operation)
Success rate by operation — time series
sum(rate(site_registry_graphql_operations_total{outcome="success", kube_namespace_name="c6a6e5-test"}[5m])) by (operation)
Error percentage by operation
sum(rate(site_registry_graphql_operations_total{outcome="failure", kube_namespace_name="c6a6e5-test"}[5m])) by (operation)
/
sum(rate(site_registry_graphql_operations_total{kube_namespace_name="c6a6e5-test"}[5m])) by (operation)
Use Explore to confirm exact label names (kube_namespace_name, kube_workload_name, etc.) — they can vary slightly by platform.
Full panel catalog with titles, types, PromQL, and how to read each panel: Operational metrics dashboard — panel reference
Quick summary
| Section | Panels | Metric families |
|---|---|---|
| OVERVIEW | 1 | (text) |
| GRAPHQL HEALTH | 8 | graphql_operations_total, graphql_operation_duration_seconds |
| HTTP LAYER | 5 | http_requests_total, http_request_duration_seconds |
| AUTH | 2 | auth_failures_total |
Incident panel: Enquiry failures with error_class=~"server|unknown".
Dashboard scope: namespace c6a6e5-test, workload nr-site-registry-test-backend.
Default time range: Last 6 hours for rate[5m] charts; Last 24 hours for Number panels using increase[6h] / [24h].
| Area | Expected impact |
|---|---|
| GraphQL API / business logic | No change to contracts or behavior |
| Performance | Negligible (counter increment per request) |
| Risk | Plugin must not throw; /metrics must not be blocked by auth |
| Deploy | Normal backend release to test, then prod |
Rollback: redeploy previous image; remove annotations if needed.
-
oc describe podshowsprometheus.io/scrape,prometheus.io/path=/metrics, andprometheus.io/portmatchingPORT(e.g. 3000 inc6a6e5-test). -
oc port-forwardto that port (e.g.13000:3000) andcurlreturns 200 with allsite_registry_*HELP lines. - After UI traffic,
curlshowssite_registry_graphql_operations_totalsamples (e.g.searchSites,findSiteBySiteIdLoggedInUser). - Sysdig Explore finds the metric with
kube_namespace_name="c6a6e5-test"(or your test namespace). - Dashboard SiteRegistry-Test-Graphql with 4 sections and 16 panels (test).
- Doc updated with metric names and panel reference.
- Clone dashboard for prod (change namespace/workload only).
-
user_audiencelabel for external-user monitoring at go-live.
| Symptom | Likely cause |
|---|---|
port-forward → connection refused on 8080 inside pod |
Backend listens on another port (test: 3000). Run oc describe pod and forward LOCAL:3000. |
curl: (52) Empty reply from server |
Port-forward not running, wrong local port, or tunnel lost after refused backend port |
/metrics returns 401 |
Keycloak blocking scraper |
/metrics 404 |
Route not registered |
| Only HELP/TYPE, no sample lines | No traffic yet; use UI then curl again |
| Local counters OK, Sysdig empty | Missing/wrong scrape annotations or platform config; confirm prometheus.io/port matches listen port |
Only anonymous operation |
Clients not sending operationName |
Many failure / client on site-detail ops |
Often expected (success: false in JSON when not authorized or missing data) |
| Teammate can't see Sysdig team | Email in CR ≠ SSO login email; or CR not Reconciled |
Platform help: Microsoft Teams OpenShift-howto-sysdig
| Task | Owner |
|---|---|
Backend metrics + /metrics |
App team |
| Helm scrape annotations + test deploy | App / DevOps |
| Confirm scrape in Sysdig | Platform / Teams OpenShift-howto-sysdig if stuck |
| Dashboard panels | App team |
| SysdigTeam / access | Already in <license-plate>-tools (edit users via CR only) |
- Sysdig Monitor – team setup
- RED metrics: rate, errors, duration
- Operational metrics overview