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
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 11s
This commit is contained in:
150
server.js
150
server.js
@@ -501,36 +501,102 @@ function parseWatchtower(lines) {
|
||||
let current = null;
|
||||
|
||||
for (const line of lines) {
|
||||
const timeMatch = line.match(/time="([^"]+)"/);
|
||||
const time = timeMatch?.[1] || null;
|
||||
let time = null;
|
||||
let isStart = false;
|
||||
let isFound = false;
|
||||
let isWarning = false;
|
||||
let isDone = false;
|
||||
let msg = '';
|
||||
|
||||
if (
|
||||
line.includes('Running a one time update.')
|
||||
|| line.includes('Checking all containers')
|
||||
|| line.includes('Found new')
|
||||
|| line.includes('Stopping /')
|
||||
|| line.includes('Creating /')
|
||||
) {
|
||||
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="([^"]+)"/);
|
||||
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: [] };
|
||||
}
|
||||
|
||||
if (line.includes('Found new')) {
|
||||
current = current || { startedAt: time, found: [], warnings: [] };
|
||||
current.found.push(line.replace(/^.*msg="/, '').replace(/"$/, ''));
|
||||
if (isFound && current) {
|
||||
current.found.push(msg || line);
|
||||
}
|
||||
|
||||
if (line.includes('level=warning')) {
|
||||
current = current || { startedAt: time, found: [], warnings: [] };
|
||||
current.warnings.push(line.replace(/^.*msg="/, '').replace(/"$/, ''));
|
||||
if (isWarning && current) {
|
||||
current.warnings.push(msg || line);
|
||||
}
|
||||
|
||||
const done = line.match(/Session done.*Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/);
|
||||
if (done) {
|
||||
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,19 +604,37 @@ 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;
|
||||
return {
|
||||
startedAt: null,
|
||||
finishedAt: time,
|
||||
failed: Number(done[1]),
|
||||
scanned: Number(done[2]),
|
||||
updated: Number(done[3]),
|
||||
found: [],
|
||||
warnings: []
|
||||
};
|
||||
// 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: 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;
|
||||
|
||||
Reference in New Issue
Block a user