diff --git a/packages/webview/src/component/pods/PodLogs.svelte b/packages/webview/src/component/pods/PodLogs.svelte index dd17d026..f9e4a198 100644 --- a/packages/webview/src/component/pods/PodLogs.svelte +++ b/packages/webview/src/component/pods/PodLogs.svelte @@ -8,7 +8,7 @@ import NoLogIcon from '/@/component/icons/NoLogIcon.svelte'; import type { Terminal } from '@xterm/xterm'; import TerminalWindow from '/@/component/terminal/TerminalWindow.svelte'; import { SvelteMap } from 'svelte/reactivity'; -import { ansi256Colours, colourizedANSIContainerName } from '/@/component/terminal/terminal-colors'; +import { ansi256Colours, colourizedANSIContainerName, colorizeLogLevel } from '/@/component/terminal/terminal-colors'; interface Props { object: V1Pod; @@ -54,13 +54,17 @@ onMount(async () => { // All lines are prefixed, except the last one if it's empty. const lines = data .split('\n') + .map(line => colorizeLogLevel(line)) .map((line, index, arr) => index < arr.length - 1 || line.length > 0 ? `${padding}${colouredName}|${line}` : line, ); callback(lines.join('\n')); } : (_name: string, data: string, callback: (data: string) => void): void => { - callback(data); + const lines = data + .split('\n') + .map(line => colorizeLogLevel(line)); + callback(lines.join('\n')); }; for (const containerName of object.spec?.containers.map(c => c.name) ?? []) { diff --git a/packages/webview/src/component/terminal/terminal-colors.ts b/packages/webview/src/component/terminal/terminal-colors.ts index 11d50efc..64502d99 100644 --- a/packages/webview/src/component/terminal/terminal-colors.ts +++ b/packages/webview/src/component/terminal/terminal-colors.ts @@ -32,8 +32,52 @@ export const ansi256Colours = [ '\u001b[34;1m', // bright blue ]; +// ANSI colors for log levels +const LOG_LEVEL_COLORS: Record = { + DBG: '\u001b[36m', // cyan + DEBUG: '\u001b[36m', + INF: '\u001b[32m', // green + INFO: '\u001b[32m', + WRN: '\u001b[33m', // yellow + WARN: '\u001b[33m', + WARNING: '\u001b[33m', + ERR: '\u001b[31m', // red + ERROR: '\u001b[31m', + FATAL: '\u001b[31;1m', // bright red + TRACE: '\u001b[35m', // magenta +}; + // Function that takes the container name and ANSI colour and encapsulates the name in the colour, // making sure that we reset the colour back to white after the name. export function colourizedANSIContainerName(name: string, colour: string): string { return `${colour}${name}\u001b[0m`; } + +/** + * Colorizes log levels in brackets for better readability. + * Detects patterns like [timestamp LEVEL] or [LEVEL] anywhere in the line. + * + * Examples: + * - [23:10:06 INF] -> green + * - [DBG] -> cyan + * - [ERROR] -> red + * - 2025-10-29T23:10:10.688386132-05:00 [23:10:10 INF] -> green (with K8s timestamp) + */ +export function colorizeLogLevel(logLine: string): string { + // Pattern to match [timestamp? LEVEL] anywhere in the line (removed ^ anchor) + const logLevelPattern = /(\[(?:[0-9:]+\s+)?)(DBG|DEBUG|INF|INFO|WRN|WARN|WARNING|ERR|ERROR|FATAL|TRACE)(\])/i; + + const match = logLine.match(logLevelPattern); + if (match && match.index !== undefined) { + const beforeBracket = logLine.slice(0, match.index); + const prefix = match[1]; // [timestamp or [ + const level = match[2].toUpperCase(); + const suffix = match[3]; // ] + const rest = logLine.slice(match.index + match[0].length); + + const color = LOG_LEVEL_COLORS[level] || '\u001b[37m'; + return `${beforeBracket}${color}${prefix}${level}${suffix}\u001b[0m${rest}`; + } + + return logLine; +}