diff --git a/vscode-appdaemon/src/logExplorer.ts b/vscode-appdaemon/src/logExplorer.ts index a2c93e2..97e14c7 100644 --- a/vscode-appdaemon/src/logExplorer.ts +++ b/vscode-appdaemon/src/logExplorer.ts @@ -5,7 +5,7 @@ import { randomBytes } from 'crypto'; import { LogEntry, parseLine, decodeLogLine } from './logModel'; import { findLogSource, SourceDocument } from './logSource'; -type Cursor = { offset: number; identity: string; pending: Buffer; last?: LogEntry; discard: boolean; generation: string }; +type Cursor = { offset: number; identity: string; pending: Buffer; last?: LogEntry; errorBlock?: boolean; discard: boolean; generation: string }; export class LogExplorer implements vscode.Disposable { private panel?: vscode.WebviewPanel; private view?: vscode.WebviewView; @@ -168,6 +168,25 @@ export class LogExplorer implements vscode.Disposable { if (cursor.discard) { cursor.discard = false; continue; } const entry = parseLine(line, file, position, zone); if (entry) { + const isErrorLog = path.basename(file) === 'error.log'; + const failedApp = isErrorLog && entry.app === 'Error' && entry.level === 'ERROR' + ? entry.message.match(/^=+\s+Failed to start '([^']+)'/) + : undefined; + if (failedApp) { + entry.app = failedApp[1]; + entry.message = `Failed to start '${failedApp[1]}'`; + cursor.errorBlock = true; + } else if (isErrorLog && cursor.errorBlock && cursor.last && entry.app === 'Error' && entry.level === 'ERROR') { + if ((cursor.last.raw?.length || 0) < 64 * 1024) { + cursor.last.message += '\n' + entry.message; + cursor.last.raw += '\n' + line; + } + if (/^=+\s*$/.test(entry.message)) { cursor.errorBlock = false; } + changed = true; + continue; + } else if (cursor.errorBlock) { + cursor.errorBlock = false; + } if (!entry.trace_id && !line.startsWith('{')) { entry.event_id += ':' + cursor.generation; } const existing = this.entries.get(entry.event_id); if (!existing || entry.raw?.startsWith('{')) { this.entries.set(entry.event_id, entry); } diff --git a/vscode-appdaemon/src/logModel.ts b/vscode-appdaemon/src/logModel.ts index 1b1eb11..a33ead5 100644 --- a/vscode-appdaemon/src/logModel.ts +++ b/vscode-appdaemon/src/logModel.ts @@ -14,9 +14,15 @@ export function rawTime(date: string, zone: string): number { const cacheKey = zone + ':' + date; const cached = timeCache.get(cacheKey); if (cached !== undefined) { return cached; } - const m = date.match(/^(\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/); + const m = date.match(/^(?:(\d{4})-(\d\d)-(\d\d)|(\d\d)-(\d\d)-(\d\d)) (\d\d):(\d\d):(\d\d)/); if (!m) { return NaN; } - const utc = Date.UTC(2000 + Number(m[3]), Number(m[2]) - 1, Number(m[1]), Number(m[4]), Number(m[5]), Number(m[6])); + const year = Number(m[1] || (2000 + Number(m[6]))); + const month = Number(m[2] || m[5]); + const day = Number(m[3] || m[4]); + const hour = Number(m[7]); + const minute = Number(m[8]); + const second = Number(m[9]); + const utc = Date.UTC(year, month - 1, day, hour, minute, second); try { let formatter = formatters.get(zone); if (!formatter) { formatter = new Intl.DateTimeFormat('en-GB', { timeZone: zone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23' }); formatters.set(zone, formatter); } @@ -36,7 +42,7 @@ export function parseLine(line: string, file: string, offset: number, zone = 'Eu return { ...e, level: String(e.level || 'INFO'), kind: String(e.kind || 'log'), file, raw: line, time: Date.parse(e.timestamp) }; } catch { return undefined; } } - const m = line.match(/^(\d{2}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?)\s+(DEBUG|INFO|WARNING|ERROR|CRITICAL)\s+([^:]+):\s?(.*)$/); + const m = line.match(/^((?:\d{2}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2}(?:\.\d+)?)\s+(DEBUG|INFO|WARNING|ERROR|CRITICAL)\s+([^:]+):\s?(.*)$/); if (!m) { return undefined; } const group = m[4].match(/\[group=([^\]\r\n]+):([a-f0-9]{12})\]/i); const id = m[4].match(/\[event=([\w-]+) trace=([\w-]+)\]/); diff --git a/vscode-appdaemon/vscode-appdaemon-0.2.0.vsix b/vscode-appdaemon/vscode-appdaemon-0.2.0.vsix index cbdadbf..4332377 100644 Binary files a/vscode-appdaemon/vscode-appdaemon-0.2.0.vsix and b/vscode-appdaemon/vscode-appdaemon-0.2.0.vsix differ