BREAKING CHANGE(daemon): Refactor daemon and service management: remove IPC auto-spawn, add TspmServiceManager, tighten IPC/client/CLI behavior and tests
This commit is contained in:
@@ -7,67 +7,93 @@ import { formatLog } from '../../helpers/formatting.js';
|
||||
import { withStreamingLifecycle } from '../../helpers/lifecycle.js';
|
||||
|
||||
export function registerLogsCommand(smartcli: plugins.smartcli.Smartcli) {
|
||||
registerIpcCommand(smartcli, 'logs', async (argvArg: CliArguments) => {
|
||||
const id = argvArg._[1];
|
||||
if (!id) {
|
||||
console.error('Error: Please provide a process ID');
|
||||
console.log('Usage: tspm logs <id> [options]');
|
||||
console.log('\nOptions:');
|
||||
console.log(' --lines <n> Number of lines to show (default: 50)');
|
||||
console.log(' --follow Stream logs in real-time (like tail -f)');
|
||||
return;
|
||||
}
|
||||
registerIpcCommand(
|
||||
smartcli,
|
||||
'logs',
|
||||
async (argvArg: CliArguments) => {
|
||||
const id = argvArg._[1];
|
||||
if (!id) {
|
||||
console.error('Error: Please provide a process ID');
|
||||
console.log('Usage: tspm logs <id> [options]');
|
||||
console.log('\nOptions:');
|
||||
console.log(' --lines <n> Number of lines to show (default: 50)');
|
||||
console.log(' --follow Stream logs in real-time (like tail -f)');
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = getNumber(argvArg, 'lines', 50);
|
||||
const follow = getBool(argvArg, 'follow', 'f');
|
||||
const lines = getNumber(argvArg, 'lines', 50);
|
||||
const follow = getBool(argvArg, 'follow', 'f');
|
||||
|
||||
const response = await tspmIpcClient.request('getLogs', { id, lines });
|
||||
|
||||
if (!follow) {
|
||||
// One-shot mode - auto-disconnect handled by registerIpcCommand
|
||||
console.log(`Logs for process: ${id} (last ${lines} lines)`);
|
||||
const response = await tspmIpcClient.request('getLogs', { id, lines });
|
||||
|
||||
if (!follow) {
|
||||
// One-shot mode - auto-disconnect handled by registerIpcCommand
|
||||
console.log(`Logs for process: ${id} (last ${lines} lines)`);
|
||||
console.log('─'.repeat(60));
|
||||
for (const log of response.logs) {
|
||||
const timestamp = new Date(log.timestamp).toLocaleTimeString();
|
||||
const prefix =
|
||||
log.type === 'stdout'
|
||||
? '[OUT]'
|
||||
: log.type === 'stderr'
|
||||
? '[ERR]'
|
||||
: '[SYS]';
|
||||
console.log(`${timestamp} ${prefix} ${log.message}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Streaming mode
|
||||
console.log(`Logs for process: ${id} (streaming...)`);
|
||||
console.log('─'.repeat(60));
|
||||
|
||||
let lastSeq = 0;
|
||||
for (const log of response.logs) {
|
||||
const timestamp = new Date(log.timestamp).toLocaleTimeString();
|
||||
const prefix = log.type === 'stdout' ? '[OUT]' : log.type === 'stderr' ? '[ERR]' : '[SYS]';
|
||||
const prefix =
|
||||
log.type === 'stdout'
|
||||
? '[OUT]'
|
||||
: log.type === 'stderr'
|
||||
? '[ERR]'
|
||||
: '[SYS]';
|
||||
console.log(`${timestamp} ${prefix} ${log.message}`);
|
||||
if (log.seq !== undefined) lastSeq = Math.max(lastSeq, log.seq);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Streaming mode
|
||||
console.log(`Logs for process: ${id} (streaming...)`);
|
||||
console.log('─'.repeat(60));
|
||||
|
||||
let lastSeq = 0;
|
||||
for (const log of response.logs) {
|
||||
const timestamp = new Date(log.timestamp).toLocaleTimeString();
|
||||
const prefix = log.type === 'stdout' ? '[OUT]' : log.type === 'stderr' ? '[ERR]' : '[SYS]';
|
||||
console.log(`${timestamp} ${prefix} ${log.message}`);
|
||||
if (log.seq !== undefined) lastSeq = Math.max(lastSeq, log.seq);
|
||||
}
|
||||
|
||||
await withStreamingLifecycle(
|
||||
async () => {
|
||||
await tspmIpcClient.subscribe(id, (log: any) => {
|
||||
if (log.seq !== undefined && log.seq <= lastSeq) return;
|
||||
if (log.seq !== undefined && log.seq > lastSeq + 1) {
|
||||
console.log(`[WARNING] Log gap detected: expected seq ${lastSeq + 1}, got ${log.seq}`);
|
||||
}
|
||||
const timestamp = new Date(log.timestamp).toLocaleTimeString();
|
||||
const prefix = log.type === 'stdout' ? '[OUT]' : log.type === 'stderr' ? '[ERR]' : '[SYS]';
|
||||
console.log(`${timestamp} ${prefix} ${log.message}`);
|
||||
if (log.seq !== undefined) lastSeq = log.seq;
|
||||
});
|
||||
},
|
||||
async () => {
|
||||
console.log('\n\nStopping log stream...');
|
||||
try { await tspmIpcClient.unsubscribe(id); } catch {}
|
||||
try { await tspmIpcClient.disconnect(); } catch {}
|
||||
}
|
||||
);
|
||||
}, {
|
||||
actionLabel: 'get logs',
|
||||
keepAlive: (argv) => getBool(argv, 'follow', 'f')
|
||||
});
|
||||
}
|
||||
await withStreamingLifecycle(
|
||||
async () => {
|
||||
await tspmIpcClient.subscribe(id, (log: any) => {
|
||||
if (log.seq !== undefined && log.seq <= lastSeq) return;
|
||||
if (log.seq !== undefined && log.seq > lastSeq + 1) {
|
||||
console.log(
|
||||
`[WARNING] Log gap detected: expected seq ${lastSeq + 1}, got ${log.seq}`,
|
||||
);
|
||||
}
|
||||
const timestamp = new Date(log.timestamp).toLocaleTimeString();
|
||||
const prefix =
|
||||
log.type === 'stdout'
|
||||
? '[OUT]'
|
||||
: log.type === 'stderr'
|
||||
? '[ERR]'
|
||||
: '[SYS]';
|
||||
console.log(`${timestamp} ${prefix} ${log.message}`);
|
||||
if (log.seq !== undefined) lastSeq = log.seq;
|
||||
});
|
||||
},
|
||||
async () => {
|
||||
console.log('\n\nStopping log stream...');
|
||||
try {
|
||||
await tspmIpcClient.unsubscribe(id);
|
||||
} catch {}
|
||||
try {
|
||||
await tspmIpcClient.disconnect();
|
||||
} catch {}
|
||||
},
|
||||
);
|
||||
},
|
||||
{
|
||||
actionLabel: 'get logs',
|
||||
keepAlive: (argv) => getBool(argv, 'follow', 'f'),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user