fix: Parse both JSON and text formatted logs in parseWatchtower
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 11s

This commit is contained in:
Kroonk
2026-05-21 17:53:41 +02:00
parent ae116f52f9
commit ddca0ae831

150
server.js
View File

@@ -501,36 +501,102 @@ function parseWatchtower(lines) {
let current = null; let current = null;
for (const line of lines) { for (const line of lines) {
const timeMatch = line.match(/time="([^"]+)"/); let time = null;
const time = timeMatch?.[1] || null; let isStart = false;
let isFound = false;
let isWarning = false;
let isDone = false;
let msg = '';
if ( let failed = 0;
line.includes('Running a one time update.') let scanned = 0;
|| line.includes('Checking all containers') let updated = 0;
|| line.includes('Found new')
|| line.includes('Stopping /') // Try parsing as JSON first
|| line.includes('Creating /') try {
) { const parsed = JSON.parse(line);
time = parsed.time || null;
msg = parsed.msg || parsed.message || '';
if (
msg.includes('Running a one time update.')
|| msg.includes('Checking all containers')
|| msg.includes('Found new')
|| msg.includes('Stopping /')
|| msg.includes('Creating /')
) {
isStart = true;
}
if (msg.includes('Found new')) {
isFound = true;
}
if (parsed.level === 'warning' || parsed.level === 'warn') {
isWarning = true;
}
if (msg.includes('Session done')) {
isDone = true;
failed = Number(parsed.Failed ?? parsed.failed ?? 0);
scanned = Number(parsed.Scanned ?? parsed.scanned ?? 0);
updated = Number(parsed.Updated ?? parsed.updated ?? 0);
}
} catch {
// Fallback: Parse as standard text logfmt
const timeMatch = line.match(/time="([^"]+)"/);
time = timeMatch?.[1] || null;
if (
line.includes('Running a one time update.')
|| line.includes('Checking all containers')
|| line.includes('Found new')
|| line.includes('Stopping /')
|| line.includes('Creating /')
) {
isStart = true;
}
if (line.includes('Found new')) {
isFound = true;
const msgMatch = line.match(/msg="([^"]+)"/);
msg = msgMatch?.[1] || line;
}
if (line.includes('level=warning') || line.includes('level=warn')) {
isWarning = true;
const msgMatch = line.match(/msg="([^"]+)"/);
msg = msgMatch?.[1] || line;
}
const doneMatch = line.match(/Session done.*Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/);
if (doneMatch) {
isDone = true;
failed = Number(doneMatch[1]);
scanned = Number(doneMatch[2]);
updated = Number(doneMatch[3]);
}
}
// Process parsed information
if (isStart) {
current = current || { startedAt: time, found: [], warnings: [] }; current = current || { startedAt: time, found: [], warnings: [] };
} }
if (line.includes('Found new')) { if (isFound && current) {
current = current || { startedAt: time, found: [], warnings: [] }; current.found.push(msg || line);
current.found.push(line.replace(/^.*msg="/, '').replace(/"$/, ''));
} }
if (line.includes('level=warning')) { if (isWarning && current) {
current = current || { startedAt: time, found: [], warnings: [] }; current.warnings.push(msg || line);
current.warnings.push(line.replace(/^.*msg="/, '').replace(/"$/, ''));
} }
const done = line.match(/Session done.*Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/); if (isDone) {
if (done) {
current = current || { startedAt: time, found: [], warnings: [] }; current = current || { startedAt: time, found: [], warnings: [] };
current.finishedAt = time; current.finishedAt = time;
current.failed = Number(done[1]); current.failed = failed;
current.scanned = Number(done[2]); current.scanned = scanned;
current.updated = Number(done[3]); current.updated = updated;
sessions.push(current); sessions.push(current);
current = null; current = null;
} }
@@ -538,19 +604,37 @@ function parseWatchtower(lines) {
if (sessions.length) return sessions.at(-1); if (sessions.length) return sessions.at(-1);
const lastDoneLine = [...lines].reverse().find((line) => line.includes('Session done')); // Ultimate fallback if no full session was structured sequentially but a "Session done" exists
const done = lastDoneLine?.match(/Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/); for (const line of [...lines].reverse()) {
if (done) { try {
const time = lastDoneLine.match(/time="([^"]+)"/)?.[1] || null; const parsed = JSON.parse(line);
return { const msg = parsed.msg || parsed.message || '';
startedAt: null, if (msg.includes('Session done')) {
finishedAt: time, return {
failed: Number(done[1]), startedAt: null,
scanned: Number(done[2]), finishedAt: parsed.time || null,
updated: Number(done[3]), failed: Number(parsed.Failed ?? parsed.failed ?? 0),
found: [], scanned: Number(parsed.Scanned ?? parsed.scanned ?? 0),
warnings: [] updated: Number(parsed.Updated ?? parsed.updated ?? 0),
}; found: [],
warnings: []
};
}
} catch {
const doneMatch = line.match(/Session done.*Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/);
if (doneMatch) {
const timeMatch = line.match(/time="([^"]+)"/);
return {
startedAt: null,
finishedAt: timeMatch?.[1] || null,
failed: Number(doneMatch[1]),
scanned: Number(doneMatch[2]),
updated: Number(doneMatch[3]),
found: [],
warnings: []
};
}
}
} }
return null; return null;