diff --git a/sources/app/api.ts b/sources/app/api.ts index c36e495..b4d2b53 100644 --- a/sources/app/api.ts +++ b/sources/app/api.ts @@ -135,6 +135,102 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }> httpRequestDurationHistogram.observe({ method, route, status }, duration); }); + // Global error handler + app.setErrorHandler(async (error, request, reply) => { + const method = request.method; + const url = request.url; + const userAgent = request.headers['user-agent'] || 'unknown'; + const ip = request.ip || 'unknown'; + + // Log the error with comprehensive context + log({ + module: 'fastify-error', + level: 'error', + method, + url, + userAgent, + ip, + statusCode: error.statusCode || 500, + errorCode: error.code, + stack: error.stack + }, `Unhandled error: ${error.message}`); + + // Return appropriate error response + const statusCode = error.statusCode || 500; + + if (statusCode >= 500) { + // Internal server errors - don't expose details + return reply.code(statusCode).send({ + error: 'Internal Server Error', + message: 'An unexpected error occurred', + statusCode + }); + } else { + // Client errors - can expose more details + return reply.code(statusCode).send({ + error: error.name || 'Error', + message: error.message || 'An error occurred', + statusCode + }); + } + }); + + // Error hook for additional logging + app.addHook('onError', async (request, reply, error) => { + const method = request.method; + const url = request.url; + const duration = (Date.now() - (request.startTime || Date.now())) / 1000; + + log({ + module: 'fastify-hook-error', + level: 'error', + method, + url, + duration, + statusCode: reply.statusCode || error.statusCode || 500, + errorName: error.name, + errorCode: error.code + }, `Request error: ${error.message}`); + }); + + // Handle uncaught exceptions in routes + app.addHook('preHandler', async (request, reply) => { + // Store original reply.send to catch errors in response serialization + const originalSend = reply.send.bind(reply); + reply.send = function(payload: any) { + try { + return originalSend(payload); + } catch (error: any) { + log({ + module: 'fastify-serialization-error', + level: 'error', + method: request.method, + url: request.url, + stack: error.stack + }, `Response serialization error: ${error.message}`); + throw error; + } + }; + }); + + // Not found handler + app.setNotFoundHandler(async (request, reply) => { + log({ + module: 'fastify-not-found', + level: 'warn', + method: request.method, + url: request.url, + userAgent: request.headers['user-agent'] || 'unknown', + ip: request.ip || 'unknown' + }, `404 Not Found: ${request.method} ${request.url}`); + + return reply.code(404).send({ + error: 'Not Found', + message: `Route ${request.method} ${request.url} not found`, + statusCode: 404 + }); + }); + // Authentication decorator app.decorate('authenticate', async function (request: any, reply: any) { try { diff --git a/sources/main.ts b/sources/main.ts index 0872cfd..9c366d9 100644 --- a/sources/main.ts +++ b/sources/main.ts @@ -46,6 +46,60 @@ async function main() { log('Shutting down...'); } +// Process-level error handling +process.on('uncaughtException', (error) => { + log({ + module: 'process-error', + level: 'error', + stack: error.stack, + name: error.name + }, `Uncaught Exception: ${error.message}`); + + console.error('Uncaught Exception:', error); + process.exit(1); +}); + +process.on('unhandledRejection', (reason, promise) => { + const errorMsg = reason instanceof Error ? reason.message : String(reason); + const errorStack = reason instanceof Error ? reason.stack : undefined; + + log({ + module: 'process-error', + level: 'error', + stack: errorStack, + reason: String(reason) + }, `Unhandled Rejection: ${errorMsg}`); + + console.error('Unhandled Rejection at:', promise, 'reason:', reason); + process.exit(1); +}); + +process.on('warning', (warning) => { + log({ + module: 'process-warning', + level: 'warn', + name: warning.name, + stack: warning.stack + }, `Process Warning: ${warning.message}`); +}); + +// Log when the process is about to exit +process.on('exit', (code) => { + if (code !== 0) { + log({ + module: 'process-exit', + level: 'error', + exitCode: code + }, `Process exiting with code: ${code}`); + } else { + log({ + module: 'process-exit', + level: 'info', + exitCode: code + }, 'Process exiting normally'); + } +}); + main().catch((e) => { console.error(e); process.exit(1);