GitRoot

craft your forge, build your project, grow your community freely
  1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: EUPL-1.2
  4
  5package logger
  6
  7import (
  8	"context"
  9	"crypto/rand"
 10	"encoding/hex"
 11	"fmt"
 12	"log/slog"
 13	"os"
 14)
 15
 16const (
 17	FS_LOGGER_NAME          LoggerName = "GitRootFs"
 18	SSH_SERVER_LOGGER_NAME  LoggerName = "ServerSsh"
 19	HTTP_SERVER_LOGGER_NAME LoggerName = "ServerHttp"
 20	FS_STORER_LOGGER_NAME   LoggerName = "GitRootFsStorer"
 21	FS_PLUGIN               LoggerName = "FsPlugin"
 22	REPOSITORY_MANAGER      LoggerName = "RepositoryManager"
 23	PLUGIN_MANAGER          LoggerName = "PluginManager"
 24	BACKGROUND_MANAGER      LoggerName = "BackgroundManager"
 25	WASM                    LoggerName = "Wasm"
 26	EXEC                    LoggerName = "ExecManager"
 27)
 28
 29var levelByName = map[LoggerName]slog.Level{
 30	FS_LOGGER_NAME:          slog.LevelWarn,
 31	SSH_SERVER_LOGGER_NAME:  slog.LevelWarn,
 32	HTTP_SERVER_LOGGER_NAME: slog.LevelWarn,
 33	FS_STORER_LOGGER_NAME:   slog.LevelWarn,
 34	FS_PLUGIN:               slog.LevelWarn,
 35	REPOSITORY_MANAGER:      slog.LevelWarn,
 36	PLUGIN_MANAGER:          slog.LevelDebug,
 37	BACKGROUND_MANAGER:      slog.LevelWarn,
 38	WASM:                    slog.LevelWarn,
 39	EXEC:                    slog.LevelWarn,
 40}
 41
 42type LoggerName string
 43
 44type Logger struct {
 45	c     context.Context
 46	l     *slog.Logger
 47	level slog.Level
 48	name  LoggerName
 49}
 50
 51type LoggerPair struct {
 52	key   string
 53	value any
 54}
 55
 56func NewLogger(name LoggerName) *Logger {
 57	return NewLoggerCtx(name, context.Background())
 58}
 59
 60func NewLoggerCtx(name LoggerName, ctx context.Context) *Logger {
 61	var level = slog.LevelWarn
 62	if l, ok := levelByName[name]; ok {
 63		level = l
 64	}
 65	log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level}))
 66	l := &Logger{
 67		c:     ctx,
 68		l:     log,
 69		level: level,
 70		name:  name,
 71	}
 72	return l.With("name", string(name))
 73}
 74
 75func GenerateRandomID() string {
 76	bytes := make([]byte, 16)
 77	if _, err := rand.Read(bytes); err != nil {
 78		panic(err)
 79	}
 80	return hex.EncodeToString(bytes)
 81}
 82
 83func NewLoggerPair(key string, value any) LoggerPair {
 84	return LoggerPair{
 85		key:   key,
 86		value: value,
 87	}
 88}
 89
 90type Loggable interface {
 91	Log() string
 92}
 93
 94func NewLoggerPairNamedSlice[T Loggable](key string, value []T) LoggerPair {
 95	names := make([]string, len(value))
 96	for i, n := range value {
 97		names[i] = n.Log()
 98	}
 99	return LoggerPair{
100		key:   key,
101		value: names,
102	}
103}
104
105func (l *Logger) NewSubLogger(name string) *Logger {
106	return l.NewSubLoggerCtx(name, l.c)
107}
108
109func (l *Logger) NewSubLoggerCtx(name string, ctx context.Context) *Logger {
110	subName := fmt.Sprintf("%s::%s", l.name, name)
111	log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: l.level}))
112	sub := &Logger{
113		c:     ctx,
114		l:     log,
115		level: l.level,
116		name:  LoggerName(subName),
117	}
118	return sub.With("name", subName)
119}
120
121func (l *Logger) With(key string, value string) *Logger {
122	l.l = slog.New(l.l.Handler().WithAttrs([]slog.Attr{{Key: key, Value: slog.StringValue(value)}}))
123	return l
124}
125
126func (l *Logger) Debug(msg string, attr ...LoggerPair) *Logger {
127	attrs := make([]slog.Attr, len(attr))
128	for i, a := range attr {
129		attrs[i] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)}
130	}
131	if caller, ok := extractCaller(l.c); ok {
132		attrs = append(attrs, slog.Attr{Key: "caller", Value: slog.StringValue(caller)})
133	}
134	l.l.LogAttrs(l.c, slog.LevelDebug, msg, attrs...)
135	return l
136}
137
138func (l *Logger) Info(msg string, attr ...LoggerPair) *Logger {
139	attrs := make([]slog.Attr, len(attr))
140	for i, a := range attr {
141		attrs[i] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)}
142	}
143	if caller, ok := extractCaller(l.c); ok {
144		attrs = append(attrs, slog.Attr{Key: "caller", Value: slog.StringValue(caller)})
145	}
146	l.l.LogAttrs(l.c, slog.LevelInfo, msg, attrs...)
147	return l
148}
149
150func (l *Logger) Warn(msg string, attr ...LoggerPair) *Logger {
151	attrs := make([]slog.Attr, len(attr))
152	for i, a := range attr {
153		attrs[i] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)}
154	}
155	l.l.LogAttrs(l.c, slog.LevelWarn, msg, attrs...)
156	return l
157}
158
159func (l *Logger) Error(msg string, err error, attr ...LoggerPair) *Logger {
160	attrs := make([]slog.Attr, len(attr)+1)
161	attrs[0] = slog.Attr{Key: "err", Value: slog.AnyValue(err)}
162	for i, a := range attr {
163		attrs[i+1] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)}
164	}
165	l.l.LogAttrs(l.c, slog.LevelError, msg, attrs...)
166	return l
167}
168
169func (l *Logger) LogCaller(ctx context.Context, msg string) *Logger {
170	attr := slog.Attr{Key: "caller", Value: slog.StringValue("NO")}
171	if caller, ok := extractCaller(ctx); ok {
172		attr = slog.Attr{Key: "caller", Value: slog.StringValue(caller)}
173	}
174	l.l.LogAttrs(l.c, slog.LevelInfo, msg, attr)
175	return l
176}