package journald import ( "context" "fmt" "iter" "strings" "sync" "time" "github.com/coreos/go-systemd/v22/sdjournal" ) func syslogMatchData(syslogIdentifier string) string { builder := strings.Builder{} builder.WriteString(sdjournal.SD_JOURNAL_FIELD_SYSLOG_IDENTIFIER) builder.WriteString("=") builder.WriteString(syslogIdentifier) return builder.String() } type Journald struct { journal *sdjournal.Journal lock sync.Mutex } func NewJournald(syslogName string) (*Journald, error) { journal, err := sdjournal.NewJournal() if err != nil { return nil, fmt.Errorf("failed to initialize journal: %w", err) } if syslogName != "" { matchData := syslogMatchData(syslogName) err = journal.AddMatch(matchData) if err != nil { return nil, fmt.Errorf("failed to add match %s to journal: %w", matchData, err) } } err = journal.SeekTail() if err != nil { return nil, fmt.Errorf("failed to seek to tail: %w", err) } _, err = journal.Previous() if err != nil { return nil, fmt.Errorf("failed to go to tail message: %w", err) } return &Journald{ journal: journal, }, nil } func (j *Journald) Close() error { err := j.journal.Close() if err != nil { return fmt.Errorf("failed to close journal: %w", err) } return nil } func (j *Journald) Next() (*sdjournal.JournalEntry, error) { j.lock.Lock() defer j.lock.Unlock() entryCount, err := j.journal.Next() if err != nil { return nil, fmt.Errorf("failed to get next journald entry: %w", err) } if entryCount == 0 { return nil, nil } entry, err := j.journal.GetEntry() if err != nil { return nil, fmt.Errorf("failed to get journald entry: %w", err) } return entry, nil } func (j *Journald) Iter(ctx context.Context) iter.Seq2[*sdjournal.JournalEntry, error] { return func(yield func(*sdjournal.JournalEntry, error) bool) { for { j.journal.Wait(time.Second) if ctx.Err() != nil { yield(nil, fmt.Errorf("failed to iterate on journald, context error: %w", ctx.Err())) return } entry, err := j.Next() if err != nil { yield(nil, err) return } // no more entries at the moment if entry == nil { continue } // we were told to stop if !yield(entry, nil) { return } } } }