Skip to content

feat(api): add a /health endpoint for monitoring - #8

Merged
greymag merged 3 commits into
masterfrom
feature/40.health-endpoint
Sep 9, 2026
Merged

feat(api): add a /health endpoint for monitoring#8
greymag merged 3 commits into
masterfrom
feature/40.health-endpoint

Conversation

@sonny-ns5

@sonny-ns5 sonny-ns5 commented Sep 9, 2026

Copy link
Copy Markdown

Что изменилось

Появился GET /health — endpoint для мониторинга. 200, пока сервер может обратиться к хранилищу метаданных, 503, когда не может, так что проверка, читающая один только статус-код, уже корректна. Ответ не кэшируется.

{ "status": "ok", "checks": { "database": { "status": "ok", "latencyMs": 4 } } }
{ "status": "error", "checks": { "database": { "status": "error", "error": "TimeoutException" } } }

Как устроено

  • Проверка БД — реальный запрос, а не db.isConnected: база, переставшая отвечать, оставляет сокет, который по-прежнему считает себя открытым, поэтому только вернувшийся ответ доказывает, что хранилище пригодно.
  • Запрос — findOne с проекцией одного _id, а не count, который читал бы ноль документов и был бы очевидным выбором: mongo_dart 0.7.4 реализует count через legacy-опкод OP_QUERY, убранный в MongoDB 5.1, так что на 5.1+ проба всегда отвечала бы «база недоступна» (первый прогон CI это и показал на Mongo 7). findOne выбирает современный OP_MSG-путь, где сервер его поддерживает, и откатывается на legacy, где нет.
  • Проба под таймаутом (healthProbeTimeout, 5 с по умолчанию). Без него подвисшая Mongo даёт не упавшую проверку, а повисший запрос, который мониторинг прочитает как сетевую проблему, а не как проблему базы.
  • Упавшая проверка называет ошибку типом, а не сообщением: текст ошибки драйвера содержит connection string с паролем, а этот маршрут публичный. Полная ошибка уходит в лог сервера.
  • MetaStore.checkHealth() добавлен с рабочей реализацией по умолчанию (queryPackages(size: 1)), а не абстрактным членом: MetaStore документирован в README как точка расширения, и сторонняя реализация не должна перестать компилироваться из-за пробы, которой не просила. MongoStore переопределяет её своей пробой (см. пункт ниже), результат в обоих случаях выбрасывается.
  • Путь уже был зарезервирован как RouteKind.public в classifyRoute — теперь на нём есть хендлер.

Про статистику

В ответе намеренно нет ни числа пакетов, ни версии сервера, ни uptime — обсуждали и решили не отдавать. Маршрут публичный, гейт для публичных маршрутов не резолвит credential, так что хендлер в принципе не знает, кто спрашивает, и ответ один для всех. Счётчик пакетов на таком endpoint — это число, которое любой может опрашивать раз в несколько секунд, а счётчик, снимаемый по расписанию, — это лента публикаций, а не статус.

Проверено

  • unpub: dart test — 641 тест зелёный на обеих версиях матрицы CI, MongoDB 5.0.6 и 7.0.40; dart analyze и dart format --set-exit-if-changed чисто.
  • Новые тесты — unpub/test/health_test.dart (10): 200 при отвечающем хранилище, 503 при падении и при зависании, ошибка только типом (с проверкой, что пароль из connection string не попал в тело), Cache-Control: no-store, точный набор ключей ответа на всех уровнях, ответ без сессии и тот же самый ответ при включённой авторизации. Плюс группа health check в unpub_test.dart против живой Mongo: открытое соединение пробу проходит, закрытое — бросает.

sonny-ns5 and others added 3 commits September 9, 2026 12:43
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mongo_dart 0.7.4 implements `count` over the legacy OP_QUERY opcode, which
MongoDB removed in 5.1, so `/health` reported the database unreachable on every
5.1-or-newer server. Only the 5.0.6 half of the CI matrix was green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Copilot AI 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.

🔵 Needs a closer look

It introduces a new public, network-facing endpoint that probes the database and must avoid leaking connection secrets, so it warrants final human sign-off.

Pull request overview

This PR adds a public GET /health endpoint to the in_pub Dart pub server for monitoring. The handler runs a real round-trip probe against the metadata store (bounded by a configurable timeout) and returns 200 with {"status":"ok",...} when the store answers, or 503 with a type-only error when it does not. A new MetaStore.checkHealth() extension point is introduced with a concrete default (queryPackages), overridden in MongoStore with a lightweight findOne projection to avoid the removed OP_QUERY count path on MongoDB 5.1+. The response is never cached and never reveals repository contents, keeping the endpoint safe to leave public.

Changes:

  • New /health handler in App plus a healthProbeTimeout field, generated route wiring in app.g.dart, and a MetaStore.checkHealth() contract with a MongoStore.findOne override.
  • Extensive tests (health_test.dart, plus a live-Mongo health check group in unpub_test.dart) covering healthy/failing/hanging stores, secret non-leakage, response shape, caching header, and auth-on/off parity.
  • Documentation updates in README.md, CHANGELOG.md, and CLAUDE.md.
File summaries
File Description
unpub/lib/src/app.dart Adds healthProbeTimeout field and the /health handler with status/body/error handling.
unpub/lib/src/app.g.dart Generated router registration for GET /health.
unpub/lib/src/meta_store.dart Adds concrete checkHealth() default so third-party stores keep compiling.
unpub/lib/src/mongo_store.dart Overrides checkHealth() with a findOne _id projection to avoid OP_QUERY.
unpub/test/health_test.dart New end-to-end tests for the endpoint's behavior and safety.
unpub/test/unpub_test.dart Live-Mongo probe tests for open vs closed connections.
unpub/README.md Documents the health check endpoint and its rationale.
unpub/CHANGELOG.md Records the new endpoint under an Unreleased section.
CLAUDE.md Adds "Before handing work over" verification guidance.
Review details

Files not reviewed (1)

  • unpub/lib/src/app.g.dart: Generated file
  • Files reviewed: 8/9 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

// the database unreachable on exactly the versions this repository is
// headed for. `findOne` picks the modern OP_MSG path where the server has
// one and falls back on the legacy path where it does not.
await db.collection(packageCollection).findOne(where.fields(['_id']));
@greymag
greymag merged commit 6ac17a1 into master Sep 9, 2026
4 checks passed
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.

3 participants