From d3e96bd987abaf535965b9d3c6567fe93f71eb25 Mon Sep 17 00:00:00 2001 From: Alban Mouton Date: Wed, 27 May 2026 16:05:39 +0200 Subject: [PATCH] feat(worker): surface memory stats in run log and fix log-level colors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Memory samples now emit a single debug log line per phase (startup, each sample interval, and stop) when processing.debug is on, formatted as `Task process memory stats - rss=… heapTotal=… …` so the payload is visible in the run UI (which only renders log.msg). The reporter handle is now awaited in task.ts:finally so the closing sample lands after the final success/error log. Run log UI: `warning` entries now use `text-warning` (were styled with `text-info`), and `debug` entries use `text-medium-emphasis` so they're visually distinct from `info`. Co-Authored-By: Claude Opus 4.7 (1M context) --- ui/src/components/run/run-logs-list.vue | 8 +++++++- worker/src/task/memory-reporter.ts | 19 +++++++++++++++---- worker/src/task/task.ts | 3 ++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ui/src/components/run/run-logs-list.vue b/ui/src/components/run/run-logs-list.vue index 32b2824f..306e25c4 100644 --- a/ui/src/components/run/run-logs-list.vue +++ b/ui/src/components/run/run-logs-list.vue @@ -18,7 +18,7 @@ {{ log.msg }} @@ -65,6 +65,12 @@ const taskColor = (log: LogEntry) => { return 'primary' } +const logTextClass = (log: LogEntry) => { + if (log.type === 'warning') return 'text-warning' + if (log.type === 'debug') return 'text-medium-emphasis' + return '' +} + const formatDate = (date: string) => dayjs(date).format('lll') diff --git a/worker/src/task/memory-reporter.ts b/worker/src/task/memory-reporter.ts index 82804939..8189d867 100644 --- a/worker/src/task/memory-reporter.ts +++ b/worker/src/task/memory-reporter.ts @@ -3,6 +3,8 @@ import { formatMem, type MemorySample, type MemorySamplePhase } from '../utils/m type DebugLog = (msg: string, extra?: string) => Promise +const MEMORY_LOG_LABEL = 'Task process memory stats' + const buildSample = (phase: MemorySamplePhase): MemorySample => { const m = process.memoryUsage() return { @@ -22,7 +24,7 @@ const writeStdoutSample = (sample: MemorySample): void => { } export type MemoryReporterHandle = { - stop: () => void + stop: () => Promise } export const startMemoryReporter = ( @@ -30,7 +32,11 @@ export const startMemoryReporter = ( debug: DebugLog, intervalMs: number ): MemoryReporterHandle => { - writeStdoutSample(buildSample('startup')) + const startupSample = buildSample('startup') + writeStdoutSample(startupSample) + if (processing.debug) { + debug(`${MEMORY_LOG_LABEL} - ${formatMem(startupSample)}`).catch(() => { /* best-effort */ }) + } let timer: NodeJS.Timeout | null = null if (intervalMs > 0) { @@ -40,7 +46,7 @@ export const startMemoryReporter = ( if (processing.debug) { // Skip building the debug string when debug is off // (log.debug also no-ops, but avoids formatMem cost). - debug('memory', formatMem(sample)).catch(() => { /* best-effort */ }) + debug(`${MEMORY_LOG_LABEL} - ${formatMem(sample)}`).catch(() => { /* best-effort */ }) } }, intervalMs) timer.unref() @@ -53,9 +59,14 @@ export const startMemoryReporter = ( process.on('exit', onExit) return { - stop: () => { + stop: async () => { if (timer) { clearInterval(timer); timer = null } process.off('exit', onExit) + const exitSample = buildSample('exit') + writeStdoutSample(exitSample) + if (processing.debug) { + await debug(`${MEMORY_LOG_LABEL} - ${formatMem(exitSample)}`).catch(() => { /* best-effort */ }) + } } } } diff --git a/worker/src/task/task.ts b/worker/src/task/task.ts index 84a4f8f7..03befbc2 100644 --- a/worker/src/task/task.ts +++ b/worker/src/task/task.ts @@ -84,7 +84,7 @@ export const run = async (mailTransport: any) => { log.warn = log.warning // for compatibility with old plugins // Start memory sampler: emits df-mem: lines on stdout for parent metrics, // and (when processing.debug) appends debug entries to run.log. - startMemoryReporter(processing, log.debug, config.worker.task.memorySampleIntervalMs) + const memReporter = startMemoryReporter(processing, log.debug, config.worker.task.memorySampleIntervalMs) if (run.status === 'running') { await log.step('Reprise après interruption.') } @@ -196,6 +196,7 @@ export const run = async (mailTransport: any) => { } return err } finally { + await memReporter.stop() try { await tmpDir.cleanup() } catch (err) {