package logger import ( "bufio" "compress/gzip" "encoding/json" "fmt" "io" "os" "path/filepath" "sort" "strings" "time" ) // ReadLogs returns all log entries for agentID between from and to (inclusive). func ReadLogs(baseDir, agentID string, from, to time.Time) ([]json.RawMessage, error) { var result []json.RawMessage for d := from; !d.After(to); d = d.AddDate(0, 0, 1) { entries, err := ReadDayLogs(baseDir, agentID, d) if err != nil { continue // skip missing days } result = append(result, entries...) } return result, nil } // ReadDayLogs returns all log entries for a specific day. func ReadDayLogs(baseDir, agentID string, date time.Time) ([]json.RawMessage, error) { dir := filepath.Join(baseDir, agentID) prefix := date.Format("2006-01-02") entries, err := os.ReadDir(dir) if err != nil { return nil, fmt.Errorf("read dir %s: %w", dir, err) } var result []json.RawMessage for _, e := range entries { name := e.Name() if !strings.HasPrefix(name, prefix) || !isLogFile(name) { continue } lines, err := readLogFile(filepath.Join(dir, name)) if err != nil { continue } result = append(result, lines...) } return result, nil } // SearchLogs returns log entries where field equals value, within the date range. func SearchLogs(baseDir, agentID string, field, value string, from, to time.Time) ([]json.RawMessage, error) { all, err := ReadLogs(baseDir, agentID, from, to) if err != nil { return nil, err } var matched []json.RawMessage for _, raw := range all { var m map[string]any if json.Unmarshal(raw, &m) != nil { continue } if v, ok := m[field]; ok && fmt.Sprint(v) == value { matched = append(matched, raw) } } return matched, nil } // ListAgents returns the agent IDs that have log directories under baseDir. func ListAgents(baseDir string) ([]string, error) { entries, err := os.ReadDir(baseDir) if err != nil { return nil, fmt.Errorf("read base dir %s: %w", baseDir, err) } var ids []string for _, e := range entries { if e.IsDir() { ids = append(ids, e.Name()) } } sort.Strings(ids) return ids, nil } // ListDates returns the dates for which logs exist for the given agent. func ListDates(baseDir, agentID string) ([]time.Time, error) { dir := filepath.Join(baseDir, agentID) entries, err := os.ReadDir(dir) if err != nil { return nil, fmt.Errorf("read dir %s: %w", dir, err) } seen := make(map[string]bool) var dates []time.Time for _, e := range entries { if e.IsDir() || !isLogFile(e.Name()) { continue } d := parseDateFromFilename(e.Name()) if d.IsZero() { continue } key := d.Format("2006-01-02") if !seen[key] { seen[key] = true dates = append(dates, d) } } sort.Slice(dates, func(i, j int) bool { return dates[i].Before(dates[j]) }) return dates, nil } // readLogFile reads all JSONL lines from a file (.jsonl or .jsonl.gz). func readLogFile(path string) ([]json.RawMessage, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() var r io.Reader = f if strings.HasSuffix(path, ".gz") { gz, err := gzip.NewReader(f) if err != nil { return nil, err } defer gz.Close() r = gz } var lines []json.RawMessage scanner := bufio.NewScanner(r) scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) for scanner.Scan() { line := scanner.Bytes() if len(line) == 0 { continue } cp := make([]byte, len(line)) copy(cp, line) lines = append(lines, json.RawMessage(cp)) } return lines, scanner.Err() }