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

124
server.js
View File

@@ -501,8 +501,51 @@ function parseWatchtower(lines) {
let current = null; let current = null;
for (const line of lines) { for (const line of lines) {
let time = null;
let isStart = false;
let isFound = false;
let isWarning = false;
let isDone = false;
let msg = '';
let failed = 0;
let scanned = 0;
let updated = 0;
// Try parsing as JSON first
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="([^"]+)"/); const timeMatch = line.match(/time="([^"]+)"/);
const time = timeMatch?.[1] || null; time = timeMatch?.[1] || null;
if ( if (
line.includes('Running a one time update.') line.includes('Running a one time update.')
@@ -511,26 +554,49 @@ function parseWatchtower(lines) {
|| line.includes('Stopping /') || line.includes('Stopping /')
|| line.includes('Creating /') || line.includes('Creating /')
) { ) {
current = current || { startedAt: time, found: [], warnings: [] }; isStart = true;
} }
if (line.includes('Found new')) { if (line.includes('Found new')) {
current = current || { startedAt: time, found: [], warnings: [] }; isFound = true;
current.found.push(line.replace(/^.*msg="/, '').replace(/"$/, '')); const msgMatch = line.match(/msg="([^"]+)"/);
msg = msgMatch?.[1] || line;
} }
if (line.includes('level=warning')) { if (line.includes('level=warning') || line.includes('level=warn')) {
current = current || { startedAt: time, found: [], warnings: [] }; isWarning = true;
current.warnings.push(line.replace(/^.*msg="/, '').replace(/"$/, '')); const msgMatch = line.match(/msg="([^"]+)"/);
msg = msgMatch?.[1] || line;
} }
const done = line.match(/Session done.*Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/); const doneMatch = line.match(/Session done.*Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/);
if (done) { 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: [] };
}
if (isFound && current) {
current.found.push(msg || line);
}
if (isWarning && current) {
current.warnings.push(msg || line);
}
if (isDone) {
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,20 +604,38 @@ 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);
const msg = parsed.msg || parsed.message || '';
if (msg.includes('Session done')) {
return { return {
startedAt: null, startedAt: null,
finishedAt: time, finishedAt: parsed.time || null,
failed: Number(done[1]), failed: Number(parsed.Failed ?? parsed.failed ?? 0),
scanned: Number(done[2]), scanned: Number(parsed.Scanned ?? parsed.scanned ?? 0),
updated: Number(done[3]), updated: Number(parsed.Updated ?? parsed.updated ?? 0),
found: [], found: [],
warnings: [] 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;
} }