From 83e8c44d98b5ce52ff3345bd45e7487d79c38d3f Mon Sep 17 00:00:00 2001 From: Bojan Kucera Date: Thu, 5 Jun 2025 09:24:25 -0400 Subject: [PATCH] improved logger and added shutdown handler --- apps/data-service/src/index.ts | 4 +- libs/logger/src/gracefulShutdown.ts | 116 ++++++++++++++++++++++++++++ libs/logger/src/index.ts | 2 + libs/utils/tsconfig.json | 3 +- 4 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 libs/logger/src/gracefulShutdown.ts diff --git a/apps/data-service/src/index.ts b/apps/data-service/src/index.ts index a2f9fa7..ffcbeee 100644 --- a/apps/data-service/src/index.ts +++ b/apps/data-service/src/index.ts @@ -1,7 +1,7 @@ /** * Data Service - Combined live and historical data ingestion */ -import { createLogger } from '@stock-bot/logger'; +import { createLogger, GracefulShutdownManager } from '@stock-bot/logger'; import { loadEnvVariables } from '@stock-bot/config'; import { Hono } from 'hono'; import { serve } from '@hono/node-server'; @@ -11,6 +11,8 @@ loadEnvVariables(); const app = new Hono(); const logger = createLogger('data-service'); +const shutdownManager = new GracefulShutdownManager(logger); + const PORT = parseInt(process.env.DATA_SERVICE_PORT || '3002'); // Health check endpoint app.get('/health', (c) => { diff --git a/libs/logger/src/gracefulShutdown.ts b/libs/logger/src/gracefulShutdown.ts new file mode 100644 index 0000000..ef2d783 --- /dev/null +++ b/libs/logger/src/gracefulShutdown.ts @@ -0,0 +1,116 @@ +import pino from 'pino'; + +interface ShutdownHandler { + name: string; + handler: () => Promise | void; + timeout?: number; +} + +export class GracefulShutdownManager { + private logger: pino.Logger; + private handlers: ShutdownHandler[] = []; + private isShuttingDown = false; + private startTime = Date.now(); + + constructor(logger: pino.Logger) { + this.logger = logger; + this.setupSignalHandlers(); + } + + /** + * Register a shutdown handler + */ + addHandler(handler: ShutdownHandler): void { + this.handlers.push(handler); + } + + /** + * Setup signal handlers for graceful shutdown + */ + private setupSignalHandlers(): void { + // Graceful shutdown signals + process.on('SIGTERM', () => this.shutdown('SIGTERM', 0)); + process.on('SIGINT', () => this.shutdown('SIGINT', 0)); + process.on('SIGUSR2', () => this.shutdown('SIGUSR2', 0)); // nodemon + + // Fatal error handlers + process.on('uncaughtException', (error) => { + this.logger.error('Uncaught exception', error, { fatal: true }); + this.shutdown('uncaughtException', 1); + }); + + process.on('unhandledRejection', (reason, promise) => { + this.logger.error('Unhandled promise rejection', { + reason: reason instanceof Error ? reason.message : reason, + stack: reason instanceof Error ? reason.stack : undefined, + fatal: true + }); + this.shutdown('unhandledRejection', 1); + }); + + // Process warnings + process.on('warning', (warning) => { + this.logger.warn('Process warning', { + name: warning.name, + message: warning.message, + stack: warning.stack + }); + }); + + // Final exit + process.on('exit', (code) => { + const uptime = Date.now() - this.startTime; + console.log(`Process exiting with code ${code} after ${uptime}ms`); + }); + } + + /** + * Execute graceful shutdown + */ + private async shutdown(signal: string, exitCode: number): Promise { + if (this.isShuttingDown) { + this.logger.warn('Force shutdown - multiple signals received'); + process.exit(1); + } + + this.isShuttingDown = true; + const shutdownStart = Date.now(); + + this.logger.info('Graceful shutdown initiated', { + signal, + exitCode, + uptime: Date.now() - this.startTime, + memoryUsage: process.memoryUsage(), + handlersCount: this.handlers.length + }); + + // Execute shutdown handlers + for (const handler of this.handlers) { + try { + this.logger.info(`Executing shutdown handler: ${handler.name}`); + const timeout = handler.timeout || 5000; + + await Promise.race([ + Promise.resolve(handler.handler()), + new Promise((_, reject) => + setTimeout(() => reject(new Error(`Timeout after ${timeout}ms`)), timeout) + ) + ]); + + this.logger.info(`Shutdown handler completed: ${handler.name}`); + } catch (error) { + this.logger.error(`Shutdown handler failed: ${handler.name}`, error); + } + } + + const shutdownDuration = Date.now() - shutdownStart; + this.logger.info('Graceful shutdown completed', { + signal, + shutdownDuration, + totalUptime: Date.now() - this.startTime + }); + + // Give logs time to flush + setTimeout(() => process.exit(exitCode), 100); + } +} \ No newline at end of file diff --git a/libs/logger/src/index.ts b/libs/logger/src/index.ts index 8a5eb8a..af7bfd4 100644 --- a/libs/logger/src/index.ts +++ b/libs/logger/src/index.ts @@ -45,3 +45,5 @@ export type { } from './types'; export type { LoggingMiddlewareOptions } from './middleware'; + +export { GracefulShutdownManager } from './gracefulShutdown'; diff --git a/libs/utils/tsconfig.json b/libs/utils/tsconfig.json index 296ca8c..4ad4f6c 100644 --- a/libs/utils/tsconfig.json +++ b/libs/utils/tsconfig.json @@ -7,5 +7,6 @@ }, "include": [ "src/**/*" - ], "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] + ], + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"], }