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}