// SPDX-FileCopyrightText: 2025 Romain Maneschi // // SPDX-License-Identifier: EUPL-1.2 package logger import ( "context" "crypto/rand" "encoding/hex" "fmt" "log/slog" "os" ) const ( FS_LOGGER_NAME LoggerName = "GitRootFs" SSH_SERVER_LOGGER_NAME LoggerName = "ServerSsh" HTTP_SERVER_LOGGER_NAME LoggerName = "ServerHttp" FS_STORER_LOGGER_NAME LoggerName = "GitRootFsStorer" FS_PLUGIN LoggerName = "FsPlugin" REPOSITORY_MANAGER LoggerName = "RepositoryManager" PLUGIN_MANAGER LoggerName = "PluginManager" BACKGROUND_MANAGER LoggerName = "BackgroundManager" WASM LoggerName = "Wasm" EXEC LoggerName = "ExecManager" REPOSITORY_LOCK LoggerName = "RepositoryLock" ) var levelByName = map[LoggerName]slog.Level{ FS_LOGGER_NAME: slog.LevelWarn, SSH_SERVER_LOGGER_NAME: slog.LevelWarn, HTTP_SERVER_LOGGER_NAME: slog.LevelWarn, FS_STORER_LOGGER_NAME: slog.LevelWarn, FS_PLUGIN: slog.LevelWarn, REPOSITORY_MANAGER: slog.LevelWarn, PLUGIN_MANAGER: slog.LevelWarn, BACKGROUND_MANAGER: slog.LevelWarn, WASM: slog.LevelWarn, EXEC: slog.LevelWarn, REPOSITORY_LOCK: slog.LevelWarn, } type LoggerName string type Logger struct { c context.Context l *slog.Logger level slog.Level name LoggerName } type LoggerPair struct { key string value any } func NewLogger(name LoggerName) *Logger { return NewLoggerCtx(name, context.Background()) } func NewLoggerCtx(name LoggerName, ctx context.Context) *Logger { var level = slog.LevelWarn if l, ok := levelByName[name]; ok { level = l } log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level})) l := &Logger{ c: ctx, l: log, level: level, name: name, } return l.With("name", string(name)) } func GenerateRandomID() string { bytes := make([]byte, 16) if _, err := rand.Read(bytes); err != nil { panic(err) } return hex.EncodeToString(bytes) } func NewLoggerPair(key string, value any) LoggerPair { return LoggerPair{ key: key, value: value, } } type Loggable interface { Log() string } func NewLoggerPairNamedSlice[T Loggable](key string, value []T) LoggerPair { names := make([]string, len(value)) for i, n := range value { names[i] = n.Log() } return LoggerPair{ key: key, value: names, } } func (l *Logger) NewSubLogger(name string) *Logger { return l.NewSubLoggerCtx(name, l.c) } func (l *Logger) NewSubLoggerCtx(name string, ctx context.Context) *Logger { subName := fmt.Sprintf("%s::%s", l.name, name) log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: l.level})) sub := &Logger{ c: ctx, l: log, level: l.level, name: LoggerName(subName), } return sub.With("name", subName) } func (l *Logger) With(key string, value string) *Logger { l.l = slog.New(l.l.Handler().WithAttrs([]slog.Attr{{Key: key, Value: slog.StringValue(value)}})) return l } func (l *Logger) Debug(msg string, attr ...LoggerPair) *Logger { attrs := make([]slog.Attr, len(attr)) for i, a := range attr { attrs[i] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)} } if caller, ok := extractCaller(l.c); ok { attrs = append(attrs, slog.Attr{Key: "caller", Value: slog.StringValue(caller)}) } l.l.LogAttrs(l.c, slog.LevelDebug, msg, attrs...) return l } func (l *Logger) Info(msg string, attr ...LoggerPair) *Logger { attrs := make([]slog.Attr, len(attr)) for i, a := range attr { attrs[i] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)} } if caller, ok := extractCaller(l.c); ok { attrs = append(attrs, slog.Attr{Key: "caller", Value: slog.StringValue(caller)}) } l.l.LogAttrs(l.c, slog.LevelInfo, msg, attrs...) return l } func (l *Logger) Warn(msg string, attr ...LoggerPair) *Logger { attrs := make([]slog.Attr, len(attr)) for i, a := range attr { attrs[i] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)} } l.l.LogAttrs(l.c, slog.LevelWarn, msg, attrs...) return l } func (l *Logger) Error(msg string, err error, attr ...LoggerPair) *Logger { attrs := make([]slog.Attr, len(attr)+1) attrs[0] = slog.Attr{Key: "err", Value: slog.AnyValue(err)} for i, a := range attr { attrs[i+1] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)} } l.l.LogAttrs(l.c, slog.LevelError, msg, attrs...) return l } func (l *Logger) LogCaller(ctx context.Context, msg string) *Logger { attr := slog.Attr{Key: "caller", Value: slog.StringValue("NO")} if caller, ok := extractCaller(ctx); ok { attr = slog.Attr{Key: "caller", Value: slog.StringValue(caller)} } l.l.LogAttrs(l.c, slog.LevelInfo, msg, attr) return l }