feat(errors): mcpd error-log ring buffer + 'mcpctl errors'
Some checks failed
CI/CD / lint (pull_request) Successful in 1m1s
CI/CD / typecheck (pull_request) Successful in 1m2s
CI/CD / test (pull_request) Successful in 1m21s
CI/CD / smoke (pull_request) Failing after 1m50s
CI/CD / build (pull_request) Successful in 5m12s
CI/CD / publish (pull_request) Has been skipped
Some checks failed
CI/CD / lint (pull_request) Successful in 1m1s
CI/CD / typecheck (pull_request) Successful in 1m2s
CI/CD / test (pull_request) Successful in 1m21s
CI/CD / smoke (pull_request) Failing after 1m50s
CI/CD / build (pull_request) Successful in 5m12s
CI/CD / publish (pull_request) Has been skipped
Operators can now see recent mcpd error/fatal logs without kubectl: - mcpd tees level>=error pino records into an in-memory ring buffer (src/mcpd/src/services/error-log-buffer.ts; wired via pino.multistream in server.ts so stdout logging is unchanged). Captures structured errors incl. fatal kinds like BACKEND_TOKEN_DEAD. - GET /api/v1/logs/errors?limit=N (RBAC: 'logs' operation). - CLI: 'mcpctl errors [-n N]' renders TIME/LEVEL/DETAIL, most-recent-first. Buffer unit tests (6); full suite 2223 passing. Needs a deploy to go live. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
59
src/cli/src/commands/errors.ts
Normal file
59
src/cli/src/commands/errors.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Command } from 'commander';
|
||||
import type { ApiClient } from '../api-client.js';
|
||||
|
||||
export interface ErrorsCommandDeps {
|
||||
client: ApiClient;
|
||||
log: (...args: string[]) => void;
|
||||
}
|
||||
|
||||
interface ErrorEntry {
|
||||
time: number;
|
||||
level: number;
|
||||
msg?: string;
|
||||
kind?: string;
|
||||
reqId?: string;
|
||||
err?: string;
|
||||
}
|
||||
|
||||
/** pino numeric level → short label. */
|
||||
function levelLabel(level: number): string {
|
||||
if (level >= 60) return 'FATAL';
|
||||
if (level >= 50) return 'ERROR';
|
||||
return String(level);
|
||||
}
|
||||
|
||||
function fmtTime(ms: number): string {
|
||||
if (!ms) return '-';
|
||||
// Local HH:MM:SS — enough to correlate with "it broke just now".
|
||||
const d = new Date(ms);
|
||||
const p = (n: number): string => String(n).padStart(2, '0');
|
||||
return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||
}
|
||||
|
||||
export function createErrorsCommand(deps?: Partial<ErrorsCommandDeps>): Command {
|
||||
const log = deps?.log ?? ((...args: string[]): void => { console.log(...args); });
|
||||
|
||||
return new Command('errors')
|
||||
.description('Show recent mcpd error/fatal log entries')
|
||||
.option('-n, --limit <count>', 'max entries to show (default 50)', '50')
|
||||
.action(async (opts: { limit: string }) => {
|
||||
const client = deps?.client;
|
||||
if (!client) throw new Error('errors: no API client configured');
|
||||
|
||||
const limit = Number(opts.limit) > 0 ? Number(opts.limit) : 50;
|
||||
const res = await client.get<{ errors: ErrorEntry[] }>(`/api/v1/logs/errors?limit=${limit}`);
|
||||
const errors = res.errors ?? [];
|
||||
|
||||
if (errors.length === 0) {
|
||||
log('No recent mcpd errors. 🎉');
|
||||
return;
|
||||
}
|
||||
|
||||
log(`TIME LEVEL DETAIL`);
|
||||
for (const e of errors) {
|
||||
const detail = [e.kind, e.msg ?? e.err].filter(Boolean).join(' — ') || '(no message)';
|
||||
log(`${fmtTime(e.time).padEnd(9)} ${levelLabel(e.level).padEnd(6)} ${detail}`);
|
||||
}
|
||||
log(`\n${errors.length} entr${errors.length === 1 ? 'y' : 'ies'} (most recent first; in-memory, cleared on mcpd restart)`);
|
||||
});
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import { createRotateCommand } from './commands/rotate.js';
|
||||
import { createReviewCommand } from './commands/review.js';
|
||||
import { createSkillsCommand } from './commands/skills.js';
|
||||
import { createPasswdCommand } from './commands/passwd.js';
|
||||
import { createErrorsCommand } from './commands/errors.js';
|
||||
import { ApiClient, ApiError } from './api-client.js';
|
||||
import { loadConfig } from './config/index.js';
|
||||
import { loadCredentials } from './auth/index.js';
|
||||
@@ -263,6 +264,11 @@ export function createProgram(): Command {
|
||||
log: (...args) => console.log(...args),
|
||||
}));
|
||||
|
||||
program.addCommand(createErrorsCommand({
|
||||
client,
|
||||
log: (...args) => console.log(...args),
|
||||
}));
|
||||
|
||||
program.addCommand(createBackupCommand({
|
||||
client,
|
||||
log: (...args) => console.log(...args),
|
||||
|
||||
Reference in New Issue
Block a user