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
49 for d.ScanRecord() {
50 msg := LogMessage{
51 ID: fmt.Sprintf("%d", time.Now().UnixNano()),
52 Time: time.Now(),
53 }
54 for d.ScanKeyval() {
55 switch string(d.Key()) {
56 case "time":
57 parsed, err := time.Parse(time.RFC3339, string(d.Value()))
58 if err != nil {
59 return 0, fmt.Errorf("parsing time: %w", err)
60 }
61 msg.Time = parsed
62 case "level":
63 msg.Level = strings.ToLower(string(d.Value()))
64 case "msg":
65 msg.Message = string(d.Value())
66 default:
67 if string(d.Key()) == persistKeyArg {
68 msg.Persist = true
69 } else if string(d.Key()) == PersistTimeArg {
70 parsed, err := time.ParseDuration(string(d.Value()))
71 if err != nil {
72 continue
73 }
74 msg.PersistTime = parsed
75 } else {
76 msg.Attributes = append(msg.Attributes, Attr{
77 Key: string(d.Key()),
78 Value: string(d.Value()),
79 })
80 }
81 }
82 }
83 defaultLogData.Add(msg)
84 }
85 if d.Err() != nil {
86 return 0, d.Err()
87 }
88 return len(p), nil
89}
90
91func NewWriter() *writer {
92 w := &writer{}
93 return w
94}
95
96func Subscribe(ctx context.Context) <-chan pubsub.Event[LogMessage] {
97 return defaultLogData.Subscribe(ctx)
98}
99
100func List() []LogMessage {
101 return defaultLogData.List()
102}