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;
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 time = timeMatch?.[1] || null;
time = timeMatch?.[1] || null;
if (
line.includes('Running a one time update.')
@@ -511,26 +554,49 @@ function parseWatchtower(lines) {
|| line.includes('Stopping /')
|| line.includes('Creating /')
) {
current = current || { startedAt: time, found: [], warnings: [] };
isStart = true;
}
if (line.includes('Found new')) {
current = current || { startedAt: time, found: [], warnings: [] };
current.found.push(line.replace(/^.*msg="/, '').replace(/"$/, ''));
isFound = true;
const msgMatch = line.match(/msg="([^"]+)"/);
msg = msgMatch?.[1] || line;
}
if (line.includes('level=warning')) {
current = current || { startedAt: time, found: [], warnings: [] };
current.warnings.push(line.replace(/^.*msg="/, '').replace(/"$/, ''));
if (line.includes('level=warning') || line.includes('level=warn')) {
isWarning = true;
const msgMatch = line.match(/msg="([^"]+)"/);
msg = msgMatch?.[1] || line;
}
const done = line.match(/Session done.*Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/);
if (done) {
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: [] };
}
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.finishedAt = time;
current.failed = Number(done[1]);
current.scanned = Number(done[2]);
current.updated = Number(done[3]);
current.failed = failed;
current.scanned = scanned;
current.updated = updated;
sessions.push(current);
current = null;
}
@@ -538,20 +604,38 @@ function parseWatchtower(lines) {
if (sessions.length) return sessions.at(-1);
const lastDoneLine = [...lines].reverse().find((line) => line.includes('Session done'));
const done = lastDoneLine?.match(/Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/);
if (done) {
const time = lastDoneLine.match(/time="([^"]+)"/)?.[1] || null;
// Ultimate fallback if no full session was structured sequentially but a "Session done" exists
for (const line of [...lines].reverse()) {
try {
const parsed = JSON.parse(line);
const msg = parsed.msg || parsed.message || '';
if (msg.includes('Session done')) {
return {
startedAt: null,
finishedAt: time,
failed: Number(done[1]),
scanned: Number(done[2]),
updated: Number(done[3]),
finishedAt: parsed.time || null,
failed: Number(parsed.Failed ?? parsed.failed ?? 0),
scanned: Number(parsed.Scanned ?? parsed.scanned ?? 0),
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;
}