diff --git a/.ralph/fix_plan.md b/.ralph/fix_plan.md index 0de9e47..9526f3e 100644 --- a/.ralph/fix_plan.md +++ b/.ralph/fix_plan.md @@ -385,14 +385,14 @@ Spec: `.ralph/specs/phase-18-cli-cicd.md` --- -## Phase 23: Observability [PENDIENTE] +## Phase 23: Observability [COMPLETO] -- [ ] 23.1: Request correlation: requestId en CADA log entry via pino child logger -- [ ] 23.2: Structured error logging con contexto (userId, sessionId, etc.) -- [ ] 23.3: Liveness probe: GET /health/live -- [ ] 23.4: Readiness probe: GET /health/ready (DB + job queue check) -- [ ] 23.5: Startup probe: medir tiempo de arranque, loguear -- [ ] 23.6: Commit: `fase(23): observability and health probes` +- [x] 23.1: Request correlation: requestId en CADA log entry via pino child logger +- [x] 23.2: Structured error logging con contexto (userId, sessionId, etc.) +- [x] 23.3: Liveness probe: GET /health/live +- [x] 23.4: Readiness probe: GET /health/ready (DB + job queue check) +- [x] 23.5: Startup probe: medir tiempo de arranque, loguear +- [x] 23.6: Commit: `fase(23): observability and health probes` --- diff --git a/dist/api/middleware/errorHandler.js b/dist/api/middleware/errorHandler.js index 08768b1..37edea8 100644 --- a/dist/api/middleware/errorHandler.js +++ b/dist/api/middleware/errorHandler.js @@ -51,10 +51,12 @@ class RateLimitError extends AppError { } exports.RateLimitError = RateLimitError; function globalErrorHandler(err, req, res, _next) { - const logger = req.log; + const authReq = req; + const logger = authReq.log; + const userId = authReq.user?.id; if (err instanceof AppError && err.isOperational) { if (logger) { - logger.warn({ err, statusCode: err.statusCode }, err.message); + logger.warn({ err, statusCode: err.statusCode, userId }, err.message); } const body = { error: err.message, code: err.code }; if (err instanceof ValidationError && err.details !== undefined) { @@ -64,7 +66,7 @@ function globalErrorHandler(err, req, res, _next) { return; } if (logger) { - logger.error({ err }, 'Unhandled error'); + logger.error({ err, userId }, 'Unhandled error'); } else { console.error('Unhandled error', err); diff --git a/dist/main.js b/dist/main.js index 757fc56..fbe0913 100644 --- a/dist/main.js +++ b/dist/main.js @@ -84,6 +84,8 @@ const ReportWorker_1 = require("./jobs/workers/ReportWorker"); const server_1 = require("./api/server"); const SocketGateway_1 = require("./realtime/SocketGateway"); async function bootstrap() { + // Startup probe — measure total boot time + const startupAt = Date.now(); // 1. Config const config = (0, Config_1.loadConfig)(); // 2. Logger @@ -208,7 +210,8 @@ async function bootstrap() { await new Promise((resolve) => { httpServer.listen(config.port, config.host, resolve); }); - logger.info({ port: config.port, host: config.host }, 'ABE server ready'); + const startupMs = Date.now() - startupAt; + logger.info({ port: config.port, host: config.host, startupMs }, 'ABE server ready'); // 14. Graceful shutdown let shuttingDown = false; async function shutdown(signal) { diff --git a/src/api/middleware/errorHandler.ts b/src/api/middleware/errorHandler.ts index c2da710..c72d946 100644 --- a/src/api/middleware/errorHandler.ts +++ b/src/api/middleware/errorHandler.ts @@ -49,17 +49,27 @@ export class RateLimitError extends AppError { } } +type ExtendedReq = Request & { + log?: { + warn(obj: Record, msg: string): void; + error(obj: Record, msg: string): void; + }; + user?: { id: string; email: string; role: string }; +}; + export function globalErrorHandler( err: Error, req: Request, res: Response, _next: NextFunction, ): void { - const logger = (req as Request & { log?: { warn: Function; error: Function } }).log; + const authReq = req as ExtendedReq; + const logger = authReq.log; + const userId = authReq.user?.id; if (err instanceof AppError && err.isOperational) { if (logger) { - logger.warn({ err, statusCode: err.statusCode }, err.message); + logger.warn({ err, statusCode: err.statusCode, userId }, err.message); } const body: Record = { error: err.message, code: err.code }; if (err instanceof ValidationError && err.details !== undefined) { @@ -70,7 +80,7 @@ export function globalErrorHandler( } if (logger) { - logger.error({ err }, 'Unhandled error'); + logger.error({ err, userId }, 'Unhandled error'); } else { console.error('Unhandled error', err); } diff --git a/src/main.ts b/src/main.ts index ce22291..ef85441 100644 --- a/src/main.ts +++ b/src/main.ts @@ -93,6 +93,9 @@ import { createServer } from './api/server'; import { SocketGateway } from './realtime/SocketGateway'; async function bootstrap(): Promise { + // Startup probe — measure total boot time + const startupAt = Date.now(); + // 1. Config const config = loadConfig(); @@ -247,7 +250,8 @@ async function bootstrap(): Promise { await new Promise((resolve) => { httpServer.listen(config.port, config.host, resolve); }); - logger.info({ port: config.port, host: config.host }, 'ABE server ready'); + const startupMs = Date.now() - startupAt; + logger.info({ port: config.port, host: config.host, startupMs }, 'ABE server ready'); // 14. Graceful shutdown let shuttingDown = false;