Repository navigation
feat(backend): Prometheus /metrics endpoint + request middleware - #875
Conversation
PR C of the observability rollout. Sentry was already wired in bootstrap.ts; this adds the Prometheus scrape surface to match the bot (PR #873). Endpoint - GET /metrics → Prometheus text exposition (mounted on the same Express app, before the /api rate limiter, no auth — restrict at the network layer) - METRICS_DISABLED=true skips the route entirely Metrics - lucky_backend_http_requests_total{method,route,status}: per-request counter labelled with the resolved Express route TEMPLATE (e.g. /api/guilds/:guildId), not the raw path, so cardinality stays bounded - lucky_backend_http_request_duration_seconds{method,route,status}: histogram with buckets tuned for typical API latencies (5ms – 10s) - lucky_backend_http_server_errors_total{method,route}: separate 5xx counter for easier alerting - prom-client defaults: process_cpu, eventloop lag, heap, GC, etc. Middleware - New metricsMiddleware records duration on response 'finish'. Mounted before express.json so it observes the full server time, not just handler time. Unmatched routes (404s, static) collapse to "unmatched" to keep cardinality finite. Tests - metrics.test.ts (middleware): records counters/histograms on response finish; 5xx-only error counter; route template label; unmatched + baseUrl fallbacks - metrics.test.ts (route): /metrics serves text exposition with default Node metrics + service label; METRICS_DISABLED no-op - All 72 existing middleware tests still pass Next: PR D (frontend @sentry/react) and PR E (Grafana dashboards using these metrics + lucky_bot_guilds_total from #873).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (8)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
LucasSantana-Dev has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
|
Failed to generate code suggestions for PR |
|
Size Change: 0 B Total Size: 422 kB ℹ️ View Unchanged
|
) ## Summary Fixes #874 by: 1. **Bumping the pinned ref of the org reusable \`quality.yml\`** to the merge commit of [LucasSantana-Dev/.github#2](LucasSantana-Dev/.github#2). That PR adds \`GITHUB_TOKEN\` + \`YOUTUBE_DL_SKIP_DOWNLOAD=true\` to the install step in the \`lint\` and \`deadcode\` jobs, eliminating the 60 req/hr unauthenticated cap that's been failing PRs since the observability rollout started. 2. **Applying the same env vars to local \`release.yml\`** for parity. Release builds don't strictly need the yt-dlp binary, so skipping the fetch is harmless. ## Why this scope | Workflow | npm ci flags | Status | |---|---|---| | \`ci.yml\` | \`--legacy-peer-deps --ignore-scripts\` | already safe | | \`sonarcloud.yml\` | \`--legacy-peer-deps --ignore-scripts\` | already safe | | \`bundle-size.yml\` | runs postinstall | already sets both env vars | | \`release.yml\` | bare \`npm ci\` | **fixed here** | | reusable \`quality.yml\` | runs postinstall | **fixed via SHA bump** | ## Closes - #874 ## Unblocks - PR #873 (bot observability metrics) - PR #875 (backend observability) - PR #876 (frontend Sentry) - PR #877 (paired Lucky-side network for homelab #135) ## Test plan - [ ] \`quality / Lint\` passes on this PR's first run - [ ] After merge: re-run failed \`quality / Lint\` on #873 / #875 / #876 — they should pass without retry
There was a problem hiding this comment.
LucasSantana-Dev has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
|
) ## Summary Adds the `lucky-monitoring` Docker network and joins both `bot` and `backend` services to it, so the homelab Prometheus container can scrape: - `lucky-bot:9091/metrics` (added in #873) - `lucky-backend:5000/metrics` (added in #875) …without exposing those ports to the host. Lucky owns/creates the network; homelab joins it as `external: true` in `compose/monitoring.yml` (homelab PR #135). ## Why this PR exists separately PR #135 in the homelab repo declares `lucky-monitoring` as an **external** network. Without this Lucky-side change, the homelab stack would fail to start (network does not exist) and the new `LuckyBotDown` / `LuckyBackendDown` alerts would fire immediately on deploy because the scrape targets would not be DNS-resolvable. ## Deploy order 1. Merge this PR (creates the network on the Lucky compose project on next deploy) 2. Redeploy Lucky → `docker network ls` shows `lucky-monitoring` 3. Merge homelab #135 4. Redeploy homelab monitoring stack → Prometheus container joins the existing network 5. Verify in Grafana that `up{job=~"lucky-.*"} == 1` ## Test plan - [ ] CI passes (lint / Docker build / compose validate) - [ ] After deploy: `docker network inspect lucky-monitoring` shows both `lucky-bot` and `lucky-backend` attached - [ ] After homelab redeploy: Prometheus target page shows both Lucky targets as `UP` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Updated Docker Compose configuration to enable monitoring infrastructure connections for bot and backend services. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/LucasSantana-Dev/Lucky/pull/877?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary PR D of the observability rollout. Adds browser-side error reporting + performance tracing to the dashboard SPA, completing the data-layer coverage across bot (#873), backend (#875), and frontend. ## What - **`Sentry.init`** on app boot via `initSentry()` called from `main.tsx` before React mounts so early errors are captured - **`reactRouterV7BrowserTracingIntegration`** wired to `react-router-dom` v7 so route changes become Sentry transactions; LCP / CLS / INP captured automatically per transaction - **`replayIntegration`**: 10% of sessions, 100% of sessions that hit an error - **`captureFrontendException(err, context?)`** helper for catch-block reporting where the existing UI already surfaces the error state ## Configuration All optional Vite env vars — missing `VITE_SENTRY_DSN` makes `initSentry` a no-op (dev / preview / self-hosted without telemetry): | Env | Default | Meaning | |---|---|---| | `VITE_SENTRY_DSN` | — | Enables Sentry when set | | `VITE_SENTRY_ENVIRONMENT` | `import.meta.env.MODE` | dev / staging / prod | | `VITE_SENTRY_RELEASE` | `VITE_COMMIT_SHA` | Release identifier | | `VITE_SENTRY_TRACES_SAMPLE_RATE` | `0.1` | % of route changes traced | | `VITE_SENTRY_REPLAYS_SESSION_SAMPLE_RATE` | `0.1` | % of sessions replayed | | `VITE_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE` | `1.0` | % of error sessions replayed | ## Test plan - [x] `sentry.test.ts` (7 cases): - no-op without DSN (privacy-by-default for self-hosted) - `Sentry.init` shape with DSN + env + release - Router v7 + replay integrations wired - custom sample rates honored - default sample rates applied when env missing - `captureFrontendException` with and without extra context - [x] Frontend \`type:check\` clean - [x] Frontend \`lint\` clean (\`--max-warnings 0\`) - [ ] Manual: set \`VITE_SENTRY_DSN\` in \`.env.local\`, click around, confirm a route-change transaction lands in Sentry ## Deployment notes - Set \`VITE_SENTRY_DSN\` (and the rest of the \`VITE_SENTRY_*\` env, as needed) in the production build secrets / \`.env.production\` - Set \`VITE_SENTRY_RELEASE\` to the commit SHA from CI so errors are tagged with the right release - No DSN exposed at build time → completely off in self-hosted forks unless the operator opts in ## Known CI hazard The yt-dlp postinstall flake (#874) may trip the quality / Lint job. Re-run or land #874's fix first. ## Out of scope - Source maps upload via \`@sentry/vite-plugin\` — wire in PR E once the release pipeline is firmed up - Standalone Web Vitals package — Sentry's \`browserTracingIntegration\` already captures the core vitals on the first navigation transaction, no need for a duplicate path - **PR E**: Grafana dashboards + alert rules consuming the metrics from #873 + #875 + this PR's Sentry data
## Summary Cut v2.13.0 of Lucky. Bumps root + 4 workspaces from `2.11.0` → `2.13.0` (skipping the archived `2.12.0`) and promotes the CHANGELOG `[Unreleased]` block to `[2.13.0] - 2026-05-21`. ## Headline changes since v2.11.0 **Added** - Guild Automation Module Executor seam + AutoMessages pilot (#901) - Sentry React SDK + Router v7 tracing/replay on frontend (#876) - Prometheus `/metrics` on backend (#875) + bot (#873) - Guild join/leave history tracking (#872) - Trivy image-scan on docker-publish, Phase A audit-only (#883) - Self-hosted developer-tooling register on landing page (#868) **Changed** - Backend migrated to Zod 4 API (#919) — unblocked the CVE patch + ended the lockfile fragility loop - 3 bot circular-deps clusters broken (#885, #886, #888) **Fixed** - brace-expansion DoS + ws uninit-memory CVEs patched (#921) - nginx-alpine CVEs (#881) - CI postinstall rate limit + madge actionlint (#878, #905) Full list in CHANGELOG.md. ## Next steps (after this PR merges) 1. Open `release/v2.13.0 → main` PR with merge-commit method 2. Tag `v2.13.0` on the merge commit 3. Cut next `release` (homelab-style bare branch) — Lucky's bare-release migration is still pending the user removing protection on `release/v2.11.0`
## Release v2.13.0 Promotes \`release/v2.13.0\` to \`main\` for the v2.13.0 cut. **$AHEAD commits across all merged PRs since v2.11.0 ship.** (Skipping v2.12.0 — the branch existed but its work was rolled forward into v2.13.0 alongside this session's Zod migration + CVE patches + standards adoption.) ## Headline changes **Added** — Guild Automation Module Executor pilot (#901), Sentry frontend (#876), Prometheus metrics on bot+backend (#873, #875), guild membership history (#872), Trivy image-scan Phase A (#883), landing redesign (#868). **Changed** — Backend migrated to Zod 4 API (#919), 3 bot circular-deps clusters broken (#885/#886/#888). **Fixed** — brace-expansion + ws moderate CVEs (#921), nginx-alpine CVEs (#881), CI postinstall rate limit (#878), madge actionlint (#905). **Internal** — shared coverageThreshold gate (#909/#914), Feature-removal sweep checklist + dangerfile guard (#908/#913), monitoring network, AI-doc policy, 4 new ADRs. Full list in [CHANGELOG.md](./CHANGELOG.md). ## Merge method This PR should land via **merge commit** (NOT squash) to preserve the individual PR SHAs in main's history. After merge: 1. Tag \`v2.13.0\` on the merge commit 2. Create GitHub release with notes from CHANGELOG.md 3. Fast-forward \`release/v2.13.0\` to match the new main HEAD ## Test plan - [ ] All 30 checks green except infra (snyk plan cap) - [ ] Verify \`gh pr view 922 --json mergeCommit\` shows the chore-bump commit on release tip - [ ] After merge: confirm \`origin/main\` contains the full $AHEAD commits



