// Parses log/bmc/commer-comp/*, per-component BMC transcripts (slot presence, // HGX management controller, switch VR/power, switch CPLD). Each rotated log // (component.log plus component.tar.gz.0..9) uses the same // "[timestamp][file, line][level] message" line format. package inspur import ( "archive/tar" "bufio" "bytes" "compress/gzip" "fmt" "io" "regexp" "strings" "time" "git.mchus.pro/mchus/logpile/internal/models" "git.mchus.pro/mchus/logpile/internal/parser" ) var commerCompComponents = []string{"commerslot", "commerhmc", "commerswvr", "commerswcpld"} var commerCompLineRegex = regexp.MustCompile(`^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]\[[^\]]*\]\[(\w+)\]\s*(.+)$`) var commerCompNoisePatterns = []*regexp.Regexp{ regexp.MustCompile(`(?i)invalid comp data`), regexp.MustCompile(`(?i)error in update redis`), regexp.MustCompile(`(?i)mutextimeout\s*=\s*0`), } var commerCompFailureRegex = regexp.MustCompile(`(?i)\b(fail|failed|failure|error)\b`) // ParseCommerCompEvents extracts BMC component failure events from // log/bmc/commer-comp// logs, including rotated *.tar.gz.N parts. func ParseCommerCompEvents(files []parser.ExtractedFile, location *time.Location) []models.Event { var events []models.Event for _, f := range files { path := strings.ToLower(f.Path) if !strings.Contains(path, "/commer-comp/") { continue } component := commerCompComponentFromPath(path) if component == "" { continue } if strings.Contains(path, ".tar.gz") { for _, part := range decodeNestedTarGz(f.Content) { events = append(events, parseCommerCompLog(part, component, location)...) } continue } if !strings.HasSuffix(path, ".log") { continue } events = append(events, parseCommerCompLog(f.Content, component, location)...) } return events } func commerCompComponentFromPath(path string) string { for _, c := range commerCompComponents { if strings.Contains(path, "/"+c+"/") { return c } } return "" } // decodeNestedTarGz unpacks a rotated log part (a full tar.gz archive stored // as e.g. commerhmc.tar.gz.0) and returns the contents of every regular file // inside it. func decodeNestedTarGz(content []byte) [][]byte { gz, err := gzip.NewReader(bytes.NewReader(content)) if err != nil { return nil } defer gz.Close() tr := tar.NewReader(gz) var out [][]byte for { hdr, err := tr.Next() if err == io.EOF { break } if err != nil { break } if hdr.Typeflag != tar.TypeReg { continue } buf, err := io.ReadAll(tr) if err != nil { continue } out = append(out, buf) } return out } func parseCommerCompLog(content []byte, component string, location *time.Location) []models.Event { var events []models.Event scanner := bufio.NewScanner(bytes.NewReader(content)) scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) for scanner.Scan() { line := scanner.Text() m := commerCompLineRegex.FindStringSubmatch(line) if m == nil { continue } msg := strings.TrimSpace(m[3]) if isCommerCompNoise(msg) || !commerCompFailureRegex.MatchString(msg) { continue } ts, err := time.ParseInLocation("2006-01-02 15:04:05", m[1], location) if err != nil { continue } events = append(events, models.Event{ ID: fmt.Sprintf("commer_comp_%s_%d_%s", component, ts.Unix(), commerCompIDSuffix(msg)), Timestamp: ts, Source: fmt.Sprintf("BMC/%s", component), SensorType: "bmc_component", EventType: m[2], Severity: commerCompSeverity(m[2]), Description: msg, RawData: line, }) } return events } func isCommerCompNoise(msg string) bool { for _, re := range commerCompNoisePatterns { if re.MatchString(msg) { return true } } return false } var commerCompNonAlnumRegex = regexp.MustCompile(`[^a-zA-Z0-9]+`) // commerCompIDSuffix derives a short, stable suffix from the message so that // two different failures logged in the same second get distinct event IDs. func commerCompIDSuffix(msg string) string { s := commerCompNonAlnumRegex.ReplaceAllString(strings.ToLower(msg), "_") s = strings.Trim(s, "_") if len(s) > 40 { s = s[:40] } return s } func commerCompSeverity(level string) models.Severity { switch strings.ToLower(level) { case "critial", "critical", "error": return models.SeverityCritical case "warning", "warn": return models.SeverityWarning default: return models.SeverityInfo } }