package storage import ( "bufio" "context" "encoding/json" "os" "strings" "git.tornberg.me/go-otel/internal/websocket" "github.com/natefinch/lumberjack" ) type FileStore struct { logger *lumberjack.Logger } func NewFileStore(filename string) *FileStore { return &FileStore{ logger: &lumberjack.Logger{ Filename: filename, MaxSize: 100, // megabytes MaxBackups: 3, MaxAge: 28, // days Compress: true, // disabled by default }, } } func (fs *FileStore) Append(ctx context.Context, entry websocket.HistoryEntry) error { data, err := json.Marshal(entry) if err != nil { return err } _, err = fs.logger.Write(append(data, '\n')) return err } func (fs *FileStore) GetHistory(ctx context.Context, limit int) ([]websocket.HistoryEntry, error) { return fs.Search(ctx, "", limit) } func (fs *FileStore) Search(ctx context.Context, query string, limit int) ([]websocket.HistoryEntry, error) { if limit <= 0 { limit = 50 // Default to a manageable number } file, err := os.Open(fs.logger.Filename) if err != nil { if os.IsNotExist(err) { return nil, nil // No logs yet } return nil, err } defer file.Close() var matchingLines []string scanner := bufio.NewScanner(file) // Lowercase query for case-insensitive search lowerQuery := strings.ToLower(query) for scanner.Scan() { line := scanner.Text() if query == "" || strings.Contains(strings.ToLower(line), lowerQuery) { matchingLines = append(matchingLines, line) // Only keep the most recent 'limit' matches to save memory if len(matchingLines) > limit { matchingLines = matchingLines[1:] } } } var results []websocket.HistoryEntry // Reverse to get latest first (since matchingLines contains them in chronological order) for i := len(matchingLines) - 1; i >= 0; i-- { var entry websocket.HistoryEntry if err := json.Unmarshal([]byte(matchingLines[i]), &entry); err == nil { results = append(results, entry) } } return results, scanner.Err() }