From b9598f9351b64c101257dd11ecf6cddadffeadac Mon Sep 17 00:00:00 2001 From: nguyentrungthat <80239428+nguentrungthat@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:42:02 +0700 Subject: [PATCH] Refactor log rule handling and enhance test session logic Replaces per-rule application with a unified error-based approach in log processing. Expands and clarifies log rules, introduces deduplication and error counting in TestSession, and updates types to support richer error and result reporting. Improves final result output for better clarity and debugging. --- BACKEND/app/services/line_connection.ts | 18 +- BACKEND/app/ultils/helper.ts | 218 +++++++++++++++++++++--- BACKEND/app/ultils/types.ts | 24 ++- 3 files changed, 227 insertions(+), 33 deletions(-) diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index 78f9bf5..09214e4 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -788,12 +788,7 @@ export default class LineConnection { handleLogLine = (line: string) => { try { const parsed = classifyLog(line) - const matchedRules = applyRules(parsed) - - matchedRules.forEach((rule) => { - // console.log(rule) - this.session.applyRule(rule) - }) + this.session.applyParsedLog(parsed) } catch (error) { console.log('handleLogLine', error) } @@ -807,7 +802,16 @@ export default class LineConnection { return } const result = this.session.finalize() - console.log('FINAL RESULT:', this.config.apcName, this.config.lineNumber, result) + + console.log('===== TEST RESULT =====') + console.log('STATUS:', result.status) + console.log('SUMMARY:', result.summary) + + result.errors.forEach((err, idx) => { + console.log(`\n[${idx + 1}] ${err.level} - ${err.ruleId}`) + console.log('Message:', err.message) + console.log('Log:', err.evidence.raw) + }) } catch (err: any) { console.error('Error checking log:', err) } diff --git a/BACKEND/app/ultils/helper.ts b/BACKEND/app/ultils/helper.ts index 373e1f5..afe3388 100644 --- a/BACKEND/app/ultils/helper.ts +++ b/BACKEND/app/ultils/helper.ts @@ -3,7 +3,7 @@ import fs from 'node:fs' import path from 'node:path' import nodeMailer from 'nodemailer' import zulip from 'zulip-js' -import { LogRule, ParsedLog } from './types.js' +import { LogRule, ParsedLog, TestError, TestResult } from './types.js' type DetectAI = { status: string[] @@ -351,58 +351,226 @@ export function classifyLog(line: string): ParsedLog { } export const RULES: LogRule[] = [ + // BOOT { id: 'BOOT_OK', category: 'BOOT', - match: /IOS XE Software|System Bootstrap/, - result: 'PASS', + match: /IOS XE Software|System Bootstrap|Boot successful/i, + level: 'PASS', message: 'Boot successful', }, { id: 'BOOT_LOOP', category: 'BOOT', match: /boot loop|reloading|restart/i, - result: 'FAIL', + level: 'FAIL', message: 'Boot loop detected', }, + { + id: 'BOOT_CRASH', + category: 'BOOT', + match: /crashinfo|Traceback|Kernel panic/i, + level: 'FAIL', + message: 'System crash detected during boot', + }, + { + id: 'BOOT_SLOW', + category: 'BOOT', + match: /Booting.*takes longer than expected/i, + level: 'WARN', + message: 'Boot time abnormal', + }, + // LICENSE + { + id: 'LICENSE_OK', + category: 'LICENSE', + match: /License State:\s*ACTIVE|Smart Licensing Status:\s*AUTHORIZED/i, + level: 'PASS', + message: 'License active', + }, { id: 'LICENSE_EXPIRED', category: 'LICENSE', match: /Evaluation.*expired|license expired/i, - result: 'WARN', + level: 'WARN', message: 'License expired', }, { - id: 'HW_FAIL', + id: 'LICENSE_NOT_REGISTERED', + category: 'LICENSE', + match: /NOT REGISTERED|Registration failed/i, + level: 'WARN', + message: 'License not registered', + }, + { + id: 'LICENSE_DISABLED', + category: 'LICENSE', + match: /Feature.*disabled due to license/i, + level: 'FAIL', + message: 'Critical features disabled by license', + }, + // INTERFACE + { + id: 'INTERFACE_UP', + category: 'INTERFACE', + match: /LINK-3-UPDOWN: Interface .* up/i, + level: 'PASS', + message: 'Interface up', + }, + { + id: 'INTERFACE_FLAP', + category: 'INTERFACE', + match: /LINK-3-UPDOWN: Interface .* down/i, + level: 'WARN', + message: 'Interface flapping detected', + }, + { + id: 'INTERFACE_ERROR', + category: 'INTERFACE', + match: /input errors|CRC|frame error/i, + level: 'WARN', + message: 'Interface errors detected', + }, + // HARDWARE + { + id: 'FAN_FAIL', category: 'HARDWARE', - match: /(FAN|PSU).*(FAIL|CRITICAL)/i, - result: 'FAIL', - message: 'Hardware failure', + match: /FAN.*(FAIL|CRITICAL|NOT PRESENT)/i, + level: 'FAIL', + message: 'Fan failure', + }, + { + id: 'PSU_FAIL', + category: 'HARDWARE', + match: /PSU.*(FAIL|CRITICAL|NOT PRESENT)/i, + level: 'FAIL', + message: 'Power supply failure', + }, + { + id: 'TEMP_HIGH', + category: 'HARDWARE', + match: /TEMP.*(HIGH|CRITICAL)/i, + level: 'FAIL', + message: 'Over temperature detected', + }, + { + id: 'HW_WARNING', + category: 'HARDWARE', + match: /ENVIRONMENT WARNING/i, + level: 'WARN', + message: 'Hardware environment warning', + }, + // ERROR + { + id: 'MEMORY_ERROR', + category: 'ERROR', + match: /malloc|out of memory|memory corruption/i, + level: 'FAIL', + message: 'Memory error detected', + }, + { + id: 'FLASH_ERROR', + category: 'ERROR', + match: /flash.*(error|corrupt|fail)/i, + level: 'FAIL', + message: 'Flash storage error', + }, + { + id: 'CONFIG_MISSING', + category: 'ERROR', + match: /startup-config is missing|No configuration found/i, + level: 'WARN', + message: 'Startup configuration missing', + }, + { + id: 'SECURE_BOOT_FAIL', + category: 'ERROR', + match: /Secure Boot.*(FAIL|ERROR)/i, + level: 'FAIL', + message: 'Secure boot failed', }, ] -export function applyRules(log: ParsedLog): LogRule[] { - return RULES.filter((rule) => rule.category === log.category && rule.match.test(log.raw)) +export function applyRules(log: ParsedLog): TestError[] { + return RULES.filter( + (rule): rule is LogRule & { level: 'FAIL' | 'WARN' } => + rule.category === log.category && rule.match.test(log.raw) && rule.level !== 'PASS' + ).map((rule) => ({ + ruleId: rule.id, + level: rule.level, // ✅ giờ TS biết chắc chỉ FAIL | WARN + message: rule.message, + evidence: { + raw: log.raw, + timestamp: log.timestamp, + }, + })) } export class TestSession { - boot = false - hasHwFail = false - licenseWarn = false - issues: string[] = [] + bootOk = false + errors: TestError[] = [] - public applyRule(rule: LogRule) { - if (rule.id === 'BOOT_OK') this.boot = true - if (rule.result === 'FAIL') this.hasHwFail = true - if (rule.result === 'WARN') this.licenseWarn = true - this.issues.push(rule.message) + // 👉 dùng Set để deduplicate + private errorFingerprints = new Set() + + applyParsedLog(log: ParsedLog) { + // Detect boot OK + if (/IOS XE Software|System Bootstrap/.test(log.raw)) { + this.bootOk = true + } + + const matchedErrors = applyRules(log) + matchedErrors.forEach((err) => this.addError(err)) } - public finalize() { - if (!this.boot) return 'FAIL' - if (this.hasHwFail) return 'FAIL' - if (this.licenseWarn) return 'PARTIAL' - return 'PASS' + private addError(err: TestError) { + const fingerprint = `${err.ruleId}|${this.normalize(err.evidence.raw)}` + + const existing = this.errors.find( + (e) => `${e.ruleId}|${this.normalize(e.evidence.raw)}` === fingerprint + ) + + if (existing) { + existing.evidence.count = (existing.evidence.count ?? 1) + 1 + return + } + + err.evidence.count = 1 + this.errors.push(err) + } + + private normalize(raw: string): string { + return raw + .toLowerCase() + .replace(/\d+/g, '#') // thay số (PSU 1, PSU 2 → PSU #) + .replace(/\s+/g, ' ') + .trim() + } + + finalize(): TestResult { + const hasFail = this.errors.some((e) => e.level === 'FAIL') + const hasWarn = this.errors.some((e) => e.level === 'WARN') + + let status: TestResult['status'] = 'PASS' + if (!this.bootOk || hasFail) status = 'FAIL' + else if (hasWarn) status = 'PARTIAL' + + return { + status, + summary: this.buildSummary(status), + errors: this.errors, + } + } + + private buildSummary(status: TestResult['status']): string { + switch (status) { + case 'PASS': + return 'All tests passed' + case 'FAIL': + return 'Critical errors detected' + case 'PARTIAL': + return 'Warnings detected during test' + } } } diff --git a/BACKEND/app/ultils/types.ts b/BACKEND/app/ultils/types.ts index 4a66cb3..ceda333 100644 --- a/BACKEND/app/ultils/types.ts +++ b/BACKEND/app/ultils/types.ts @@ -22,6 +22,28 @@ export interface LogRule { id: string category: LogCategory match: RegExp - result: RuleResult + level: RuleResult message: string } + +export interface LogEvidence { + raw: string + timestamp?: Date +} + +export interface TestError { + ruleId: string + level: 'FAIL' | 'WARN' + message: string + evidence: { + raw: string + timestamp?: Date + count?: number + } +} + +export interface TestResult { + status: 'PASS' | 'FAIL' | 'PARTIAL' + summary: string + errors: TestError[] +}