Summary
PR C of the observability rollout. Sentry was already wired in
bootstrap.ts; this adds the Prometheus scrape surface to match the bot (#873).What
Endpoint
GET /metrics→ Prometheus text exposition (mounted on the existing Express app, before the/apirate limiter, no auth — restrict at the network layer)METRICS_DISABLED=trueskips the route entirelyMetrics
lucky_backend_http_requests_total{method,route,status}— per-request counter, labelled with the resolved Express route TEMPLATE (e.g./api/guilds/:guildId), not the raw path, so cardinality stays boundedlucky_backend_http_request_duration_seconds{method,route,status}— histogram with buckets tuned for typical API latencies (5ms – 10s)lucky_backend_http_server_errors_total{method,route}— separate 5xx counter for easier alertingprom-clientdefaults:process_cpu,eventloop_lag, heap, GC, etc.Middleware
metricsMiddlewarerecords duration on response'finish'. Mounted right afterrequestLogger, beforeexpress.json(), so it observes the full server time, not just handler time. Unmatched routes (404s, static) collapse to"unmatched"to keep cardinality finite.Test plan
metrics.test.ts(middleware, 6 cases): counters/histograms recorded on finish, 5xx-only error counter, route template label, unmatched + baseUrl fallbacksmetrics.test.ts(route, 2 cases): /metrics serves text exposition with default Node metrics +service="lucky-backend"label; METRICS_DISABLED no-optsc --noEmitcleancurl backend:5000/metrics | grep lucky_backend_http_requests_totalDeployment notes
PORT/WEBAPP_PORT, default 5000) — no extra port to open/metricsthrough nginx or expose the backend port internallyjob_name: lucky-backend,static_configs.targets: [backend:5000],metrics_path: /metricsKnown CI hazard
CI lint may show a transient
youtube-dl-exec postinstallfailure unrelated to this PR — tracked in #874 (GitHub API rate limit on shared runners). Re-run the affected check or land #874's fix first.Out of scope
@sentry/reactbrowser SDK + web-vitals + route-change trackinglucky_bot_guilds_totalfrom feat(bot): Prometheus /metrics endpoint + guild count gauge #873