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	REPOSITORY_LOCK         LoggerName = "RepositoryLock"
 28)
 29
 30var levelByName = map[LoggerName]slog.Level{
 31	FS_LOGGER_NAME:          slog.LevelWarn,
 32	SSH_SERVER_LOGGER_NAME:  slog.LevelWarn,
 33	HTTP_SERVER_LOGGER_NAME: slog.LevelWarn,
 34	FS_STORER_LOGGER_NAME:   slog.LevelWarn,
 35	FS_PLUGIN:               slog.LevelWarn,
 36	REPOSITORY_MANAGER:      slog.LevelWarn,
 37	PLUGIN_MANAGER:          slog.LevelWarn,
 38	BACKGROUND_MANAGER:      slog.LevelWarn,
 39	WASM:                    slog.LevelWarn,
 40	EXEC:                    slog.LevelWarn,
 41	REPOSITORY_LOCK:         slog.LevelWarn,
 42}
 43
 44type LoggerName string
 45
 46type Logger struct {
 47	c     context.Context
 48	l     *slog.Logger
 49	level slog.Level
 50	name  LoggerName
 51}
 52
 53type LoggerPair struct {
 54	key   string
 55	value any
 56}
 57
 58func NewLogger(name LoggerName) *Logger {
 59	return NewLoggerCtx(name, context.Background())
 60}
 61
 62func NewLoggerCtx(name LoggerName, ctx context.Context) *Logger {
 63	var level = slog.LevelWarn
 64	if l, ok := levelByName[name]; ok {
 65		level = l
 66	}
 67	log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level}))
 68	l := &Logger{
 69		c:     ctx,
 70		l:     log,
 71		level: level,
 72		name:  name,
 73	}
 74	return l.With("name", string(name))
 75}
 76
 77func GenerateRandomID() string {
 78	bytes := make([]byte, 16)
 79	if _, err := rand.Read(bytes); err != nil {
 80		panic(err)
 81	}
 82	return hex.EncodeToString(bytes)
 83}
 84
 85func NewLoggerPair(key string, value any) LoggerPair {
 86	return LoggerPair{
 87		key:   key,
 88		value: value,
 89	}
 90}
 91
 92type Loggable interface {
 93	Log() string
 94}
 95
 96func NewLoggerPairNamedSlice[T Loggable](key string, value []T) LoggerPair {
 97	names := make([]string, len(value))
 98	for i, n := range value {
 99		names[i] = n.Log()
100	}
101	return LoggerPair{
102		key:   key,
103		value: names,
104	}
105}
106
107func (l *Logger) NewSubLogger(name string) *Logger {
108	return l.NewSubLoggerCtx(name, l.c)
109}
110
111func (l *Logger) NewSubLoggerCtx(name string, ctx context.Context) *Logger {
112	subName := fmt.Sprintf("%s::%s", l.name, name)
113	log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: l.level}))
114	sub := &Logger{
115		c:     ctx,
116		l:     log,
117		level: l.level,
118		name:  LoggerName(subName),
119	}
120	return sub.With("name", subName)
121}
122
123func (l *Logger) With(key string, value string) *Logger {
124	l.l = slog.New(l.l.Handler().WithAttrs([]slog.Attr{{Key: key, Value: slog.StringValue(value)}}))
125	return l
126}
127
128func (l *Logger) Debug(msg string, attr ...LoggerPair) *Logger {
129	attrs := make([]slog.Attr, len(attr))
130	for i, a := range attr {
131		attrs[i] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)}
132	}
133	if caller, ok := extractCaller(l.c); ok {
134		attrs = append(attrs, slog.Attr{Key: "caller", Value: slog.StringValue(caller)})
135	}
136	l.l.LogAttrs(l.c, slog.LevelDebug, msg, attrs...)
137	return l
138}
139
140func (l *Logger) Info(msg string, attr ...LoggerPair) *Logger {
141	attrs := make([]slog.Attr, len(attr))
142	for i, a := range attr {
143		attrs[i] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)}
144	}
145	if caller, ok := extractCaller(l.c); ok {
146		attrs = append(attrs, slog.Attr{Key: "caller", Value: slog.StringValue(caller)})
147	}
148	l.l.LogAttrs(l.c, slog.LevelInfo, msg, attrs...)
149	return l
150}
151
152func (l *Logger) Warn(msg string, attr ...LoggerPair) *Logger {
153	attrs := make([]slog.Attr, len(attr))
154	for i, a := range attr {
155		attrs[i] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)}
156	}
157	l.l.LogAttrs(l.c, slog.LevelWarn, msg, attrs...)
158	return l
159}
160
161func (l *Logger) Error(msg string, err error, attr ...LoggerPair) *Logger {
162	attrs := make([]slog.Attr, len(attr)+1)
163	attrs[0] = slog.Attr{Key: "err", Value: slog.AnyValue(err)}
164	for i, a := range attr {
165		attrs[i+1] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)}
166	}
167	l.l.LogAttrs(l.c, slog.LevelError, msg, attrs...)
168	return l
169}
170
171func (l *Logger) LogCaller(ctx context.Context, msg string) *Logger {
172	attr := slog.Attr{Key: "caller", Value: slog.StringValue("NO")}
173	if caller, ok := extractCaller(ctx); ok {
174		attr = slog.Attr{Key: "caller", Value: slog.StringValue(caller)}
175	}
176	l.l.LogAttrs(l.c, slog.LevelInfo, msg, attr)
177	return l
178}