1package logging
  2
  3import (
  4	"bytes"
  5	"context"
  6	"fmt"
  7	"strings"
  8	"sync"
  9	"time"
 10
 11	"github.com/go-logfmt/logfmt"
 12	"github.com/opencode-ai/opencode/internal/pubsub"
 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}