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