From 45b5e6a6a125f14c96c7a28a993edcd4aa850e68 Mon Sep 17 00:00:00 2001 From: chr1syy Date: Sun, 7 Jun 2026 11:56:45 +0200 Subject: [PATCH 1/2] fix(logger): restore wallclock timestamp on console output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #48 split formatting into `formatLine` (no timestamp, console) and `formatEntry` (timestamp, file). On Linux this was fine — the journal stamps every line — but on macOS launchd writes raw bytes to `logs/maestro-relay.log`, so `maestro-relay-ctl logs` showed untimestamped debug/info/warn lines. Route all console emitters through `formatEntry(...).trimEnd()`. Linux gets a (redundant) inner ISO timestamp alongside the journal's local-time prefix; macOS gets parity with pre-#48 behavior. `logs/errors.log` unchanged. --- src/__tests__/logger.test.ts | 6 +++--- src/core/logger.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/__tests__/logger.test.ts b/src/__tests__/logger.test.ts index d395716..19042e0 100644 --- a/src/__tests__/logger.test.ts +++ b/src/__tests__/logger.test.ts @@ -69,7 +69,7 @@ test('level-gated methods do not emit when disabled', async () => { assert.equal(debugCalls.length, 0, 'debug should be suppressed at warn level'); assert.equal(infoCalls.length, 0, 'info should be suppressed at warn level'); assert.equal(warnCalls.length, 1, 'warn should pass at warn level'); - assert.match(warnCalls[0], /\[WARN\] \[test\/ctx\] warn-detail/); + assert.match(warnCalls[0], /^\[\d{4}-\d{2}-\d{2}T[^\]]+\] WARN \[test\/ctx\] warn-detail$/); } finally { console.debug = origDebug; console.info = origInfo; @@ -86,7 +86,7 @@ test('debug emits when level is debug', async () => { logger.setLevel('debug'); logger.debug('test/ctx', 'detail'); assert.equal(calls.length, 1); - assert.match(calls[0], /\[DEBUG\] \[test\/ctx\] detail/); + assert.match(calls[0], /^\[\d{4}-\d{2}-\d{2}T[^\]]+\] DEBUG \[test\/ctx\] detail$/); } finally { console.debug = orig; } @@ -103,7 +103,7 @@ test('error always emits at any level that includes error', async () => { calls.length = 0; await logger.error('test/ctx', 'err-detail'); assert.equal(calls.length, 1, `error should fire at level=${level}`); - assert.match(calls[0], /\[ERROR\] \[test\/ctx\] err-detail/); + assert.match(calls[0], /^\[\d{4}-\d{2}-\d{2}T[^\]]+\] ERROR \[test\/ctx\] err-detail$/); } } finally { console.error = orig; diff --git a/src/core/logger.ts b/src/core/logger.ts index d0bb26d..e9d771e 100644 --- a/src/core/logger.ts +++ b/src/core/logger.ts @@ -42,7 +42,7 @@ function shouldEmit(level: LogLevel): boolean { function emit(level: LogLevel, context: string, detail: string, sink: (line: string) => void) { if (!shouldEmit(level)) return; - sink(formatLine(level.toUpperCase(), context, detail)); + sink(formatEntry(level.toUpperCase(), context, detail).trimEnd()); } export const logger = { @@ -68,7 +68,7 @@ export const logger = { emit('warn', context, detail, (line) => console.warn(line)); }, async error(context: string, detail: string): Promise { - if (shouldEmit('error')) console.error(formatLine('ERROR', context, detail)); + if (shouldEmit('error')) console.error(formatEntry('ERROR', context, detail).trimEnd()); try { await ensureDir(); await appendFile(LOG_FILE, formatEntry('ERROR', context, detail)); From 645ae832244c403cf72e36937a4dd5f895317b47 Mon Sep 17 00:00:00 2001 From: chr1syy Date: Sun, 7 Jun 2026 12:44:40 +0200 Subject: [PATCH 2/2] chore(logger): remove unused formatLine helper Left over after the macOS timestamp fix routed all console output through formatEntry; formatLine had no remaining callers. Dropping it removes dead code and keeps logger.ts focused on a single line format. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/core/logger.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/core/logger.ts b/src/core/logger.ts index e9d771e..e062cc2 100644 --- a/src/core/logger.ts +++ b/src/core/logger.ts @@ -32,10 +32,6 @@ function formatEntry(level: string, context: string, detail: string): string { return `[${ts}] ${level} [${sanitize(context)}] ${sanitize(detail)}\n`; } -function formatLine(level: string, context: string, detail: string): string { - return `[${level}] [${sanitize(context)}] ${sanitize(detail)}`; -} - function shouldEmit(level: LogLevel): boolean { return LEVELS[level] >= currentLevel; }