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