diff --git a/SKILL.md b/SKILL.md index 0ac5272..67ec639 100644 --- a/SKILL.md +++ b/SKILL.md @@ -191,9 +191,10 @@ View daemon logs. | Option | Default | Description | |--------|---------|-------------| | `-f, --follow` | false | Follow log output (like `tail -f`) | +| `-c, --coco` | false | Show Cashu wallet-engine (coco) logs instead of daemon logs | | `-n, --lines ` | 50 | Number of lines to show | -Log files are stored at `~/.routstrd/logs/YYYY-MM-DD.log`. +Log files are stored at `~/.routstrd/logs/YYYY-MM-DD.log`. Wallet-engine (Cashu/coco) diagnostics go to a separate `~/.routstrd/coco-logs/YYYY-MM-DD.log` so they don't pollute the main daemon logs. ## Wallet Commands @@ -317,4 +318,5 @@ When `routstrd onboard` runs, it automatically configures a `routstr` provider i | `~/.routstrd/routstr.db` | SQLite database | | `~/.routstrd/routstrd.sock` | IPC socket | | `~/.routstrd/routstrd.pid` | PID file | -| `~/.routstrd/logs/YYYY-MM-DD.log` | Daily log files | +| `~/.routstrd/logs/YYYY-MM-DD.log` | Daily daemon log files | +| `~/.routstrd/coco-logs/YYYY-MM-DD.log` | Daily Cashu wallet-engine (coco) log files | diff --git a/src/cli.ts b/src/cli.ts index cd38571..fd3ae3f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -25,7 +25,7 @@ import { LOGS_DIR, type RoutstrdConfig, } from "./utils/config"; -import { logger } from "./utils/logger"; +import { COCO_LOGS_DIR, logger } from "./utils/logger"; import { setupIntegration, runIntegrationsForClients } from "./integrations"; import { stopLegacyCocod } from "./daemon/wallet/coco-client"; import { getClientsList } from "./utils/clients"; @@ -1948,11 +1948,11 @@ program }); // Logs -function getLogFileForDate(date: Date = new Date()): string { +function getLogFileForDate(logsDir: string, date: Date = new Date()): string { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); - return `${LOGS_DIR}/${year}-${month}-${day}.log`; + return `${logsDir}/${year}-${month}-${day}.log`; } function readLastLines(file: string, lines: number): string { @@ -1993,17 +1993,19 @@ program .command("logs") .description("View daemon logs") .option("-f, --follow", "Follow log output", false) + .option("-c, --coco", "Show Cashu wallet-engine (coco) logs instead of daemon logs", false) .option("-n, --lines ", "Number of lines to show", "50") - .action(async (options: { follow: boolean; lines: string }) => { + .action(async (options: { follow: boolean; lines: string; coco: boolean }) => { await requireLocalDaemon(); - const todayFile = getLogFileForDate(); + const logsDir = options.coco ? COCO_LOGS_DIR : LOGS_DIR; + const todayFile = getLogFileForDate(logsDir); const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); - const yesterdayFile = getLogFileForDate(yesterday); + const yesterdayFile = getLogFileForDate(logsDir, yesterday); if (!existsSync(todayFile) && !existsSync(yesterdayFile)) { console.log("No log files found. Daemon may not have started yet."); - console.log(`Logs directory: ${LOGS_DIR}`); + console.log(`Logs directory: ${logsDir}`); process.exit(1); } diff --git a/src/daemon/wallet/coco-client.ts b/src/daemon/wallet/coco-client.ts index 2b351a3..2b6bc1b 100644 --- a/src/daemon/wallet/coco-client.ts +++ b/src/daemon/wallet/coco-client.ts @@ -30,7 +30,7 @@ import type { NpcAddress, NpcUsernameResult, } from "./cocod-client"; -import { logger } from "../../utils/logger"; +import { cocoLogger, logger } from "../../utils/logger"; const NPC_DEFAULT_BASE_URL = "https://npubx.cash"; @@ -148,9 +148,10 @@ function createCocoLogger(bindings: Record = {}): CocoLogger { ) => { // Coco diagnostics may contain proof secrets or encoded tokens. Keep only // an explicit metadata allowlist; startup counts and operation IDs remain - // useful without copying wallet material into routstrd's logs. + // useful without copying wallet material into routstrd's logs. Written to + // ~/.routstrd/coco-logs/ so wallet-engine noise stays out of the main logs. const metadata = safeCocoMetadata([bindings, ...meta]); - logger[level]( + cocoLogger[level]( `[coco] ${message}`, ...(Object.keys(metadata).length > 0 ? [metadata] : []), ); diff --git a/src/start-daemon.ts b/src/start-daemon.ts index 9e45702..a9c58a5 100644 --- a/src/start-daemon.ts +++ b/src/start-daemon.ts @@ -9,7 +9,6 @@ import { logger } from "./utils/logger"; import { CONFIG_DIR, LOGS_DIR } from "./utils/config"; import { withCrossProcessLock } from "./utils/process-lock"; import { fileURLToPath } from "url"; -import { existsSync } from "fs"; const DAEMON_STARTUP_LOCK_PATH = `${CONFIG_DIR}/routstrd-startup.lock`; const DEBUG_LOG_PATH = `${CONFIG_DIR}/debug.log`; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 2a4311c..fa77fb6 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -4,25 +4,27 @@ import { join } from "path"; const HOME = process.env.HOME || process.env.USERPROFILE || ""; const LOG_DIR = process.env.ROUTSTRD_DIR || `${HOME}/.routstrd`; const LOGS_DIR = join(LOG_DIR, "logs"); +/** Wallet-engine (coco-core / Cashu) diagnostics land in their own directory. */ +export const COCO_LOGS_DIR = join(LOG_DIR, "coco-logs"); -function getLogFileForDate(date: Date = new Date()): string { +function getLogFileForDate(logsDir: string, date: Date = new Date()): string { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); - return join(LOGS_DIR, `${year}-${month}-${day}.log`); + return join(logsDir, `${year}-${month}-${day}.log`); } -function ensureLogDir() { - if (!existsSync(LOGS_DIR)) { - mkdirSync(LOGS_DIR, { recursive: true }); +function ensureDir(dir: string) { + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); } } // NOTE: writes are synchronous on purpose — the daemon calls process.exit() // right after logger.error(...) on fatal paths, and async writes were being // silently dropped, making startup failures invisible. -function writeLog(level: string, ...args: unknown[]) { - ensureLogDir(); +function writeLog(logsDir: string, level: string, ...args: unknown[]) { + ensureDir(logsDir); const timestamp = new Date().toISOString(); const message = args .map((a) => { @@ -40,7 +42,7 @@ function writeLog(level: string, ...args: unknown[]) { }) .join(" "); const line = `[${timestamp}] [${level}] ${message}\n`; - const logFile = getLogFileForDate(new Date(timestamp)); + const logFile = getLogFileForDate(logsDir, new Date(timestamp)); try { appendFileSync(logFile, line); } catch (error) { @@ -48,20 +50,23 @@ function writeLog(level: string, ...args: unknown[]) { } } -export const logger = { - log: (...args: unknown[]) => { - writeLog("INFO", ...args); - }, - debug: (...args: unknown[]) => { - writeLog("DEBUG", ...args); - }, - warn: (...args: unknown[]) => { - writeLog("WARN", ...args); - }, - error: (...args: unknown[]) => { - writeLog("ERROR", ...args); - }, - info: (...args: unknown[]) => { - writeLog("INFO", ...args); - }, -}; +export interface FileLogger { + log: (...args: unknown[]) => void; + debug: (...args: unknown[]) => void; + warn: (...args: unknown[]) => void; + error: (...args: unknown[]) => void; + info: (...args: unknown[]) => void; +} + +function createLogger(logsDir: string): FileLogger { + return { + log: (...args) => writeLog(logsDir, "INFO", ...args), + debug: (...args) => writeLog(logsDir, "DEBUG", ...args), + warn: (...args) => writeLog(logsDir, "WARN", ...args), + error: (...args) => writeLog(logsDir, "ERROR", ...args), + info: (...args) => writeLog(logsDir, "INFO", ...args), + }; +} + +export const logger = createLogger(LOGS_DIR); +export const cocoLogger = createLogger(COCO_LOGS_DIR);