feat(monitoring): add shared types for monitoring system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martin Porwoll 2026-02-15 00:11:33 +00:00
parent c7fd95d3da
commit 214e2ddde8
2 changed files with 241 additions and 0 deletions

148
src/lib/monitoring/types.ts Normal file
View file

@ -0,0 +1,148 @@
// === System Health ===
export interface SystemHealth {
cpuUsagePercent: number
memoryUsedMB: number
memoryTotalMB: number
memoryUsagePercent: number
diskUsedGB: number
diskTotalGB: number
diskUsagePercent: number
loadAvg1: number
loadAvg5: number
uptime: number // seconds
}
// === Service Statuses ===
export type ServiceStatusType = 'online' | 'warning' | 'offline'
export interface ProcessStatus {
status: ServiceStatusType
pid: number
memoryMB: number
uptimeSeconds: number
restarts: number
}
export interface PostgresqlStatus {
status: ServiceStatusType
connections: number
maxConnections: number
latencyMs: number
}
export interface PgBouncerStatus {
status: ServiceStatusType
activeConnections: number
waitingClients: number
poolSize: number
}
export interface RedisStatus {
status: ServiceStatusType
memoryUsedMB: number
connectedClients: number
opsPerSec: number
}
export interface ServiceStatuses {
payload: ProcessStatus
queueWorker: ProcessStatus
postgresql: PostgresqlStatus
pgbouncer: PgBouncerStatus
redis: RedisStatus
}
// === External Statuses ===
export interface SmtpStatus {
status: ServiceStatusType
lastCheck: string // ISO date
responseTimeMs: number
}
export type OAuthStatusType = 'ok' | 'expiring_soon' | 'expired' | 'error'
export interface OAuthTokenStatus {
status: OAuthStatusType
tokensTotal: number
tokensExpiringSoon: number
tokensExpired: number
}
export interface CronJobStatus {
lastRun: string // ISO date
status: 'ok' | 'failed' | 'unknown'
}
export interface CronStatuses {
communitySync: CronJobStatus
tokenRefresh: CronJobStatus
youtubeSync: CronJobStatus
}
export interface ExternalStatuses {
smtp: SmtpStatus
metaOAuth: OAuthTokenStatus
youtubeOAuth: OAuthTokenStatus
cronJobs: CronStatuses
}
// === Performance ===
export interface PerformanceMetrics {
avgResponseTimeMs: number
p95ResponseTimeMs: number
p99ResponseTimeMs: number
errorRate: number // 0-1
requestsPerMinute: number
}
// === Full Snapshot ===
export interface SystemMetrics {
timestamp: string // ISO date
system: SystemHealth
services: ServiceStatuses
external: ExternalStatuses
performance: PerformanceMetrics
}
// === SSE Events (discriminated union) ===
export type MonitoringEvent =
| { type: 'health'; data: SystemHealth }
| { type: 'service'; data: Partial<ServiceStatuses> }
| { type: 'alert'; data: AlertEvent }
| { type: 'log'; data: LogEvent }
| { type: 'performance'; data: PerformanceMetrics }
export interface AlertEvent {
id: string
ruleId: string
metric: string
value: number
threshold: number
severity: AlertSeverity
message: string
timestamp: string
}
export interface LogEvent {
id: string
level: LogLevel
source: LogSource
message: string
timestamp: string
context?: Record<string, unknown>
}
// === Enums as union types ===
export type AlertCondition = 'gt' | 'lt' | 'eq' | 'gte' | 'lte'
export type AlertSeverity = 'warning' | 'error' | 'critical'
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal'
export type LogSource = 'payload' | 'queue-worker' | 'cron' | 'email' | 'oauth' | 'sync'
// === Performance Tracker Entry ===
export interface PerformanceEntry {
timestamp: number // Date.now()
method: string
path: string
statusCode: number
durationMs: number
}

View file

@ -0,0 +1,93 @@
import { describe, it, expect } from 'vitest'
import type {
SystemMetrics,
MonitoringEvent,
AlertCondition,
LogLevel,
LogSource,
} from '@/lib/monitoring/types'
describe('Monitoring Types', () => {
it('SystemMetrics has all required sections', () => {
const metrics: SystemMetrics = {
timestamp: new Date().toISOString(),
system: {
cpuUsagePercent: 23,
memoryUsedMB: 4200,
memoryTotalMB: 8192,
memoryUsagePercent: 51.3,
diskUsedGB: 30,
diskTotalGB: 50,
diskUsagePercent: 60,
loadAvg1: 0.5,
loadAvg5: 0.8,
uptime: 1209600,
},
services: {
payload: {
status: 'online',
pid: 1234,
memoryMB: 512,
uptimeSeconds: 86400,
restarts: 0,
},
queueWorker: {
status: 'online',
pid: 5678,
memoryMB: 256,
uptimeSeconds: 86400,
restarts: 0,
},
postgresql: { status: 'online', connections: 12, maxConnections: 50, latencyMs: 2 },
pgbouncer: { status: 'online', activeConnections: 8, waitingClients: 0, poolSize: 20 },
redis: { status: 'online', memoryUsedMB: 48, connectedClients: 5, opsPerSec: 120 },
},
external: {
smtp: {
status: 'online',
lastCheck: new Date().toISOString(),
responseTimeMs: 180,
},
metaOAuth: { status: 'ok', tokensTotal: 2, tokensExpiringSoon: 1, tokensExpired: 0 },
youtubeOAuth: { status: 'ok', tokensTotal: 3, tokensExpiringSoon: 0, tokensExpired: 0 },
cronJobs: {
communitySync: { lastRun: new Date().toISOString(), status: 'ok' },
tokenRefresh: { lastRun: new Date().toISOString(), status: 'ok' },
youtubeSync: { lastRun: new Date().toISOString(), status: 'ok' },
},
},
performance: {
avgResponseTimeMs: 120,
p95ResponseTimeMs: 350,
p99ResponseTimeMs: 800,
errorRate: 0.02,
requestsPerMinute: 45,
},
}
expect(metrics.system.cpuUsagePercent).toBe(23)
expect(metrics.services.payload.status).toBe('online')
expect(metrics.external.smtp.status).toBe('online')
expect(metrics.performance.avgResponseTimeMs).toBe(120)
})
it('MonitoringEvent types are exhaustive', () => {
const events: MonitoringEvent['type'][] = ['health', 'service', 'alert', 'log', 'performance']
expect(events).toHaveLength(5)
})
it('AlertCondition covers all comparison operators', () => {
const conditions: AlertCondition[] = ['gt', 'lt', 'eq', 'gte', 'lte']
expect(conditions).toHaveLength(5)
})
it('LogLevel covers all severity levels', () => {
const levels: LogLevel[] = ['debug', 'info', 'warn', 'error', 'fatal']
expect(levels).toHaveLength(5)
})
it('LogSource covers all system components', () => {
const sources: LogSource[] = ['payload', 'queue-worker', 'cron', 'email', 'oauth', 'sync']
expect(sources).toHaveLength(6)
})
})