// Package inspurlegacy parses the legacy Inspur "onekeylog" BMC diagnostic // archive produced by older AMI-based Inspur BMCs (e.g. NF5466M5 / NF5280M5 // generation). That format predates the Kaytus-style onekeylog handled by the // internal/parser/vendors/inspur package: it has no asset.json, no // devicefrusdr.log, no selelist.csv and no component.log. Instead hardware // inventory lives in Inspur_AssetInfoInventory.log, FRU data in a binary // FRU.bin, the event log in an Inspur___IDL text file plus a // pipe-delimited sel.log, and BMC syslog in flat .log files at the archive // root. // // IMPORTANT: bump parserVersion on any behavior change (see module-versioning). package inspurlegacy import ( "strings" "git.mchus.pro/mchus/logpile/internal/models" "git.mchus.pro/mchus/logpile/internal/parser" ) // parserVersion follows the N.M module-versioning scheme. const parserVersion = "1.0" func init() { parser.Register(&Parser{}) } // Parser implements parser.VendorParser for the legacy Inspur onekeylog format. type Parser struct{} // Name returns a human-readable parser name. func (p *Parser) Name() string { return "Inspur Legacy onekeylog BMC Parser" } // Vendor returns the parser identifier. func (p *Parser) Vendor() string { return "inspur_legacy" } // Version returns the parser module version. func (p *Parser) Version() string { return parserVersion } // DetectPriority makes this parser win a confidence tie against the broader // Kaytus-style Inspur parser, which also matches anything under onekeylog/. func (p *Parser) DetectPriority() int { return 10 } // modernMarkers are files that only appear in the newer Kaytus-style onekeylog. // Their presence means the other Inspur parser should handle the archive. var modernMarkers = []string{ "asset.json", "devicefrusdr.log", "selelist.csv", "component.log", "onekeylog_dreport.log", } // Detect returns a confidence score for the legacy onekeylog format. func (p *Parser) Detect(files []parser.ExtractedFile) int { confidence := 0 for _, f := range files { base := strings.ToLower(baseName(f.Path)) for _, m := range modernMarkers { if base == m { return 0 } } switch { case base == "inspur_assetinfoinventory.log": confidence += 40 case isLegacyIDLName(base): confidence += 30 case base == "fru.bin": confidence += 15 case base == "blackbox.log": confidence += 10 case base == "eepromdata.log": confidence += 10 case base == "sdr.dat": confidence += 10 case base == "sadtadreg.dat": confidence += 5 } } if confidence > 100 { return 100 } return confidence } // Parse extracts events, FRU and hardware inventory from a legacy onekeylog. func (p *Parser) Parse(files []parser.ExtractedFile) (*models.AnalysisResult, error) { result := &models.AnalysisResult{ Events: make([]models.Event, 0), FRU: make([]models.FRUInfo, 0), Sensors: make([]models.SensorReading, 0), } hw := &models.HardwareConfig{} result.Hardware = hw if f := parser.FindFileByName(files, "FRU.bin"); f != nil { if fru, ok := DecodeBinaryFRU(f.Content); ok { result.FRU = append(result.FRU, fru.toFRUInfo()) fru.applyToBoardInfo(&hw.BoardInfo) } } if f := parser.FindFileByName(files, "Inspur_AssetInfoInventory.log"); f != nil { ParseAssetInfoInventory(f.Content, hw) } // Event sources, most structured first. for _, f := range findLegacyIDLFiles(files) { result.Events = append(result.Events, ParseIDLEvents(f.Content)...) } if f := parser.FindFileByName(files, "sel.log"); f != nil { result.Events = append(result.Events, ParseSELLog(f.Content)...) } if f := parser.FindFileByName(files, "blackbox.log"); f != nil { result.Events = append(result.Events, ParseBlackbox(f.Content)...) } for _, f := range findRAIDLogFiles(files) { result.Events = append(result.Events, ParseMegaRAIDLog(f.Content, baseName(f.Path))...) } for _, f := range findSyslogFiles(files) { result.Events = append(result.Events, ParseAMISyslog(f.Content, baseName(f.Path))...) } result.Events = dedupeEvents(result.Events) sortEvents(result.Events) // This archive class carries no live sensor readings: SDR.dat holds only // sensor definitions, and there is no "sensor list" capture. Record why the // sensor set is empty instead of leaving it silently unexplained. result.CollectionErrors = append(result.CollectionErrors, models.CollectionError{ Section: "sensors", Message: "legacy onekeylog carries no live sensor readings (SDR.dat holds definitions only, no sensor-list capture)", }) return result, nil } func baseName(path string) string { path = strings.ReplaceAll(path, "\\", "/") if i := strings.LastIndex(path, "/"); i >= 0 { return path[i+1:] } return path } // isLegacyIDLName matches the "Inspur___IDL" event-log filename // (no extension), lower-cased. func isLegacyIDLName(base string) bool { return strings.HasPrefix(base, "inspur_") && strings.HasSuffix(base, "_idl") } func findLegacyIDLFiles(files []parser.ExtractedFile) []parser.ExtractedFile { var out []parser.ExtractedFile for _, f := range files { if isLegacyIDLName(strings.ToLower(baseName(f.Path))) { out = append(out, f) } } return out } func findRAIDLogFiles(files []parser.ExtractedFile) []parser.ExtractedFile { var out []parser.ExtractedFile for _, f := range files { base := strings.ToLower(baseName(f.Path)) if strings.HasPrefix(base, "raid") && strings.HasSuffix(base, ".log") { out = append(out, f) } } return out } // syslogFileNames are the flat AMI BMC severity logs at the archive root that // carry fault-relevant records. debug.log and audit.log are intentionally left // out: they are high-volume housekeeping noise, not diagnostics. var syslogFileNames = map[string]struct{}{ "emerg.log": {}, "alert.log": {}, "crit.log": {}, "err.log": {}, "warning.log": {}, } func findSyslogFiles(files []parser.ExtractedFile) []parser.ExtractedFile { var out []parser.ExtractedFile for _, f := range files { if _, ok := syslogFileNames[strings.ToLower(baseName(f.Path))]; ok { out = append(out, f) } } return out }