infra(relationship-worker): 🧱 Standardize health check endpoints and logging configuration for improved observability
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
3fb42f3723
commit
c7358a3ef5
3 changed files with 109 additions and 0 deletions
40
services/relationship-worker/src/config.ts
Normal file
40
services/relationship-worker/src/config.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
export interface WorkerConfig {
|
||||
databaseUrl: string;
|
||||
modelBossUrl: string;
|
||||
modelBossApiKey: string | null;
|
||||
modelBossModel: string;
|
||||
healthPort: number;
|
||||
}
|
||||
|
||||
function required(name: string): string {
|
||||
const value = process.env[name];
|
||||
if (!value || value.trim().length === 0) {
|
||||
throw new Error(`Missing required env var: ${name}`);
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function optional(name: string, fallback: string): string {
|
||||
const value = process.env[name];
|
||||
return value && value.trim().length > 0 ? value.trim() : fallback;
|
||||
}
|
||||
|
||||
function intEnv(name: string, fallback: number): number {
|
||||
const raw = process.env[name];
|
||||
if (!raw) return fallback;
|
||||
const parsed = parseInt(raw, 10);
|
||||
if (isNaN(parsed)) {
|
||||
throw new Error(`Env var ${name} must be a number, got "${raw}"`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function loadConfig(): WorkerConfig {
|
||||
return {
|
||||
databaseUrl: required('DATABASE_URL'),
|
||||
modelBossUrl: required('MODEL_BOSS_URL'),
|
||||
modelBossApiKey: process.env.MODEL_BOSS_API_KEY?.trim() || null,
|
||||
modelBossModel: optional('MODEL_BOSS_MODEL', 'auto'),
|
||||
healthPort: intEnv('HEALTH_PORT', 3803),
|
||||
};
|
||||
}
|
||||
43
services/relationship-worker/src/health-server.ts
Normal file
43
services/relationship-worker/src/health-server.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { createServer, type Server } from 'node:http';
|
||||
|
||||
import { log } from './logger';
|
||||
|
||||
export interface HealthState {
|
||||
modelBossReachable: boolean;
|
||||
lastJobAt: Date | null;
|
||||
lastCompletedAt: Date | null;
|
||||
}
|
||||
|
||||
export function startHealthServer(port: number, getState: () => HealthState): Server {
|
||||
const server = createServer((req, res) => {
|
||||
if (req.url === '/health/live') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ status: 'live' }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.url === '/health' || req.url === '/health/ready') {
|
||||
const state = getState();
|
||||
const status = state.modelBossReachable ? 200 : 503;
|
||||
res.writeHead(status, { 'Content-Type': 'application/json' });
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
status: state.modelBossReachable ? 'ok' : 'degraded',
|
||||
modelBossReachable: state.modelBossReachable,
|
||||
lastJobAt: state.lastJobAt?.toISOString() ?? null,
|
||||
lastCompletedAt: state.lastCompletedAt?.toISOString() ?? null,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
});
|
||||
|
||||
server.listen(port, () => {
|
||||
log.info('Health server listening', { port });
|
||||
});
|
||||
|
||||
return server;
|
||||
}
|
||||
26
services/relationship-worker/src/logger.ts
Normal file
26
services/relationship-worker/src/logger.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
type Level = 'debug' | 'info' | 'warn' | 'error';
|
||||
|
||||
function emit(level: Level, message: string, data?: object): void {
|
||||
const entry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
level,
|
||||
service: 'relationship-worker',
|
||||
message,
|
||||
...(data ?? {}),
|
||||
};
|
||||
const line = JSON.stringify(entry);
|
||||
if (level === 'error') {
|
||||
console.error(line);
|
||||
} else if (level === 'warn') {
|
||||
console.warn(line);
|
||||
} else {
|
||||
console.log(line);
|
||||
}
|
||||
}
|
||||
|
||||
export const log = {
|
||||
debug: (message: string, data?: object) => emit('debug', message, data),
|
||||
info: (message: string, data?: object) => emit('info', message, data),
|
||||
warn: (message: string, data?: object) => emit('warn', message, data),
|
||||
error: (message: string, data?: object) => emit('error', message, data),
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue