writer.go

  1package logging
  2
  3import (
  4	"bytes"
  5	"context"
  6	"fmt"
  7	"strings"
  8	"sync"
  9	"time"
 10
 11	"github.com/charmbracelet/crush/internal/pubsub"
 12	"github.com/go-logfmt/logfmt"
 13)
 14
 15const (
 16	persistKeyArg  = "$_persist"
 17	PersistTimeArg = "$_persist_time"
 18)
 19
 20type LogData struct {
 21	messages []LogMessage
 22	*pubsub.Broker[LogMessage]
 23	lock sync.Mutex
 24}
 25
 26func (l *LogData) Add(msg LogMessage) {
 27	l.lock.Lock()
 28	defer l.lock.Unlock()
 29	l.messages = append(l.messages, msg)
 30	l.Publish(pubsub.CreatedEvent, msg)
 31}
 32
 33func (l *LogData) List() []LogMessage {
 34	l.lock.Lock()
 35	defer l.lock.Unlock()
 36	return l.messages
 37}
 38
 39var defaultLogData = &LogData{
 40	messages: make([]LogMessage, 0),
 41	Broker:   pubsub.NewBroker[LogMessage](),
 42}
 43
 44type writer struct{}
 45
 46func (w *writer) Write(p []byte) (int, error) {
 47	d := logfmt.NewDecoder(bytes.NewReader(p))
 48	for d.ScanRecord() {
 49		msg := LogMessage{
 50			ID:   fmt.Sprintf("%d", time.Now().UnixNano()),
 51			Time: time.Now(),
 52		}
 53		for d.ScanKeyval() {
 54			switch string(d.Key()) {
 55			case "time":
 56				parsed, err := time.Parse(time.RFC3339, string(d.Value()))
 57				if err != nil {
 58					return 0, fmt.Errorf("parsing time: %w", err)
 59				}
 60				msg.Time = parsed
 61			case "level":
 62				msg.Level = strings.ToLower(string(d.Value()))
 63			case "msg":
 64				msg.Message = string(d.Value())
 65			default:
 66				if string(d.Key()) == persistKeyArg {
 67					msg.Persist = true
 68				} else if string(d.Key()) == PersistTimeArg {
 69					parsed, err := time.ParseDuration(string(d.Value()))
 70					if err != nil {
 71						continue
 72					}
 73					msg.PersistTime = parsed
 74				} else {
 75					msg.Attributes = append(msg.Attributes, Attr{
 76						Key:   string(d.Key()),
 77						Value: string(d.Value()),
 78					})
 79				}
 80			}
 81		}
 82		defaultLogData.Add(msg)
 83	}
 84	if d.Err() != nil {
 85		return 0, d.Err()
 86	}
 87	return len(p), nil
 88}
 89
 90func NewWriter() *writer {
 91	w := &writer{}
 92	return w
 93}
 94
 95func Subscribe(ctx context.Context) <-chan pubsub.Event[LogMessage] {
 96	return defaultLogData.Subscribe(ctx)
 97}
 98
 99func List() []LogMessage {
100	return defaultLogData.List()
101}