fase(23): observability and health probes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
- [x] 23.1: Request correlation: requestId en CADA log entry via pino child logger
|
||||||
- [ ] 23.2: Structured error logging con contexto (userId, sessionId, etc.)
|
- [x] 23.2: Structured error logging con contexto (userId, sessionId, etc.)
|
||||||
- [ ] 23.3: Liveness probe: GET /health/live
|
- [x] 23.3: Liveness probe: GET /health/live
|
||||||
- [ ] 23.4: Readiness probe: GET /health/ready (DB + job queue check)
|
- [x] 23.4: Readiness probe: GET /health/ready (DB + job queue check)
|
||||||
- [ ] 23.5: Startup probe: medir tiempo de arranque, loguear
|
- [x] 23.5: Startup probe: medir tiempo de arranque, loguear
|
||||||
- [ ] 23.6: Commit: `fase(23): observability and health probes`
|
- [x] 23.6: Commit: `fase(23): observability and health probes`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
8
dist/api/middleware/errorHandler.js
vendored
8
dist/api/middleware/errorHandler.js
vendored
@@ -51,10 +51,12 @@ class RateLimitError extends AppError {
|
|||||||
}
|
}
|
||||||
exports.RateLimitError = RateLimitError;
|
exports.RateLimitError = RateLimitError;
|
||||||
function globalErrorHandler(err, req, res, _next) {
|
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 (err instanceof AppError && err.isOperational) {
|
||||||
if (logger) {
|
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 };
|
const body = { error: err.message, code: err.code };
|
||||||
if (err instanceof ValidationError && err.details !== undefined) {
|
if (err instanceof ValidationError && err.details !== undefined) {
|
||||||
@@ -64,7 +66,7 @@ function globalErrorHandler(err, req, res, _next) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (logger) {
|
if (logger) {
|
||||||
logger.error({ err }, 'Unhandled error');
|
logger.error({ err, userId }, 'Unhandled error');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.error('Unhandled error', err);
|
console.error('Unhandled error', err);
|
||||||
|
|||||||
5
dist/main.js
vendored
5
dist/main.js
vendored
@@ -84,6 +84,8 @@ const ReportWorker_1 = require("./jobs/workers/ReportWorker");
|
|||||||
const server_1 = require("./api/server");
|
const server_1 = require("./api/server");
|
||||||
const SocketGateway_1 = require("./realtime/SocketGateway");
|
const SocketGateway_1 = require("./realtime/SocketGateway");
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
|
// Startup probe — measure total boot time
|
||||||
|
const startupAt = Date.now();
|
||||||
// 1. Config
|
// 1. Config
|
||||||
const config = (0, Config_1.loadConfig)();
|
const config = (0, Config_1.loadConfig)();
|
||||||
// 2. Logger
|
// 2. Logger
|
||||||
@@ -208,7 +210,8 @@ async function bootstrap() {
|
|||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
httpServer.listen(config.port, config.host, 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
|
// 14. Graceful shutdown
|
||||||
let shuttingDown = false;
|
let shuttingDown = false;
|
||||||
async function shutdown(signal) {
|
async function shutdown(signal) {
|
||||||
|
|||||||
@@ -49,17 +49,27 @@ export class RateLimitError extends AppError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExtendedReq = Request & {
|
||||||
|
log?: {
|
||||||
|
warn(obj: Record<string, unknown>, msg: string): void;
|
||||||
|
error(obj: Record<string, unknown>, msg: string): void;
|
||||||
|
};
|
||||||
|
user?: { id: string; email: string; role: string };
|
||||||
|
};
|
||||||
|
|
||||||
export function globalErrorHandler(
|
export function globalErrorHandler(
|
||||||
err: Error,
|
err: Error,
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
_next: NextFunction,
|
_next: NextFunction,
|
||||||
): void {
|
): 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 (err instanceof AppError && err.isOperational) {
|
||||||
if (logger) {
|
if (logger) {
|
||||||
logger.warn({ err, statusCode: err.statusCode }, err.message);
|
logger.warn({ err, statusCode: err.statusCode, userId }, err.message);
|
||||||
}
|
}
|
||||||
const body: Record<string, unknown> = { error: err.message, code: err.code };
|
const body: Record<string, unknown> = { error: err.message, code: err.code };
|
||||||
if (err instanceof ValidationError && err.details !== undefined) {
|
if (err instanceof ValidationError && err.details !== undefined) {
|
||||||
@@ -70,7 +80,7 @@ export function globalErrorHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (logger) {
|
if (logger) {
|
||||||
logger.error({ err }, 'Unhandled error');
|
logger.error({ err, userId }, 'Unhandled error');
|
||||||
} else {
|
} else {
|
||||||
console.error('Unhandled error', err);
|
console.error('Unhandled error', err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ import { createServer } from './api/server';
|
|||||||
import { SocketGateway } from './realtime/SocketGateway';
|
import { SocketGateway } from './realtime/SocketGateway';
|
||||||
|
|
||||||
async function bootstrap(): Promise<void> {
|
async function bootstrap(): Promise<void> {
|
||||||
|
// Startup probe — measure total boot time
|
||||||
|
const startupAt = Date.now();
|
||||||
|
|
||||||
// 1. Config
|
// 1. Config
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
|
|
||||||
@@ -247,7 +250,8 @@ async function bootstrap(): Promise<void> {
|
|||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
httpServer.listen(config.port, config.host, 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
|
// 14. Graceful shutdown
|
||||||
let shuttingDown = false;
|
let shuttingDown = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user