Add LOGPile BMC diagnostic log analyzer

Features:
- Modular parser architecture for vendor-specific formats
- Inspur/Kaytus parser supporting asset.json, devicefrusdr.log,
  component.log, idl.log, and syslog files
- PCI Vendor/Device ID lookup for hardware identification
- Web interface with tabs: Events, Sensors, Config, Serials, Firmware
- Server specification summary with component grouping
- Export to CSV, JSON, TXT formats
- BMC alarm parsing from IDL logs (memory errors, PSU events, etc.)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-25 04:11:23 +03:00
parent fb800216f1
commit 512957545a
29 changed files with 4086 additions and 1 deletions

97
internal/parser/vendors/inspur/fru.go vendored Normal file
View File

@@ -0,0 +1,97 @@
package inspur
import (
"bufio"
"regexp"
"strings"
"git.mchus.pro/mchus/logpile/internal/models"
)
var (
fruDeviceRegex = regexp.MustCompile(`^FRU Device Description\s*:\s*(.+)$`)
fruFieldRegex = regexp.MustCompile(`^\s+(.+?)\s*:\s*(.*)$`)
)
// ParseFRU parses BMC FRU (Field Replaceable Unit) output
func ParseFRU(content []byte) []models.FRUInfo {
var fruList []models.FRUInfo
var current *models.FRUInfo
scanner := bufio.NewScanner(strings.NewReader(string(content)))
for scanner.Scan() {
line := scanner.Text()
// Check for new FRU device
if matches := fruDeviceRegex.FindStringSubmatch(line); matches != nil {
if current != nil && current.Description != "" {
fruList = append(fruList, *current)
}
current = &models.FRUInfo{
Description: strings.TrimSpace(matches[1]),
}
continue
}
// Skip if no current FRU device
if current == nil {
continue
}
// Skip "Device not present" entries
if strings.Contains(line, "Device not present") {
current = nil
continue
}
// Parse FRU fields
if matches := fruFieldRegex.FindStringSubmatch(line); matches != nil {
fieldName := strings.TrimSpace(matches[1])
fieldValue := strings.TrimSpace(matches[2])
switch fieldName {
case "Chassis Type":
current.ChassisType = fieldValue
case "Chassis Part Number":
if fieldValue != "0" {
current.PartNumber = fieldValue
}
case "Chassis Serial":
if fieldValue != "0" {
current.SerialNumber = fieldValue
}
case "Board Mfg Date":
current.MfgDate = fieldValue
case "Board Mfg", "Product Manufacturer":
if fieldValue != "NULL" {
current.Manufacturer = fieldValue
}
case "Board Product", "Product Name":
if fieldValue != "NULL" {
current.ProductName = fieldValue
}
case "Board Serial", "Product Serial":
current.SerialNumber = fieldValue
case "Board Part Number", "Product Part Number":
if fieldValue != "0" {
current.PartNumber = fieldValue
}
case "Product Version":
if fieldValue != "0" {
current.Version = fieldValue
}
case "Product Asset Tag":
if fieldValue != "NULL" {
current.AssetTag = fieldValue
}
}
}
}
// Don't forget the last one
if current != nil && current.Description != "" {
fruList = append(fruList, *current)
}
return fruList
}