logger.go

  1package logging
  2
  3import (
  4	"context"
  5	"io"
  6	"log/slog"
  7	"slices"
  8
  9	"github.com/kujtimiihoxha/termai/internal/pubsub"
 10	"golang.org/x/exp/maps"
 11)
 12
 13const DefaultLevel = "info"
 14
 15const (
 16	persistKeyArg  = "$persist"
 17	PersistTimeArg = "$persist_time"
 18)
 19
 20var levels = map[string]slog.Level{
 21	"debug":      slog.LevelDebug,
 22	DefaultLevel: slog.LevelInfo,
 23	"warn":       slog.LevelWarn,
 24	"error":      slog.LevelError,
 25}
 26
 27func ValidLevels() []string {
 28	keys := maps.Keys(levels)
 29	slices.SortFunc(keys, func(a, b string) int {
 30		if a == DefaultLevel {
 31			return -1
 32		}
 33		if b == DefaultLevel {
 34			return 1
 35		}
 36		if a < b {
 37			return -1
 38		}
 39		return 1
 40	})
 41	return keys
 42}
 43
 44func NewLogger(opts Options) Interface {
 45	logger := &Logger{}
 46	broker := pubsub.NewBroker[LogMessage]()
 47	writer := &writer{
 48		messages: []LogMessage{},
 49		Broker:   broker,
 50	}
 51
 52	handler := slog.NewTextHandler(
 53		io.MultiWriter(writer),
 54		&slog.HandlerOptions{
 55			Level: slog.Level(levels[opts.Level]),
 56		},
 57	)
 58	logger.logger = slog.New(handler)
 59	logger.writer = writer
 60
 61	return logger
 62}
 63
 64type Options struct {
 65	Level string
 66}
 67
 68type Logger struct {
 69	logger *slog.Logger
 70	writer *writer
 71}
 72
 73func (l *Logger) SetLevel(level string) {
 74	if _, ok := levels[level]; !ok {
 75		level = DefaultLevel
 76	}
 77	handler := slog.NewTextHandler(
 78		io.MultiWriter(l.writer),
 79		&slog.HandlerOptions{
 80			Level: levels[level],
 81		},
 82	)
 83	l.logger = slog.New(handler)
 84}
 85
 86// PersistDebug implements Interface.
 87func (l *Logger) PersistDebug(msg string, args ...any) {
 88	args = append(args, persistKeyArg, true)
 89	l.Debug(msg, args...)
 90}
 91
 92// PersistError implements Interface.
 93func (l *Logger) PersistError(msg string, args ...any) {
 94	args = append(args, persistKeyArg, true)
 95	l.Error(msg, args...)
 96}
 97
 98// PersistInfo implements Interface.
 99func (l *Logger) PersistInfo(msg string, args ...any) {
100	args = append(args, persistKeyArg, true)
101	l.Info(msg, args...)
102}
103
104// PersistWarn implements Interface.
105func (l *Logger) PersistWarn(msg string, args ...any) {
106	args = append(args, persistKeyArg, true)
107	l.Warn(msg, args...)
108}
109
110func (l *Logger) Debug(msg string, args ...any) {
111	l.logger.Debug(msg, args...)
112}
113
114func (l *Logger) Info(msg string, args ...any) {
115	l.logger.Info(msg, args...)
116}
117
118func (l *Logger) Warn(msg string, args ...any) {
119	l.logger.Warn(msg, args...)
120}
121
122func (l *Logger) Error(msg string, args ...any) {
123	l.logger.Error(msg, args...)
124}
125
126func (l *Logger) List() []LogMessage {
127	return l.writer.messages
128}
129
130func (l *Logger) Get(id string) (LogMessage, error) {
131	for _, msg := range l.writer.messages {
132		if msg.ID == id {
133			return msg, nil
134		}
135	}
136	return LogMessage{}, io.EOF
137}
138
139func (l *Logger) Subscribe(ctx context.Context) <-chan pubsub.Event[LogMessage] {
140	return l.writer.Subscribe(ctx)
141}