Skip to content

Commit af00025

Browse files
authored
Fix log level parsing matching 'error' in non-error contexts (#34)
* fix: restrict log level detection to structured positions Log level parsing previously matched keywords like ERROR/WARN anywhere in the log message body, causing false positives (e.g. "connection error count" being flagged as error-level). Now only matches levels in: - JSON fields (level/severity/lvl) - Key=value pairs (level=ERROR) - Bracketed markers ([ERROR]) - Near line start (after optional timestamp) * fix: handle spaced and quoted key/value in log level detection KV_LEVEL_RE now matches formats like level = "error" and severity : 'warn' in addition to level=ERROR. * fix: correct word boundary placement in KV_LEVEL_RE regex Move \b before the optional closing quote so it anchors on the captured word characters, not on the quote.
1 parent 9ce3bbf commit af00025

1 file changed

Lines changed: 54 additions & 10 deletions

File tree

ui/providers/BottomDrawer/containers/LogViewer/utils/parseLogLine.ts

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,64 @@
11
import type { LogEntry, LogLevel, RawLogLine } from '../types';
22
import { parseAnsi } from './parseAnsi';
33

4-
const LEVEL_PATTERNS: Array<{ level: LogLevel; pattern: RegExp }> = [
5-
{ level: 'error', pattern: /\b(ERROR|ERR|FATAL|PANIC|CRITICAL)\b/i },
6-
{ level: 'warn', pattern: /\b(WARN|WARNING)\b/i },
7-
{ level: 'info', pattern: /\b(INFO)\b/i },
8-
{ level: 'debug', pattern: /\b(DEBUG|DBG)\b/i },
9-
{ level: 'trace', pattern: /\b(TRACE|TRC)\b/i },
4+
const LEVEL_KEYWORDS: Array<{ level: LogLevel; words: string[] }> = [
5+
{ level: 'error', words: ['error', 'err', 'fatal', 'panic', 'critical'] },
6+
{ level: 'warn', words: ['warn', 'warning'] },
7+
{ level: 'info', words: ['info'] },
8+
{ level: 'debug', words: ['debug', 'dbg'] },
9+
{ level: 'trace', words: ['trace', 'trc'] },
1010
];
1111

12+
const LEVEL_WORD_SET = new Map<string, LogLevel>(
13+
LEVEL_KEYWORDS.flatMap(({ level, words }) =>
14+
words.map((w) => [w, level] as const),
15+
),
16+
);
17+
18+
// Match level in JSON fields: "level":"error", "severity":"WARN", "lvl":"info"
19+
const JSON_LEVEL_RE =
20+
/["'](?:level|severity|lvl)["']\s*[:=]\s*["']([a-zA-Z]+)["']/i;
21+
22+
// Match bracketed level: [ERROR], [WARN], [info]
23+
const BRACKET_LEVEL_RE = /\[([A-Za-z]+)\]/g;
24+
25+
// Match key=value level: level=ERROR, severity=warn, level = "error", severity : 'warn'
26+
const KV_LEVEL_RE = /\b(?:level|severity|lvl)\s*[=:]\s*["']?([a-zA-Z]+)\b["']?/i;
27+
28+
// Match level keyword at/near line start (with optional leading timestamp):
29+
// "ERROR ...", "2024-01-01 ERROR ...", "2024-01-01T00:00:00Z ERROR ..."
30+
const START_LEVEL_RE = /^[\d\-T:.Z+/ ]{0,35}\b([A-Za-z]+)\b/;
31+
1232
function detectLevel(content: string): LogLevel | undefined {
13-
for (const { level, pattern } of LEVEL_PATTERNS) {
14-
if (pattern.test(content)) {
15-
return level;
16-
}
33+
// 1. JSON field match
34+
const jsonMatch = JSON_LEVEL_RE.exec(content);
35+
if (jsonMatch) {
36+
const found = LEVEL_WORD_SET.get(jsonMatch[1].toLowerCase());
37+
if (found) return found;
38+
}
39+
40+
// 2. Key=value match
41+
const kvMatch = KV_LEVEL_RE.exec(content);
42+
if (kvMatch) {
43+
const found = LEVEL_WORD_SET.get(kvMatch[1].toLowerCase());
44+
if (found) return found;
45+
}
46+
47+
// 3. Bracketed match — scan all brackets, return first recognized level
48+
BRACKET_LEVEL_RE.lastIndex = 0;
49+
let bracketMatch: RegExpExecArray | null;
50+
while ((bracketMatch = BRACKET_LEVEL_RE.exec(content)) !== null) {
51+
const found = LEVEL_WORD_SET.get(bracketMatch[1].toLowerCase());
52+
if (found) return found;
1753
}
54+
55+
// 4. Level keyword near line start (after optional timestamp)
56+
const startMatch = START_LEVEL_RE.exec(content);
57+
if (startMatch) {
58+
const found = LEVEL_WORD_SET.get(startMatch[1].toLowerCase());
59+
if (found) return found;
60+
}
61+
1862
return undefined;
1963
}
2064

0 commit comments

Comments
 (0)