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.
This commit is contained in:
nguyentrungthat 2025-12-19 15:42:02 +07:00
parent b8ab1f0583
commit b9598f9351
3 changed files with 227 additions and 33 deletions

View File

@ -788,12 +788,7 @@ export default class LineConnection {
handleLogLine = (line: string) => { handleLogLine = (line: string) => {
try { try {
const parsed = classifyLog(line) const parsed = classifyLog(line)
const matchedRules = applyRules(parsed) this.session.applyParsedLog(parsed)
matchedRules.forEach((rule) => {
// console.log(rule)
this.session.applyRule(rule)
})
} catch (error) { } catch (error) {
console.log('handleLogLine', error) console.log('handleLogLine', error)
} }
@ -807,7 +802,16 @@ export default class LineConnection {
return return
} }
const result = this.session.finalize() 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) { } catch (err: any) {
console.error('Error checking log:', err) console.error('Error checking log:', err)
} }

View File

@ -3,7 +3,7 @@ import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import nodeMailer from 'nodemailer' import nodeMailer from 'nodemailer'
import zulip from 'zulip-js' import zulip from 'zulip-js'
import { LogRule, ParsedLog } from './types.js' import { LogRule, ParsedLog, TestError, TestResult } from './types.js'
type DetectAI = { type DetectAI = {
status: string[] status: string[]
@ -351,58 +351,226 @@ export function classifyLog(line: string): ParsedLog {
} }
export const RULES: LogRule[] = [ export const RULES: LogRule[] = [
// BOOT
{ {
id: 'BOOT_OK', id: 'BOOT_OK',
category: 'BOOT', category: 'BOOT',
match: /IOS XE Software|System Bootstrap/, match: /IOS XE Software|System Bootstrap|Boot successful/i,
result: 'PASS', level: 'PASS',
message: 'Boot successful', message: 'Boot successful',
}, },
{ {
id: 'BOOT_LOOP', id: 'BOOT_LOOP',
category: 'BOOT', category: 'BOOT',
match: /boot loop|reloading|restart/i, match: /boot loop|reloading|restart/i,
result: 'FAIL', level: 'FAIL',
message: 'Boot loop detected', 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', id: 'LICENSE_EXPIRED',
category: 'LICENSE', category: 'LICENSE',
match: /Evaluation.*expired|license expired/i, match: /Evaluation.*expired|license expired/i,
result: 'WARN', level: 'WARN',
message: 'License expired', 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', category: 'HARDWARE',
match: /(FAN|PSU).*(FAIL|CRITICAL)/i, match: /FAN.*(FAIL|CRITICAL|NOT PRESENT)/i,
result: 'FAIL', level: 'FAIL',
message: 'Hardware failure', 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[] { export function applyRules(log: ParsedLog): TestError[] {
return RULES.filter((rule) => rule.category === log.category && rule.match.test(log.raw)) 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 { export class TestSession {
boot = false bootOk = false
hasHwFail = false errors: TestError[] = []
licenseWarn = false
issues: string[] = []
public applyRule(rule: LogRule) { // 👉 dùng Set để deduplicate
if (rule.id === 'BOOT_OK') this.boot = true private errorFingerprints = new Set<string>()
if (rule.result === 'FAIL') this.hasHwFail = true
if (rule.result === 'WARN') this.licenseWarn = true applyParsedLog(log: ParsedLog) {
this.issues.push(rule.message) // 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() { private addError(err: TestError) {
if (!this.boot) return 'FAIL' const fingerprint = `${err.ruleId}|${this.normalize(err.evidence.raw)}`
if (this.hasHwFail) return 'FAIL'
if (this.licenseWarn) return 'PARTIAL' const existing = this.errors.find(
return 'PASS' (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'
}
} }
} }

View File

@ -22,6 +22,28 @@ export interface LogRule {
id: string id: string
category: LogCategory category: LogCategory
match: RegExp match: RegExp
result: RuleResult level: RuleResult
message: string 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[]
}