diff --git a/changelog.md b/changelog.md index 1ae85c3..604b419 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-12-15 - 1.1.0 - feat(smartexit) +Add silent logging option, structured shutdown logs, and killAll return stats + +- Introduce ISmartExitOptions with a silent flag to disable console logging +- Add internal log() helper and use a [smartexit] prefix for shutdown/error messages +- killAll() now returns Promise<{ processesKilled, cleanupFunctionsRan }> and tallies processes and cleanup functions run +- Constructor accepts options (backwards compatible) to configure behavior +- Documentation (readme.hints.md) updated with usage and example output + ## 2025-12-15 - 1.0.24 - fix(deps) bump dependencies, update tests and docs, adjust tsconfig and npm release config diff --git a/readme.hints.md b/readme.hints.md index 0519ecb..c807947 100644 --- a/readme.hints.md +++ b/readme.hints.md @@ -1 +1,29 @@ - \ No newline at end of file +# SmartExit - Development Hints + +## Logging System + +The module uses consolidated logging with a `[smartexit]` prefix: + +- **Default behavior**: Logs a single summary line on shutdown +- **Silent mode**: Pass `{ silent: true }` to constructor to disable all logging + +### Example output +``` +[smartexit] Shutdown complete: killed 3 child processes, ran 2 cleanup functions +``` + +### Usage +```typescript +// Default - logs summary +const smartExit = new SmartExit(); + +// Silent - no logging +const smartExit = new SmartExit({ silent: true }); +``` + +## killAll() Return Value + +The `killAll()` method returns stats about the cleanup: +```typescript +const { processesKilled, cleanupFunctionsRan } = await smartExit.killAll(); +``` diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 5d4db92..8f26ac8 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartexit', - version: '1.0.24', + version: '1.1.0', description: 'A library for managing graceful shutdowns of Node.js processes by handling cleanup operations, including terminating child processes.' } diff --git a/ts/index.ts b/ts/index.ts index 0289d00..49a33d2 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -1,5 +1,9 @@ import * as plugins from './smartexit.plugins.js'; +export interface ISmartExitOptions { + silent?: boolean; // Completely disable logging +} + export type TProcessSignal = | 'SIGHUP' // Hangup detected on controlling terminal or death of controlling process | 'SIGINT' // Interrupt from keyboard @@ -53,6 +57,22 @@ export class SmartExit { // Instance public processesToEnd = new plugins.lik.ObjectMap(); public cleanupFunctions = new plugins.lik.ObjectMap<() => Promise>(); + private options: ISmartExitOptions; + + /** + * Internal logging helper that respects silent option + */ + private log(message: string, isError = false): void { + if (this.options.silent) { + return; + } + const prefix = '[smartexit]'; + if (isError) { + console.error(`${prefix} ${message}`); + } else { + console.log(`${prefix} ${message}`); + } + } /** * adds a process to be exited @@ -73,59 +93,66 @@ export class SmartExit { this.processesToEnd.remove(childProcessArg); } - public async killAll() { - console.log('Checking for remaining child processes before exit...'); - if (this.processesToEnd.getArray().length > 0) { - console.log('found remaining child processes'); - let counter = 1; - this.processesToEnd.forEach(async (childProcessArg) => { - const pid = childProcessArg.pid; - console.log(`killing process #${counter} with pid ${pid}`); - plugins.smartdelay.delayFor(10000).then(() => { - if (childProcessArg.killed) { - return; - } - process.kill(pid, 'SIGKILL'); - }); - process.kill(pid, 'SIGINT'); + public async killAll(): Promise<{ processesKilled: number; cleanupFunctionsRan: number }> { + const processes = this.processesToEnd.getArray(); + const cleanupFuncs = this.cleanupFunctions.getArray(); + let processesKilled = 0; + let cleanupFunctionsRan = 0; - counter++; - }); - } else { - console.log(`ChildProcesses look clean.`); + // Kill child processes + if (processes.length > 0) { + for (const childProcessArg of processes) { + const pid = childProcessArg.pid; + if (pid) { + plugins.smartdelay.delayFor(10000).then(() => { + if (childProcessArg.killed) { + return; + } + process.kill(pid, 'SIGKILL'); + }); + process.kill(pid, 'SIGINT'); + processesKilled++; + } + } } - if (this.cleanupFunctions.getArray.length > 0) { - this.cleanupFunctions.forEach(async (cleanupFunction) => { + + // Run cleanup functions + if (cleanupFuncs.length > 0) { + for (const cleanupFunction of cleanupFuncs) { await cleanupFunction(); - }); + cleanupFunctionsRan++; + } } - console.log(`Ready to exit!`); + + return { processesKilled, cleanupFunctionsRan }; } - constructor() { + constructor(optionsArg: ISmartExitOptions = {}) { + this.options = optionsArg; + // do app specific cleaning before exiting process.on('exit', async (code) => { if (code === 0) { - console.log('Process wants to exit'); - await this.killAll(); - console.log('Exited ok!'); + const { processesKilled, cleanupFunctionsRan } = await this.killAll(); + this.log(`Shutdown complete: killed ${processesKilled} child processes, ran ${cleanupFunctionsRan} cleanup functions`); } else { - console.error('Exited NOT OK!'); + const { processesKilled, cleanupFunctionsRan } = await this.killAll(); + this.log(`Shutdown complete: killed ${processesKilled} child processes, ran ${cleanupFunctionsRan} cleanup functions (exit code: ${code})`, true); } }); // catch ctrl+c event and exit normally process.on('SIGINT', async () => { - console.log('Ctrl-C... or SIGINT signal received!'); - await this.killAll(); + const { processesKilled, cleanupFunctionsRan } = await this.killAll(); + this.log(`Shutdown complete: killed ${processesKilled} child processes, ran ${cleanupFunctionsRan} cleanup functions`); process.exit(0); }); - //catch uncaught exceptions, trace, then exit normally + // catch uncaught exceptions, trace, then exit normally process.on('uncaughtException', async (err) => { - console.log('SMARTEXIT: uncaught exception...'); - console.log(err); - await this.killAll(); + this.log(`Uncaught exception: ${err.message}`, true); + const { processesKilled, cleanupFunctionsRan } = await this.killAll(); + this.log(`Shutdown complete: killed ${processesKilled} child processes, ran ${cleanupFunctionsRan} cleanup functions (exit code: 1)`, true); process.exit(1); }); }