Files
backrest/internal/eventlog/simplefilelog.go
garethgeorge dd9e14e450 Initial commit
2023-11-10 00:59:48 -08:00

159 lines
2.8 KiB
Go

package eventlog
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"sync"
)
// SimpleLogFile is a simple log file implementation that appends to a file.
type SimpleLogFile struct {
file string // path to the log file
handle *os.File // file handle
mu sync.Mutex
}
func NewSimpleLogFile(file string) *SimpleLogFile {
return &SimpleLogFile{
file: file,
}
}
var _ LogFile = &SimpleLogFile{}
func (s *SimpleLogFile) open() error {
if s.handle != nil {
return nil
}
err := os.MkdirAll(filepath.Dir(s.file), 0755)
if err != nil {
return fmt.Errorf("failed to create parent dirs of log file %s: %w", s.file, err)
}
f, err := os.OpenFile(s.file, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("failed to open log file %s: %w", s.file, err)
}
s.handle = f
return nil
}
func (s *SimpleLogFile) Log(event interface{}) error {
data, err := json.Marshal(event)
if err != nil {
return fmt.Errorf("failed to marshal event: %w", err)
}
s.mu.Lock()
defer s.mu.Unlock()
err = s.open()
if err != nil {
return err
}
s.handle.Write(data)
s.handle.Write([]byte("\n"))
return nil
}
func (s *SimpleLogFile) Size() (int, error) {
s.mu.Lock()
defer s.mu.Unlock()
err := s.open()
if err != nil {
return 0, err
}
stat, err := s.handle.Stat()
if err != nil {
return 0, fmt.Errorf("failed to stat log file %s: %w", s.file, err)
}
return int(stat.Size()), nil
}
func (s *SimpleLogFile) Close() error {
s.mu.Lock()
defer s.mu.Unlock()
if s.handle == nil {
return nil
}
err := s.handle.Close()
if err != nil {
return fmt.Errorf("failed to close log file %s: %w", s.file, err)
}
s.handle = nil
return nil
}
// Iterator returns a function that can be called to iterate over the log file.
func (s *SimpleLogFile) Iterator() (LogIterator, error) {
s.mu.Lock()
f, err := os.OpenFile(s.file, os.O_RDONLY, 0644)
if err != nil {
s.mu.Unlock()
if errors.Is(err, os.ErrNotExist) {
return &funcLogIterator{
nextFunc: func() interface{} {
return nil
},
closeFunc: func() error {
return nil
},
}, nil
}
return nil, fmt.Errorf("failed to open log file %s: %w", s.file, err)
}
stat, err := f.Stat()
if err != nil {
s.mu.Unlock()
return nil, fmt.Errorf("failed to stat log file %s: %w", s.file, err)
}
size := int(stat.Size())
s.mu.Unlock()
reader := io.NewSectionReader(f, 0, int64(size))
scanner := bufio.NewScanner(reader)
nextFunc := func() interface{} {
if !scanner.Scan() {
return nil
}
var event interface{}
err := json.Unmarshal(scanner.Bytes(), &event)
if err != nil {
log.Default().Printf("failed to unmarshal event: %v", err)
return nil
}
return event
}
closeFunc := func() error {
return f.Close()
}
return &funcLogIterator{
nextFunc: nextFunc,
closeFunc: closeFunc,
}, nil
}