BREAKING CHANGE(daemon): Introduce persistent log storage, numeric ProcessId type, and improved process monitoring / IPC handling
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { EventEmitter } from 'events';
|
||||
import { ProcessWrapper } from './processwrapper.js';
|
||||
import { LogPersistence } from './logpersistence.js';
|
||||
import { Logger, ProcessError, handleError } from '../shared/common/utils.errorhandler.js';
|
||||
import type { IMonitorConfig, IProcessLog } from '../shared/protocol/ipc.types.js';
|
||||
import type { ProcessId } from '../shared/protocol/id.js';
|
||||
|
||||
export class ProcessMonitor extends EventEmitter {
|
||||
private processWrapper: ProcessWrapper | null = null;
|
||||
@@ -11,14 +13,36 @@ export class ProcessMonitor extends EventEmitter {
|
||||
private stopped: boolean = true; // Initially stopped until start() is called
|
||||
private restartCount: number = 0;
|
||||
private logger: Logger;
|
||||
private logs: IProcessLog[] = [];
|
||||
private logPersistence: LogPersistence;
|
||||
private processId?: ProcessId;
|
||||
private currentLogMemorySize: number = 0;
|
||||
private readonly MAX_LOG_MEMORY_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
constructor(config: IMonitorConfig) {
|
||||
constructor(config: IMonitorConfig & { id?: ProcessId }) {
|
||||
super();
|
||||
this.config = config;
|
||||
this.logger = new Logger(`ProcessMonitor:${config.name || 'unnamed'}`);
|
||||
this.logs = [];
|
||||
this.logPersistence = new LogPersistence();
|
||||
this.processId = config.id;
|
||||
this.currentLogMemorySize = 0;
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
public async start(): Promise<void> {
|
||||
// Load previously persisted logs if available
|
||||
if (this.processId) {
|
||||
const persistedLogs = await this.logPersistence.loadLogs(this.processId);
|
||||
if (persistedLogs.length > 0) {
|
||||
this.logs = persistedLogs;
|
||||
this.currentLogMemorySize = LogPersistence.calculateLogMemorySize(this.logs);
|
||||
this.logger.info(`Loaded ${persistedLogs.length} persisted logs from disk`);
|
||||
|
||||
// Delete the persisted file after loading
|
||||
await this.logPersistence.deleteLogs(this.processId);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the stopped flag so that new processes can spawn.
|
||||
this.stopped = false;
|
||||
this.log(`Starting process monitor.`);
|
||||
@@ -57,6 +81,22 @@ export class ProcessMonitor extends EventEmitter {
|
||||
|
||||
// Set up event handlers
|
||||
this.processWrapper.on('log', (log: IProcessLog): void => {
|
||||
// Store the log in our buffer
|
||||
this.logs.push(log);
|
||||
console.error(`[ProcessMonitor:${this.config.name}] Received log (type=${log.type}): ${log.message}`);
|
||||
console.error(`[ProcessMonitor:${this.config.name}] Logs array now has ${this.logs.length} items`);
|
||||
this.logger.debug(`ProcessMonitor received log: ${log.message}`);
|
||||
|
||||
// Update memory size tracking
|
||||
this.currentLogMemorySize = LogPersistence.calculateLogMemorySize(this.logs);
|
||||
|
||||
// Trim logs if they exceed memory limit (10MB)
|
||||
while (this.currentLogMemorySize > this.MAX_LOG_MEMORY_SIZE && this.logs.length > 1) {
|
||||
// Remove oldest logs until we're under the memory limit
|
||||
this.logs.shift();
|
||||
this.currentLogMemorySize = LogPersistence.calculateLogMemorySize(this.logs);
|
||||
}
|
||||
|
||||
// Re-emit the log event for upstream handlers
|
||||
this.emit('log', log);
|
||||
|
||||
@@ -65,13 +105,31 @@ export class ProcessMonitor extends EventEmitter {
|
||||
this.log(log.message);
|
||||
}
|
||||
});
|
||||
|
||||
// Re-emit start event with PID for upstream handlers
|
||||
this.processWrapper.on('start', (pid: number): void => {
|
||||
this.emit('start', pid);
|
||||
});
|
||||
|
||||
this.processWrapper.on(
|
||||
'exit',
|
||||
(code: number | null, signal: string | null): void => {
|
||||
async (code: number | null, signal: string | null): Promise<void> => {
|
||||
const exitMsg = `Process exited with code ${code}, signal ${signal}.`;
|
||||
this.logger.info(exitMsg);
|
||||
this.log(exitMsg);
|
||||
|
||||
// Flush logs to disk on exit
|
||||
if (this.processId && this.logs.length > 0) {
|
||||
try {
|
||||
await this.logPersistence.saveLogs(this.processId, this.logs);
|
||||
this.logger.debug(`Flushed ${this.logs.length} logs to disk on exit`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to flush logs to disk on exit: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-emit exit event for upstream handlers
|
||||
this.emit('exit', code, signal);
|
||||
|
||||
if (!this.stopped) {
|
||||
this.logger.info('Restarting process...');
|
||||
@@ -86,7 +144,7 @@ export class ProcessMonitor extends EventEmitter {
|
||||
},
|
||||
);
|
||||
|
||||
this.processWrapper.on('error', (error: Error | ProcessError): void => {
|
||||
this.processWrapper.on('error', async (error: Error | ProcessError): Promise<void> => {
|
||||
const errorMsg =
|
||||
error instanceof ProcessError
|
||||
? `Process error: ${error.toString()}`
|
||||
@@ -95,6 +153,16 @@ export class ProcessMonitor extends EventEmitter {
|
||||
this.logger.error(error);
|
||||
this.log(errorMsg);
|
||||
|
||||
// Flush logs to disk on error
|
||||
if (this.processId && this.logs.length > 0) {
|
||||
try {
|
||||
await this.logPersistence.saveLogs(this.processId, this.logs);
|
||||
this.logger.debug(`Flushed ${this.logs.length} logs to disk on error`);
|
||||
} catch (flushError) {
|
||||
this.logger.error(`Failed to flush logs to disk on error: ${flushError}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.stopped) {
|
||||
this.logger.info('Restarting process due to error...');
|
||||
this.log('Restarting process due to error...');
|
||||
@@ -239,9 +307,20 @@ export class ProcessMonitor extends EventEmitter {
|
||||
/**
|
||||
* Stop the monitor and prevent any further respawns.
|
||||
*/
|
||||
public stop(): void {
|
||||
public async stop(): Promise<void> {
|
||||
this.log('Stopping process monitor.');
|
||||
this.stopped = true;
|
||||
|
||||
// Flush logs to disk before stopping
|
||||
if (this.processId && this.logs.length > 0) {
|
||||
try {
|
||||
await this.logPersistence.saveLogs(this.processId, this.logs);
|
||||
this.logger.info(`Flushed ${this.logs.length} logs to disk on stop`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to flush logs to disk on stop: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
@@ -254,10 +333,12 @@ export class ProcessMonitor extends EventEmitter {
|
||||
* Get the current logs from the process
|
||||
*/
|
||||
public getLogs(limit?: number): IProcessLog[] {
|
||||
if (!this.processWrapper) {
|
||||
return [];
|
||||
console.error(`[ProcessMonitor:${this.config.name}] getLogs called, logs.length=${this.logs.length}, limit=${limit}`);
|
||||
this.logger.debug(`Getting logs, total stored: ${this.logs.length}`);
|
||||
if (limit && limit > 0) {
|
||||
return this.logs.slice(-limit);
|
||||
}
|
||||
return this.processWrapper.getLogs(limit);
|
||||
return this.logs;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user