import { readFile } from 'fs/promises'; import { Client, SFTPWrapper } from 'ssh2'; import { STAGING_CONFIG } from '../../../playwright.config'; export interface LogEntry { timestamp: string; level: 'INFO' | 'WARNING' | 'ERROR'; component: string; message: string; context?: Record; } export class LogParser { private sshConfig = { host: STAGING_CONFIG.ip, username: STAGING_CONFIG.sshUser, path: STAGING_CONFIG.path }; async parseLogFile(path: string): Promise { const logContent = await this.fetchLogContent(path); return this.parseLogContent(logContent); } async findRelatedEntries(entry: LogEntry): Promise { const allLogs = await this.parseLogFile('wordpress.log'); return allLogs.filter(log => log.component === entry.component && Math.abs(new Date(log.timestamp).getTime() - new Date(entry.timestamp).getTime()) < 5000 ); } async validateLogSequence(entries: LogEntry[], expectedFlow: string[]): Promise { let currentIndex = 0; for (const expected of expectedFlow) { let found = false; while (currentIndex < entries.length) { if (entries[currentIndex].message.includes(expected)) { found = true; break; } currentIndex++; } if (!found) return false; } return true; } private async fetchLogContent(path: string): Promise { const conn = new Client(); return new Promise((resolve, reject) => { conn.on('ready', () => { conn.sftp((err: Error | undefined, sftp: SFTPWrapper) => { if (err) { reject(err); return; } const remotePath = `${this.sshConfig.path}/wp-content/debug.log`; sftp.readFile(remotePath, (err: Error | undefined, data: Buffer) => { if (err) { reject(err); return; } resolve(data.toString()); conn.end(); }); }); }).connect(this.sshConfig); }); } private parseLogContent(content: string): LogEntry[] { const entries: LogEntry[] = []; content.split('\n') .filter(line => line.trim()) .forEach(line => { const match = line.match(/\[(.*?)\]\s+(\w+):\s+\[(\w+)\]\s+(.*)/); if (!match) return; entries.push({ timestamp: match[1], level: match[2] as 'INFO' | 'WARNING' | 'ERROR', component: match[3], message: match[4], context: this.extractContext(match[4]) }); }); return entries; } private extractContext(message: string): Record { const contextMatch = message.match(/\{.*\}/); if (!contextMatch) return {}; try { return JSON.parse(contextMatch[0]); } catch { return {}; } } }