// Package generic provides a fallback parser for unrecognized text files package generic import ( "strings" "time" "git.mchus.pro/mchus/logpile/internal/models" "git.mchus.pro/mchus/logpile/internal/parser" ) // parserVersion - version of this parser module const parserVersion = "1.0.0" func init() { parser.Register(&Parser{}) } // Parser implements VendorParser for generic text files type Parser struct{} // Name returns human-readable parser name func (p *Parser) Name() string { return "Generic Text File Parser" } // Vendor returns vendor identifier func (p *Parser) Vendor() string { return "generic" } // Version returns parser version func (p *Parser) Version() string { return parserVersion } // Detect checks if this is a text file (fallback with low confidence) // Returns confidence 0-100 func (p *Parser) Detect(files []parser.ExtractedFile) int { // Only detect if there's exactly one file (plain .gz or single file) if len(files) != 1 { return 0 } file := files[0] // Check if content looks like text (not binary) if !isLikelyText(file.Content) { return 0 } // Return low confidence so other parsers have priority return 15 } // isLikelyText checks if content is likely text (not binary) func isLikelyText(content []byte) bool { // Check first 512 bytes for binary data sample := content if len(content) > 512 { sample = content[:512] } binaryCount := 0 for _, b := range sample { // Count non-printable characters (excluding common whitespace) if b < 32 && b != '\n' && b != '\r' && b != '\t' { binaryCount++ } if b == 0 { // NULL byte is a strong indicator of binary binaryCount += 10 } } // If less than 5% binary, consider it text return binaryCount < len(sample)/20 } // Parse parses generic text file 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), } // Initialize hardware config result.Hardware = &models.HardwareConfig{} if len(files) == 0 { return result, nil } file := files[0] content := string(file.Content) // Create a single event with file info result.Events = append(result.Events, models.Event{ Timestamp: time.Now(), Source: "File", EventType: "Text File", Description: "Generic text file loaded", Severity: models.SeverityInfo, RawData: "Filename: " + file.Path, }) // Try to extract some basic info from common file types if strings.Contains(strings.ToLower(file.Path), "nvidia-bug-report") { parseNvidiaBugReport(content, result) } return result, nil } // parseNvidiaBugReport extracts info from nvidia-bug-report files func parseNvidiaBugReport(content string, result *models.AnalysisResult) { lines := strings.Split(content, "\n") // Look for GPU information for i, line := range lines { // Find NVIDIA driver version if strings.Contains(line, "NVRM version:") || strings.Contains(line, "nvidia-smi") { if i+5 < len(lines) { result.Events = append(result.Events, models.Event{ Timestamp: time.Now(), Source: "NVIDIA Driver", EventType: "Driver Info", Description: "NVIDIA driver information found", Severity: models.SeverityInfo, RawData: strings.TrimSpace(line), }) } } // Find GPU devices if strings.Contains(line, "/proc/driver/nvidia/gpus/") && strings.Contains(line, "***") { result.Events = append(result.Events, models.Event{ Timestamp: time.Now(), Source: "GPU", EventType: "GPU Device", Description: "GPU device detected", Severity: models.SeverityInfo, RawData: strings.TrimSpace(line), }) } } }