goatcounter-systemd/journald/journald.go

115 lines
2.2 KiB
Go
Raw Normal View History

2024-09-28 16:20:24 -04:00
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
}
}
}
}