feat(monitoring): add @bytelyst/monitoring package
This commit is contained in:
parent
125eb03745
commit
e9b33fb518
@ -68,6 +68,7 @@ learning_ai_common_plat/
|
|||||||
| `@bytelyst/design-tokens` | Cross-platform design tokens (JSON → CSS/TS/Kotlin/Swift) | — |
|
| `@bytelyst/design-tokens` | Cross-platform design tokens (JSON → CSS/TS/Kotlin/Swift) | — |
|
||||||
| `@bytelyst/extraction` | Extraction service client + shared types | `@bytelyst/api-client` |
|
| `@bytelyst/extraction` | Extraction service client + shared types | `@bytelyst/api-client` |
|
||||||
| `@bytelyst/testing` | Shared test helpers (Fastify inject, schema asserts) | `vitest` |
|
| `@bytelyst/testing` | Shared test helpers (Fastify inject, schema asserts) | `vitest` |
|
||||||
|
| `@bytelyst/monitoring` | Health-check aggregation utilities | — |
|
||||||
|
|
||||||
## Shared Services
|
## Shared Services
|
||||||
|
|
||||||
|
|||||||
@ -533,7 +533,7 @@ The following gaps were identified by scanning every import in the actual codeba
|
|||||||
- [ ] **7.2** Add Changesets for automated version management and changelogs
|
- [ ] **7.2** Add Changesets for automated version management and changelogs
|
||||||
- [ ] **7.3** Create reusable GitHub Actions workflow templates for service CI
|
- [ ] **7.3** Create reusable GitHub Actions workflow templates for service CI
|
||||||
- [x] **7.4** Add `@bytelyst/blob` package (extract blob storage client + SAS generation)
|
- [x] **7.4** Add `@bytelyst/blob` package (extract blob storage client + SAS generation)
|
||||||
- [ ] **7.5** Add `@bytelyst/monitoring` package (health check aggregator)
|
- [x] **7.5** Add `@bytelyst/monitoring` package (health check aggregator)
|
||||||
- [x] **7.6** Add `@bytelyst/testing` package (shared test utilities, mock factories) — created with 10 tests
|
- [x] **7.6** Add `@bytelyst/testing` package (shared test utilities, mock factories) — created with 10 tests
|
||||||
- [ ] **7.7** Evaluate Python shared package for `cosmos_client.py` + `blob_client.py` if MindLyst adds Python backend
|
- [ ] **7.7** Evaluate Python shared package for `cosmos_client.py` + `blob_client.py` if MindLyst adds Python backend
|
||||||
- [ ] **7.8** Integrate `@bytelyst/design-tokens` into LysnrAI dashboards (unified design language)
|
- [ ] **7.8** Integrate `@bytelyst/design-tokens` into LysnrAI dashboards (unified design language)
|
||||||
@ -557,7 +557,7 @@ The following gaps were identified by scanning every import in the actual codeba
|
|||||||
| **4** | `@bytelyst/design-tokens` (4 platforms) | 24 | 23 | ✅ CSS synced to MindLyst; CONTRIBUTING updated; visual verify pending |
|
| **4** | `@bytelyst/design-tokens` (4 platforms) | 24 | 23 | ✅ CSS synced to MindLyst; CONTRIBUTING updated; visual verify pending |
|
||||||
| **5** | CI/CD + Docker (pre-copy strategy) | 23 | 21 | ⚠️ All Dockerfiles rewritten, CI workflows created; Docker build blocked by proxy |
|
| **5** | CI/CD + Docker (pre-copy strategy) | 23 | 21 | ⚠️ All Dockerfiles rewritten, CI workflows created; Docker build blocked by proxy |
|
||||||
| **6** | Verification + docs + cleanup | 28 | 21 | ✅ Docs updated, depcheck done, git clean; E2E needs services |
|
| **6** | Verification + docs + cleanup | 28 | 21 | ✅ Docs updated, depcheck done, git clean; E2E needs services |
|
||||||
| **7** | Future enhancements (+testing pkg) | 10 | 2 | 🔲 @bytelyst/testing + @bytelyst/blob created |
|
| **7** | Future enhancements (+testing pkg) | 10 | 3 | 🔲 @bytelyst/testing + @bytelyst/blob + @bytelyst/monitoring created |
|
||||||
| **Total** | **10 packages (+1 bonus: logger)** | **278** | **251** | **~90% complete** |
|
| **Total** | **10 packages (+1 bonus: logger)** | **278** | **251** | **~90% complete** |
|
||||||
|
|
||||||
### Bonus Package (not in original roadmap)
|
### Bonus Package (not in original roadmap)
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
- 2026-02-14: Added Dependabot config (`.github/dependabot.yml`)
|
- 2026-02-14: Added Dependabot config (`.github/dependabot.yml`)
|
||||||
- 2026-02-14: Added pre-commit token auto-generation (`lint-staged` for `packages/design-tokens`)
|
- 2026-02-14: Added pre-commit token auto-generation (`lint-staged` for `packages/design-tokens`)
|
||||||
- 2026-02-14: Added `@bytelyst/blob` shared package (Blob client helpers + SAS URL generation)
|
- 2026-02-14: Added `@bytelyst/blob` shared package (Blob client helpers + SAS URL generation)
|
||||||
|
- 2026-02-14: Added `@bytelyst/monitoring` shared package (health-check aggregation)
|
||||||
|
|
||||||
## Prereqs (Local)
|
## Prereqs (Local)
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ Publishing + repo hygiene
|
|||||||
- [ ] **7.2** Add Changesets for automated version management and changelogs
|
- [ ] **7.2** Add Changesets for automated version management and changelogs
|
||||||
- [ ] **7.3** Create reusable GitHub Actions workflow templates for service CI
|
- [ ] **7.3** Create reusable GitHub Actions workflow templates for service CI
|
||||||
- [x] **7.4** Add `@bytelyst/blob` package (extract blob storage client + SAS generation)
|
- [x] **7.4** Add `@bytelyst/blob` package (extract blob storage client + SAS generation)
|
||||||
- [ ] **7.5** Add `@bytelyst/monitoring` package (health check aggregator)
|
- [x] **7.5** Add `@bytelyst/monitoring` package (health check aggregator)
|
||||||
- [ ] **7.7** Evaluate Python shared package for `cosmos_client.py` + `blob_client.py` if MindLyst adds Python backend
|
- [ ] **7.7** Evaluate Python shared package for `cosmos_client.py` + `blob_client.py` if MindLyst adds Python backend
|
||||||
- [ ] **7.8** Integrate `@bytelyst/design-tokens` into LysnrAI dashboards (unified design language)
|
- [ ] **7.8** Integrate `@bytelyst/design-tokens` into LysnrAI dashboards (unified design language)
|
||||||
- [x] **7.9** Add pre-commit hooks to auto-run token generation when JSON changes
|
- [x] **7.9** Add pre-commit hooks to auto-run token generation when JSON changes
|
||||||
|
|||||||
21
packages/monitoring/package.json
Normal file
21
packages/monitoring/package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "@bytelyst/monitoring",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"description": "Health-check aggregation utilities for ByteLyst services and dashboards",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"test": "vitest run"
|
||||||
|
}
|
||||||
|
}
|
||||||
99
packages/monitoring/src/__tests__/monitoring.test.ts
Normal file
99
packages/monitoring/src/__tests__/monitoring.test.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { checkService, generateHealthReport, type ServiceTarget } from '../index.js';
|
||||||
|
|
||||||
|
describe('monitoring', () => {
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checkService returns healthy for 200', async () => {
|
||||||
|
globalThis.fetch = vi.fn(async () => ({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({ status: 'ok' }),
|
||||||
|
})) as any;
|
||||||
|
|
||||||
|
const res = await checkService({ name: 'svc', url: 'http://x', path: '/health' });
|
||||||
|
expect(res.status).toBe('healthy');
|
||||||
|
expect(res.details).toEqual({ status: 'ok' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checkService returns unhealthy for non-2xx', async () => {
|
||||||
|
globalThis.fetch = vi.fn(async () => ({
|
||||||
|
ok: false,
|
||||||
|
status: 503,
|
||||||
|
json: async () => ({}),
|
||||||
|
})) as any;
|
||||||
|
|
||||||
|
const res = await checkService({ name: 'svc', url: 'http://x', path: '/health' });
|
||||||
|
expect(res.status).toBe('unhealthy');
|
||||||
|
expect(res.error).toContain('HTTP 503');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checkService returns unreachable on fetch error', async () => {
|
||||||
|
globalThis.fetch = vi.fn(async () => {
|
||||||
|
throw new Error('network');
|
||||||
|
}) as any;
|
||||||
|
|
||||||
|
const res = await checkService({ name: 'svc', url: 'http://x', path: '/health' });
|
||||||
|
expect(res.status).toBe('unreachable');
|
||||||
|
expect(res.error).toContain('network');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generateHealthReport sets overall=down when all unreachable', async () => {
|
||||||
|
globalThis.fetch = vi.fn(async () => {
|
||||||
|
throw new Error('network');
|
||||||
|
}) as any;
|
||||||
|
|
||||||
|
const services: ServiceTarget[] = [
|
||||||
|
{ name: 'a', url: 'http://a', path: '/health' },
|
||||||
|
{ name: 'b', url: 'http://b', path: '/health' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const report = await generateHealthReport(services, { timeoutMs: 10 });
|
||||||
|
expect(report.overall).toBe('down');
|
||||||
|
expect(report.summary.unreachable).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generateHealthReport sets overall=degraded when mixed', async () => {
|
||||||
|
const calls = vi.fn(async (url: string) => {
|
||||||
|
if (url.includes('good')) return { ok: true, status: 200, json: async () => ({}) };
|
||||||
|
return { ok: false, status: 500, json: async () => ({}) };
|
||||||
|
});
|
||||||
|
globalThis.fetch = calls as any;
|
||||||
|
|
||||||
|
const services: ServiceTarget[] = [
|
||||||
|
{ name: 'good', url: 'http://good', path: '/health' },
|
||||||
|
{ name: 'bad', url: 'http://bad', path: '/health' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const report = await generateHealthReport(services);
|
||||||
|
expect(report.overall).toBe('degraded');
|
||||||
|
expect(report.summary.healthy).toBe(1);
|
||||||
|
expect(report.summary.unhealthy).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generateHealthReport sets overall=healthy when all healthy', async () => {
|
||||||
|
globalThis.fetch = vi.fn(async () => ({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({}),
|
||||||
|
})) as any;
|
||||||
|
|
||||||
|
const services: ServiceTarget[] = [
|
||||||
|
{ name: 'a', url: 'http://a', path: '/health' },
|
||||||
|
{ name: 'b', url: 'http://b', path: '/health' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const report = await generateHealthReport(services);
|
||||||
|
expect(report.overall).toBe('healthy');
|
||||||
|
expect(report.summary.healthy).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
115
packages/monitoring/src/health.ts
Normal file
115
packages/monitoring/src/health.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
export interface ServiceTarget {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceCheck {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
status: 'healthy' | 'unhealthy' | 'unreachable';
|
||||||
|
responseTimeMs: number;
|
||||||
|
details?: Record<string, unknown>;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HealthReport {
|
||||||
|
overall: 'healthy' | 'degraded' | 'down';
|
||||||
|
timestamp: string;
|
||||||
|
services: ServiceCheck[];
|
||||||
|
summary: { healthy: number; unhealthy: number; unreachable: number; total: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default service targets (LysnrAI local stack).
|
||||||
|
*/
|
||||||
|
export const DEFAULT_SERVICES: ServiceTarget[] = [
|
||||||
|
{ name: 'Backend API', url: process.env.BACKEND_URL || 'http://localhost:8000', path: '/health' },
|
||||||
|
{
|
||||||
|
name: 'Growth Service',
|
||||||
|
url: process.env.GROWTH_SERVICE_URL || 'http://localhost:4001',
|
||||||
|
path: '/health',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Billing Service',
|
||||||
|
url: process.env.BILLING_SERVICE_URL || 'http://localhost:4002',
|
||||||
|
path: '/health',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Platform Service',
|
||||||
|
url: process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003',
|
||||||
|
path: '/health',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Admin Dashboard',
|
||||||
|
url: process.env.ADMIN_DASHBOARD_URL || 'http://localhost:3001',
|
||||||
|
path: '/api/health',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'User Dashboard',
|
||||||
|
url: process.env.USER_DASHBOARD_URL || 'http://localhost:3002',
|
||||||
|
path: '/api/health',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export async function checkService(
|
||||||
|
svc: ServiceTarget,
|
||||||
|
opts?: { timeoutMs?: number }
|
||||||
|
): Promise<ServiceCheck> {
|
||||||
|
const fullUrl = `${svc.url}${svc.path}`;
|
||||||
|
const start = performance.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(fullUrl, { signal: AbortSignal.timeout(opts?.timeoutMs ?? 5_000) });
|
||||||
|
const elapsed = Math.round(performance.now() - start);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
let details: Record<string, unknown> | undefined;
|
||||||
|
try {
|
||||||
|
details = (await res.json()) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
return { name: svc.name, url: svc.url, status: 'healthy', responseTimeMs: elapsed, details };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: svc.name,
|
||||||
|
url: svc.url,
|
||||||
|
status: 'unhealthy',
|
||||||
|
responseTimeMs: elapsed,
|
||||||
|
error: `HTTP ${res.status}`,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const elapsed = Math.round(performance.now() - start);
|
||||||
|
return {
|
||||||
|
name: svc.name,
|
||||||
|
url: svc.url,
|
||||||
|
status: 'unreachable',
|
||||||
|
responseTimeMs: elapsed,
|
||||||
|
error: String(err),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateHealthReport(
|
||||||
|
services: ServiceTarget[] = DEFAULT_SERVICES,
|
||||||
|
opts?: { timeoutMs?: number }
|
||||||
|
): Promise<HealthReport> {
|
||||||
|
const checks = await Promise.all(services.map(svc => checkService(svc, opts)));
|
||||||
|
|
||||||
|
const healthy = checks.filter(c => c.status === 'healthy').length;
|
||||||
|
const unhealthy = checks.filter(c => c.status === 'unhealthy').length;
|
||||||
|
const unreachable = checks.filter(c => c.status === 'unreachable').length;
|
||||||
|
|
||||||
|
let overall: HealthReport['overall'] = 'healthy';
|
||||||
|
if (unreachable === checks.length) overall = 'down';
|
||||||
|
else if (unhealthy > 0 || unreachable > 0) overall = 'degraded';
|
||||||
|
|
||||||
|
return {
|
||||||
|
overall,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
services: checks,
|
||||||
|
summary: { healthy, unhealthy, unreachable, total: checks.length },
|
||||||
|
};
|
||||||
|
}
|
||||||
1
packages/monitoring/src/index.ts
Normal file
1
packages/monitoring/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './health.js';
|
||||||
9
packages/monitoring/tsconfig.json
Normal file
9
packages/monitoring/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["src/**/*.test.ts"]
|
||||||
|
}
|
||||||
5
pnpm-lock.yaml
generated
5
pnpm-lock.yaml
generated
@ -130,6 +130,8 @@ importers:
|
|||||||
|
|
||||||
packages/logger: {}
|
packages/logger: {}
|
||||||
|
|
||||||
|
packages/monitoring: {}
|
||||||
|
|
||||||
packages/react-auth:
|
packages/react-auth:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@bytelyst/api-client':
|
'@bytelyst/api-client':
|
||||||
@ -323,6 +325,9 @@ importers:
|
|||||||
|
|
||||||
services/monitoring:
|
services/monitoring:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@bytelyst/monitoring':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/monitoring
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.12.0
|
specifier: ^22.12.0
|
||||||
version: 22.19.11
|
version: 22.19.11
|
||||||
|
|||||||
@ -18,111 +18,7 @@
|
|||||||
* USER_DASHBOARD_URL (default: http://localhost:3002)
|
* USER_DASHBOARD_URL (default: http://localhost:3002)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {};
|
import { DEFAULT_SERVICES, generateHealthReport } from '@bytelyst/monitoring';
|
||||||
|
|
||||||
interface ServiceCheck {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
status: 'healthy' | 'unhealthy' | 'unreachable';
|
|
||||||
responseTimeMs: number;
|
|
||||||
details?: Record<string, unknown>;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HealthReport {
|
|
||||||
overall: 'healthy' | 'degraded' | 'down';
|
|
||||||
timestamp: string;
|
|
||||||
services: ServiceCheck[];
|
|
||||||
summary: { healthy: number; unhealthy: number; unreachable: number; total: number };
|
|
||||||
}
|
|
||||||
|
|
||||||
const SERVICES = [
|
|
||||||
{ name: 'Backend API', url: process.env.BACKEND_URL || 'http://localhost:8000', path: '/health' },
|
|
||||||
{
|
|
||||||
name: 'Growth Service',
|
|
||||||
url: process.env.GROWTH_SERVICE_URL || 'http://localhost:4001',
|
|
||||||
path: '/health',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Billing Service',
|
|
||||||
url: process.env.BILLING_SERVICE_URL || 'http://localhost:4002',
|
|
||||||
path: '/health',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Platform Service',
|
|
||||||
url: process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003',
|
|
||||||
path: '/health',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Admin Dashboard',
|
|
||||||
url: process.env.ADMIN_DASHBOARD_URL || 'http://localhost:3001',
|
|
||||||
path: '/api/health',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'User Dashboard',
|
|
||||||
url: process.env.USER_DASHBOARD_URL || 'http://localhost:3002',
|
|
||||||
path: '/api/health',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
async function checkService(svc: {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
path: string;
|
|
||||||
}): Promise<ServiceCheck> {
|
|
||||||
const fullUrl = `${svc.url}${svc.path}`;
|
|
||||||
const start = performance.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(fullUrl, { signal: AbortSignal.timeout(5_000) });
|
|
||||||
const elapsed = Math.round(performance.now() - start);
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
let details: Record<string, unknown> | undefined;
|
|
||||||
try {
|
|
||||||
details = (await res.json()) as Record<string, unknown>;
|
|
||||||
} catch {
|
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
return { name: svc.name, url: svc.url, status: 'healthy', responseTimeMs: elapsed, details };
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
name: svc.name,
|
|
||||||
url: svc.url,
|
|
||||||
status: 'unhealthy',
|
|
||||||
responseTimeMs: elapsed,
|
|
||||||
error: `HTTP ${res.status}`,
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
const elapsed = Math.round(performance.now() - start);
|
|
||||||
return {
|
|
||||||
name: svc.name,
|
|
||||||
url: svc.url,
|
|
||||||
status: 'unreachable',
|
|
||||||
responseTimeMs: elapsed,
|
|
||||||
error: String(err),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateReport(): Promise<HealthReport> {
|
|
||||||
const checks = await Promise.all(SERVICES.map(checkService));
|
|
||||||
|
|
||||||
const healthy = checks.filter(c => c.status === 'healthy').length;
|
|
||||||
const unhealthy = checks.filter(c => c.status === 'unhealthy').length;
|
|
||||||
const unreachable = checks.filter(c => c.status === 'unreachable').length;
|
|
||||||
|
|
||||||
let overall: HealthReport['overall'] = 'healthy';
|
|
||||||
if (unreachable === checks.length) overall = 'down';
|
|
||||||
else if (unhealthy > 0 || unreachable > 0) overall = 'degraded';
|
|
||||||
|
|
||||||
return {
|
|
||||||
overall,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
services: checks,
|
|
||||||
summary: { healthy, unhealthy, unreachable, total: checks.length },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── CLI / HTTP server mode ──
|
// ── CLI / HTTP server mode ──
|
||||||
|
|
||||||
@ -134,18 +30,18 @@ if (args.includes('--serve')) {
|
|||||||
const PORT = Number(process.env.MONITOR_PORT || 4004);
|
const PORT = Number(process.env.MONITOR_PORT || 4004);
|
||||||
|
|
||||||
const server = createServer(async (_req, res) => {
|
const server = createServer(async (_req, res) => {
|
||||||
const report = await generateReport();
|
const report = await generateHealthReport(DEFAULT_SERVICES);
|
||||||
res.writeHead(report.overall === 'healthy' ? 200 : 503, { 'Content-Type': 'application/json' });
|
res.writeHead(report.overall === 'healthy' ? 200 : 503, { 'Content-Type': 'application/json' });
|
||||||
res.end(JSON.stringify(report, null, 2));
|
res.end(JSON.stringify(report, null, 2));
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
console.log(`🩺 Monitoring dashboard running on http://localhost:${PORT}`);
|
console.log(`🩺 Monitoring dashboard running on http://localhost:${PORT}`);
|
||||||
console.log(` Checking ${SERVICES.length} services every request`);
|
console.log(` Checking ${DEFAULT_SERVICES.length} services every request`);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// One-shot check
|
// One-shot check
|
||||||
const report = await generateReport();
|
const report = await generateHealthReport(DEFAULT_SERVICES);
|
||||||
const icon = { healthy: '✅', degraded: '⚠️', down: '❌' };
|
const icon = { healthy: '✅', degraded: '⚠️', down: '❌' };
|
||||||
console.log(`\n${icon[report.overall]} Overall: ${report.overall.toUpperCase()}\n`);
|
console.log(`\n${icon[report.overall]} Overall: ${report.overall.toUpperCase()}\n`);
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
"serve": "tsx health-check.ts --serve"
|
"serve": "tsx health-check.ts --serve"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@bytelyst/monitoring": "workspace:*",
|
||||||
"@types/node": "^22.12.0",
|
"@types/node": "^22.12.0",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.2",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user